Build and package ogagent py3/qt6 for windows and linux #2

Merged
nserrano merged 9 commits from py3-win into main 2024-07-23 09:58:23 +02:00
30 changed files with 1180 additions and 357 deletions

12
.gitignore vendored
View File

@ -11,10 +11,20 @@ linux/debian/ogagent.postinst.debhelper
linux/debian/ogagent.postrm.debhelper linux/debian/ogagent.postrm.debhelper
linux/configure-stamp linux/configure-stamp
linux/build-stamp linux/build-stamp
macos/build
windows/vc_redist.x64.exe
ogagent_*_all.deb ogagent_*_all.deb
ogagent_*_amd64.buildinfo ogagent_*_amd64.buildinfo
ogagent_*_amd64.changes ogagent_*_amd64.changes
ogagent_*_amd64.build ogagent_*_amd64.build
OGAgentInstaller-*.pkg
OGAgentSetup-*.exe
bin
src/build
src/dist
src/about_dialog_ui.py src/about_dialog_ui.py
src/message_dialog_ui.py src/message_dialog_ui.py
src/OGAgent_rc.py src/dist
src/build
windows/VERSION
windows/VC_redist.x64.exe

View File

@ -39,7 +39,6 @@ install-ogagent:
cp $(SOURCEDIR)/OGAgentUser.py $(LIBDIR) cp $(SOURCEDIR)/OGAgentUser.py $(LIBDIR)
# QT Dialogs & resources # QT Dialogs & resources
cp $(SOURCEDIR)/*_ui.py $(LIBDIR) cp $(SOURCEDIR)/*_ui.py $(LIBDIR)
cp $(SOURCEDIR)/OGAgent_rc.py $(LIBDIR)
# Version file # Version file
cp $(SOURCEDIR)/VERSION $(LIBDIR) cp $(SOURCEDIR)/VERSION $(LIBDIR)

View File

@ -1,3 +1,11 @@
ogagent (1.3.1-1) stable; urgency=medium
* Migrate the update script from shell to python
* pyinstaller: include the 'img' subdir
* take icons from 'img'
-- OpenGnsys developers <info@opengnsys.es> Wed, 26 Jun 2024 15:16:57 +0200
ogagent (1.3.0-2) stable; urgency=medium ogagent (1.3.0-2) stable; urgency=medium
* Add missing dependency on zenity * Add missing dependency on zenity

0
linux/desktop/OGAgentTool.desktop 100755 → 100644
View File

View File

@ -1,9 +1,9 @@
#!/bin/bash #!/bin/bash
# Create macOS installation packages. # Create macOS installation packages.
# Based on bomutils tutorail: http://bomutils.dyndns.org/tutorial.html # Based on bomutils tutorial: http://bomutils.dyndns.org/tutorial.html
cd $(dirname $0) cd $(dirname $0)
[ -r ../src/VERSION ] && VERSION="$(cat ../src/VERSION)" || VERSION="1.1.0" [ -r ../src/VERSION ] && VERSION="$(cat ../src/VERSION)" || (echo "Can't get version from ../src/VERSION" 1>&2; exit 1)
AUTHOR="OpenGnsys Project" AUTHOR="OpenGnsys Project"
# Create empty directories. # Create empty directories.
@ -12,8 +12,8 @@ mkdir -p build && cd build
mkdir -p flat/base.pkg flat/Resources/en.lproj mkdir -p flat/base.pkg flat/Resources/en.lproj
mkdir -p root/Applications mkdir -p root/Applications
# Copy application and script files. # Copy application and script files. Exclude 'test_modules'
cp -a ../../src root/Applications/OGAgent.app cp -a ../../src root/Applications/OGAgent.app; rm -rf root/Applications/OGAgent.app/test_modules
cp -a ../scripts . cp -a ../scripts .
# Create plist file. # Create plist file.
@ -84,4 +84,3 @@ EOT
# Create new Xar application archive. # Create new Xar application archive.
rm -f ../../../OGAgentInstaller-$VERSION.pkg rm -f ../../../OGAgentInstaller-$VERSION.pkg
( cd flat && xar --compression none -cf "../../../OGAgentInstaller-$VERSION.pkg" * ) ( cd flat && xar --compression none -cf "../../../OGAgentInstaller-$VERSION.pkg" * )

View File

@ -2,10 +2,10 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>Label</key> <key>Label</key>
<string>es.opengnsys.ogagent</string> <string>es.opengnsys.agent.system</string>
<key>ProgramArguments</key> <key>ProgramArguments</key>
<array> <array>
<string>/usr/bin/ogagent</string> <string>/usr/local/bin/ogagent</string>
<string>start</string> <string>start</string>
</array> </array>
<key>RunAtLoad</key> <key>RunAtLoad</key>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd>
<plist version="1.0">
<dict>
<key>Label</key>
<string>es.opengnsys.agent.user</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>WorkingDirectory</key>
<string>/Applications/OGAgent.app</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/OGAgent.app/OGAgentUser.py</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>

832
macos/scripts/ip.py 100755
View File

@ -0,0 +1,832 @@
#!/usr/bin/env python3
# encoding: utf8
"""
This program is taken from https://github.com/brona/iproute2mac
When doing 'brew install iproute2mac', we get an old version (1.4.2) that doesn't support 'ip -json'
The alternative installation method recomended in the project's README file is 'curl; chmod; mv'
Therefore we make the decision of shipping this ip.py (version 1.5.0) along opengnsys, which is pretty much the same as curling it
"""
"""
iproute2mac
CLI wrapper for basic network utilites on Mac OS X.
Homepage: https://github.com/brona/iproute2mac
The MIT License (MIT)
Copyright (c) 2015 Bronislav Robenek <brona@robenek.me>
"""
import ipaddress
import json
import os
import random
import re
import socket
import string
import subprocess
import sys
import types
# Version
VERSION = "1.5.0"
# Utilities
SUDO = "/usr/bin/sudo"
IFCONFIG = "/sbin/ifconfig"
ROUTE = "/sbin/route"
NETSTAT = "/usr/sbin/netstat"
NDP = "/usr/sbin/ndp"
ARP = "/usr/sbin/arp"
NETWORKSETUP = "/usr/sbin/networksetup"
# Helper functions
def perror(*args):
sys.stderr.write(*args)
sys.stderr.write("\n")
def execute_cmd(cmd):
print("Executing: %s" % cmd)
status, output = subprocess.getstatusoutput(cmd)
if status == 0: # unix/linux commands 0 true, 1 false
print(output)
return True
else:
perror(output)
return False
def json_dump(data, pretty):
if pretty:
print(json.dumps(data, indent=4))
else:
print(json.dumps(data, separators=(",", ":")))
return True
# Classful to CIDR conversion with "default" being passed through
def cidr_from_netstat_dst(target):
if target == "default":
return target
dots = target.count(".")
if target.find("/") == -1:
addr = target
netmask = (dots + 1) * 8
else:
[addr, netmask] = target.split("/")
addr = addr + ".0" * (3 - dots)
return addr + "/" + str(netmask)
# Convert hexadecimal netmask in prefix length
def netmask_to_length(mask):
return int(mask, 16).bit_count()
def any_startswith(words, test):
for word in words:
if word.startswith(test):
return True
return False
# Handles passsing return value, error messages and program exit on error
def help_msg(help_func):
def wrapper(func):
def inner(*args, **kwargs):
if not func(*args, **kwargs):
specific = eval(help_func)
if specific:
if isinstance(specific, types.FunctionType):
if args and kwargs:
specific(*args, **kwargs)
else:
specific()
return False
else:
raise Exception("Function expected for: " + help_func)
else:
raise Exception(
"Function variant not defined: " + help_func
)
return True
return inner
return wrapper
# Generate random MAC address with XenSource Inc. OUI
# http://www.linux-kvm.com/sites/default/files/macgen.py
def randomMAC():
mac = [
0x00,
0x16,
0x3E,
random.randint(0x00, 0x7F),
random.randint(0x00, 0xFF),
random.randint(0x00, 0xFF),
]
return ":".join(["%02x" % x for x in mac])
# Decode ifconfig output
def parse_ifconfig(res, af, address):
links = []
count = 1
for r in res.split("\n"):
if re.match(r"^\w+:", r):
if count > 1:
links.append(link)
(ifname, flags, mtu, ifindex) = re.findall(r"^(\w+): flags=\d+<(.*)> mtu (\d+) index (\d+)", r)[0]
flags = flags.split(",")
link = {
"ifindex": int(ifindex),
"ifname": ifname,
"flags": flags,
"mtu": int(mtu),
"operstate": "UNKNOWN",
"link_type": "unknown"
}
if "LOOPBACK" in flags:
link["link_type"] = "loopback"
link["address"] = "00:00:00:00:00:00"
link["broadcast"] = "00:00:00:00:00:00"
elif "POINTOPOINT" in flags:
link["link_type"] = "none"
count = count + 1
else:
if re.match(r"^\s+ether ", r):
link["link_type"] = "ether"
link["address"] = re.findall(r"(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)", r)[0]
link["broadcast"] = "ff:ff:ff:ff:ff:ff"
elif address and re.match(r"^\s+inet ", r) and af != 6:
(local, netmask) = re.findall(r"inet (\d+\.\d+\.\d+\.\d+) netmask (0x[0-9a-f]+)", r)[0]
addr = {
"family": "inet",
"local": local,
"prefixlen": netmask_to_length(netmask),
}
if re.match(r"^.*broadcast", r):
addr["broadcast"] = re.findall(r"broadcast (\d+\.\d+\.\d+\.\d+)", r)[0]
link["addr_info"] = link.get("addr_info", []) + [addr]
elif address and re.match(r"^\s+inet6 ", r) and af != 4:
(local, prefixlen) = re.findall(r"inet6 ((?:[a-f0-9:]+:+)+[a-f0-9]+)%*\w* +prefixlen (\d+)", r)[0]
link["addr_info"] = link.get("addr_info", []) + [{
"family": "inet6",
"local": local,
"prefixlen": int(prefixlen)
}]
elif re.match(r"^\s+status: ", r):
match re.findall(r"status: (\w+)", r)[0]:
case "active":
link["operstate"] = "UP"
case "inactive":
link["operstate"] = "DOWN"
if count > 1:
links.append(link)
return links
def link_addr_show(argv, af, json_print, pretty_json, address):
if len(argv) > 0 and argv[0] == "dev":
argv.pop(0)
if len(argv) > 0:
param = argv[0]
else:
param = "-a"
status, res = subprocess.getstatusoutput(
IFCONFIG + " -v " + param + " 2>/dev/null"
)
if status: # unix status
if res == "":
perror(param + " not found")
else:
perror(res)
return False
links = parse_ifconfig(res, af, address)
if json_print:
return json_dump(links, pretty_json)
for l in links:
print("%d: %s: <%s> mtu %d status %s" % (
l["ifindex"], l["ifname"], ",".join(l["flags"]), l["mtu"], l["operstate"]
))
print(
" link/" + l["link_type"] +
((" " + l["address"]) if "address" in l else "") +
((" brd " + l["broadcast"]) if "broadcast" in l else "")
)
for a in l.get("addr_info", []):
print(
" %s %s/%d" % (a["family"], a["local"], a["prefixlen"]) +
((" brd " + a["broadcast"]) if "broadcast" in a else "")
)
return True
# Help
def do_help(argv=None, af=None, json_print=None, pretty_json=None):
perror("Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }")
perror("where OBJECT := { link | addr | route | neigh }")
perror(" OPTIONS := { -V[ersion] | -j[son] | -p[retty] |")
perror(" -4 | -6 }")
perror("iproute2mac")
perror("Homepage: https://github.com/brona/iproute2mac")
perror(
"This is CLI wrapper for basic network utilities on Mac OS X"
" inspired with iproute2 on Linux systems."
)
perror(
"Provided functionality is limited and command output is not"
" fully compatible with iproute2."
)
perror(
"For advanced usage use netstat, ifconfig, ndp, arp, route "
" and networksetup directly."
)
exit(255)
def do_help_route():
perror("Usage: ip route list")
perror(" ip route get ADDRESS")
perror(" ip route { add | del | replace } ROUTE")
perror(" ip route flush cache")
perror(" ip route flush table main")
perror("ROUTE := NODE_SPEC [ INFO_SPEC ]")
perror("NODE_SPEC := [ TYPE ] PREFIX")
perror("INFO_SPEC := NH")
perror("TYPE := { blackhole }")
perror("NH := { via ADDRESS | gw ADDRESS | nexthop ADDRESS | dev STRING }")
exit(255)
def do_help_addr():
perror("Usage: ip addr show [ dev STRING ]")
perror(" ip addr { add | del } PREFIX dev STRING")
exit(255)
def do_help_link():
perror("Usage: ip link show [ DEVICE ]")
perror(" ip link set dev DEVICE")
perror(" [ { up | down } ]")
perror(" [ address { LLADDR | factory | random } ]")
perror(" [ mtu MTU ]")
exit(255)
def do_help_neigh():
perror("Usage: ip neighbour show [ [ to ] PREFIX ] [ dev DEV ]")
perror(" ip neighbour flush [ dev DEV ]")
exit(255)
# Route Module
@help_msg("do_help_route")
def do_route(argv, af, json_print, pretty_json):
if not argv or (
any_startswith(["show", "lst", "list"], argv[0]) and len(argv) == 1
):
return do_route_list(af, json_print, pretty_json)
elif "get".startswith(argv[0]) and len(argv) == 2:
argv.pop(0)
return do_route_get(argv, af, json_print, pretty_json)
elif "add".startswith(argv[0]) and len(argv) >= 3:
argv.pop(0)
return do_route_add(argv, af)
elif "delete".startswith(argv[0]) and len(argv) >= 2:
argv.pop(0)
return do_route_del(argv, af)
elif "replace".startswith(argv[0]) and len(argv) >= 3:
argv.pop(0)
return do_route_del(argv, af) and do_route_add(argv, af)
elif "flush".startswith(argv[0]) and len(argv) >= 1:
argv.pop(0)
return do_route_flush(argv, af)
else:
return False
return True
def do_route_list(af, json_print, pretty_json):
# ip route prints IPv6 or IPv4, never both
inet = "inet6" if af == 6 else "inet"
status, res = subprocess.getstatusoutput(
NETSTAT + " -nr -f " + inet + " 2>/dev/null"
)
if status:
perror(res)
return False
res = res.split("\n")
res = res[4:] # Removes first 4 lines
routes = []
for r in res:
ra = r.split()
target = ra[0]
gw = ra[1]
flags = ra[2]
# macOS Mojave and earlier vs Catalina
dev = ra[5] if len(ra) >= 6 else ra[3]
if flags.find("W") != -1:
continue
if af == 6:
target = re.sub(r"%[^ ]+/", "/", target)
else:
target = cidr_from_netstat_dst(target)
if flags.find("B") != -1:
routes.append({"type": "blackhole", "dst": target, "flags": []})
continue
if re.match(r"link.+", gw):
routes.append({"dst": target, "dev": dev, "scope": "link", "flags": []})
else:
routes.append({"dst": target, "gateway": gw, "dev": dev, "flags": []})
if json_print:
return json_dump(routes, pretty_json)
for route in routes:
if "type" in route:
print("%s %s" % (route["type"], route["dst"]))
elif "scope" in route:
print("%s dev %s scope %s" % (route["dst"], route["dev"], route["scope"]))
elif "gateway" in route:
print("%s via %s dev %s" % (route["dst"], route["gateway"], route["dev"]))
return True
def do_route_add(argv, af):
options = ""
if argv[0] == "blackhole":
argv.pop(0)
if len(argv) != 1:
return False
argv.append("via")
argv.append("::1" if ":" in argv[0] or af == 6 else "127.0.0.1")
options = "-blackhole"
if len(argv) not in (3, 5):
return False
if len(argv) == 5:
perror(
"iproute2mac: Ignoring last 2 arguments, not implemented: {} {}".format(
argv[3], argv[4]
)
)
if argv[1] in ["via", "nexthop", "gw"]:
gw = argv[2]
elif argv[1] in ["dev"]:
gw = "-interface " + argv[2]
else:
do_help_route()
prefix = argv[0]
inet = "-inet6 " if ":" in prefix or af == 6 else ""
return execute_cmd(
SUDO + " " + ROUTE + " add " + inet + prefix + " " + gw + " " + options
)
def do_route_del(argv, af):
options = ""
if argv[0] == "blackhole":
argv.pop(0)
if len(argv) != 1:
return False
if ":" in argv[0] or af == 6:
options = " ::1 -blackhole"
else:
options = " 127.0.0.1 -blackhole"
prefix = argv[0]
inet = "-inet6 " if ":" in prefix or af == 6 else ""
return execute_cmd(
SUDO + " " + ROUTE + " delete " + inet + prefix + options
)
def do_route_flush(argv, af):
if not argv:
perror('"ip route flush" requires arguments.')
perror("")
return False
# https://github.com/brona/iproute2mac/issues/38
# http://linux-ip.net/html/tools-ip-route.html
if argv[0] == "cache":
print("iproute2mac: There is no route cache to flush in MacOS,")
print(" returning 0 status code for compatibility.")
return True
elif len(argv) == 2 and argv[0] == "table" and argv[1] == "main":
family = "-inet6" if af == 6 else "-inet"
print("iproute2mac: Flushing all routes")
return execute_cmd(SUDO + " " + ROUTE + " -n flush " + family)
else:
return False
def do_route_get(argv, af, json_print, pretty_json):
target = argv[0]
inet = ""
if ":" in target or af == 6:
inet = "-inet6 "
family = socket.AF_INET6
else:
family = socket.AF_INET
status, res = subprocess.getstatusoutput(
ROUTE + " -n get " + inet + target
)
if status: # unix status or not in table
perror(res)
return False
if res.find("not in table") >= 0:
perror(res)
exit(1)
res = dict(
re.findall(
r"^\W*((?:route to|destination|gateway|interface)): (.+)$",
res,
re.MULTILINE,
)
)
route = {"dst": res["route to"], "dev": res["interface"]}
if "gateway" in res:
route["gateway"] = res["gateway"]
try:
s = socket.socket(family, socket.SOCK_DGRAM)
s.connect((route["dst"], 7))
route["prefsrc"] = src_ip = s.getsockname()[0]
s.close()
except:
pass
route["flags"] = []
route["uid"] = os.getuid()
route["cache"] = []
if json_print:
return json_dump([route], pretty_json)
print(
route["dst"] +
((" via " + route["gateway"]) if "gateway" in route else "") +
" dev " + route["dev"] +
((" src " + route["prefsrc"]) if "prefsrc" in route else "") +
" uid " + str(route["uid"])
)
return True
# Addr Module
@help_msg("do_help_addr")
def do_addr(argv, af, json_print, pretty_json):
if not argv:
argv.append("show")
if any_startswith(["show", "lst", "list"], argv[0]):
argv.pop(0)
return do_addr_show(argv, af, json_print, pretty_json)
elif "add".startswith(argv[0]) and len(argv) >= 3:
argv.pop(0)
return do_addr_add(argv, af)
elif "delete".startswith(argv[0]) and len(argv) >= 3:
argv.pop(0)
return do_addr_del(argv, af)
else:
return False
return True
def do_addr_show(argv, af, json_print, pretty_json):
return link_addr_show(argv, af, json_print, pretty_json, True)
def do_addr_add(argv, af):
if len(argv) < 2:
return False
dst = ""
if argv[1] == "peer":
argv.pop(1)
dst = argv.pop(1)
if argv[1] == "dev":
argv.pop(1)
else:
return False
try:
addr = argv[0]
dev = argv[1]
except IndexError:
perror("dev not found")
exit(1)
inet = ""
if ":" in addr or af == 6:
af = 6
inet = " inet6"
return execute_cmd(
SUDO + " " + IFCONFIG + " " + dev + inet + " add " + addr + " " + dst
)
def do_addr_del(argv, af):
if len(argv) < 2:
return False
if argv[1] == "dev":
argv.pop(1)
try:
addr = argv[0]
dev = argv[1]
except IndexError:
perror("dev not found")
exit(1)
inet = "inet"
if ":" in addr or af == 6:
af = 6
inet = "inet6"
return execute_cmd(
SUDO + " " + IFCONFIG + " " + dev + " " + inet + " " + addr + " remove"
)
# Link module
@help_msg("do_help_link")
def do_link(argv, af, json_print, pretty_json):
if not argv:
argv.append("show")
if any_startswith(["show", "lst", "list"], argv[0]):
argv.pop(0)
return do_link_show(argv, af, json_print, pretty_json)
elif "set".startswith(argv[0]):
argv.pop(0)
return do_link_set(argv, af)
else:
return False
return True
def do_link_show(argv, af, json_print, pretty_json):
return link_addr_show(argv, af, json_print, pretty_json, False)
def do_link_set(argv, af):
if not argv:
return False
elif argv[0] == "dev":
argv.pop(0)
if len(argv) < 2:
return False
dev = argv[0]
IFCONFIG_DEV_CMD = SUDO + " " + IFCONFIG + " " + dev
try:
args = iter(argv)
for arg in args:
if arg == "up":
if not execute_cmd(IFCONFIG_DEV_CMD + " up"):
return False
elif arg == "down":
if not execute_cmd(IFCONFIG_DEV_CMD + " down"):
return False
elif arg in ["address", "addr", "lladdr"]:
addr = next(args)
if addr in ["random", "rand"]:
addr = randomMAC()
elif addr == "factory":
(status, res) = subprocess.getstatusoutput(
NETWORKSETUP + " -listallhardwareports"
)
if status != 0:
return False
details = re.findall(
r"^(?:Device|Ethernet Address): (.+)$",
res,
re.MULTILINE,
)
addr = details[details.index(dev) + 1]
if not execute_cmd(IFCONFIG_DEV_CMD + " lladdr " + addr):
return False
elif arg == "mtu":
mtu = int(next(args))
if not execute_cmd(IFCONFIG_DEV_CMD + " mtu " + str(mtu)):
return False
except Exception:
return False
return True
# Neigh module
@help_msg("do_help_neigh")
def do_neigh(argv, af, json_print, pretty_json):
if not argv:
argv.append("show")
if any_startswith(["show", "list", "lst"], argv[0]) and len(argv) <= 5:
argv.pop(0)
return do_neigh_show(argv, af, json_print, pretty_json)
elif "flush".startswith(argv[0]):
argv.pop(0)
return do_neigh_flush(argv, af)
else:
return False
def do_neigh_show(argv, af, json_print, pretty_json):
prefix = None
dev = None
try:
while argv:
arg = argv.pop(0)
if arg == "to":
prefix = argv.pop(0)
elif arg == "dev":
dev = argv.pop(0)
elif prefix is None:
prefix = arg
else:
return False
if prefix:
prefix = ipaddress.ip_network(prefix, strict=False)
except Exception:
return False
nd_ll_states = {
"R": "REACHABLE",
"S": "STALE",
"D": "DELAY",
"P": "PROBE",
"I": "INCOMPLETE",
"N": "INCOMPLETE",
"W": "INCOMPLETE",
}
neighs = []
if af != 4:
res = subprocess.run(
[NDP, "-an"], capture_output=True, text=True, check=True
)
for row in res.stdout.splitlines()[1:]:
cols = row.split()
entry = {"dst": re.sub(r"%.+$", "", cols[0])}
if cols[1] != "(incomplete)":
entry["lladdr"] = cols[1]
entry["dev"] = cols[2]
if dev and entry["dev"] != dev:
continue
if prefix and ipaddress.ip_address(entry["dst"]) not in prefix:
continue
if cols[1] == "(incomplete)" and cols[4] != "R":
entry["status"] = ["INCOMPLETE"]
else:
entry["status"] = [nd_ll_states[cols[4]]]
entry["router"] = len(cols) >= 6 and cols[5] == "R"
neighs.append(entry)
if af != 6:
args = [ARP, "-anl"]
if dev:
args += ["-i", dev]
res = subprocess.run(args, capture_output=True, text=True, check=True)
for row in res.stdout.splitlines()[1:]:
cols = row.split()
entry = {"dst": cols[0]}
if cols[1] != "(incomplete)":
entry["lladdr"] = cols[1]
entry["dev"] = cols[4]
if dev and entry["dev"] != dev:
continue
if prefix and ipaddress.ip_address(entry["dst"]) not in prefix:
continue
if cols[1] == "(incomplete)":
entry["status"] = ["INCOMPLETE"]
else:
entry["status"] = ["REACHABLE"]
entry["router"] = False
neighs.append(entry)
if json_print:
return json_dump(neighs, pretty_json)
for nb in neighs:
print(
nb["dst"] +
("", " dev " + nb["dev"], "")[dev == None] +
("", " router")[nb["router"]] +
" %s" % (nb["status"][0])
)
return True
def do_neigh_flush(argv, af):
if len(argv) != 2:
perror("Flush requires arguments.")
exit(1)
if argv[0] != "dev":
return False
dev = argv[1]
if af != 4:
print(
"iproute2mac: NDP doesn't support filtering by interface,"
"flushing all IPv6 entries."
)
execute_cmd(SUDO + " " + NDP + " -cn")
if af != 6:
execute_cmd(SUDO + " " + ARP + " -a -d -i " + dev)
return True
# Match iproute2 commands
# https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/ip.c#n86
cmds = [
("address", do_addr),
("route", do_route),
("neighbor", do_neigh),
("neighbour", do_neigh),
("link", do_link),
("help", do_help),
]
@help_msg("do_help")
def main(argv):
af = -1 # default / both
json_print = False
pretty_json = False
while argv and argv[0].startswith("-"):
# Turn --opt into -opt
argv[0] = argv[0][1:] if argv[0][1] == "-" else argv[0]
# Process options
if argv[0] == "-6":
af = 6
argv.pop(0)
elif argv[0] == "-4":
af = 4
argv.pop(0)
elif argv[0].startswith("-color"):
perror("iproute2mac: Color option is not implemented")
argv.pop(0)
elif "-json".startswith(argv[0]):
json_print = True
argv.pop(0)
elif "-pretty".startswith(argv[0]):
pretty_json = True
argv.pop(0)
elif "-Version".startswith(argv[0]):
print("iproute2mac, v" + VERSION)
exit(0)
elif "-help".startswith(argv[0]):
return False
else:
perror('Option "{}" is unknown, try "ip help".'.format(argv[0]))
exit(255)
if not argv:
return False
for cmd, cmd_func in cmds:
if cmd.startswith(argv[0]):
argv.pop(0)
# Functions return true or terminate with exit(255)
# See help_msg and do_help*
return cmd_func(argv, af, json_print, pretty_json)
perror('Object "{}" is unknown, try "ip help".'.format(argv[0]))
exit(1)
if __name__ == "__main__":
main(sys.argv[1:])

View File

@ -3,4 +3,4 @@
FOLDER=/Applications/OGAgent.app FOLDER=/Applications/OGAgent.app
cd $FOLDER cd $FOLDER
python -m opengnsys.linux.OGAgentService $@ /usr/local/bin/python3 -m opengnsys.linux.OGAgentService $@

View File

@ -1,17 +1,11 @@
#!/usr/bin/env sh #!/usr/bin/env sh
# Directories
SRCDIR=$(dirname "$0") SRCDIR=$(dirname "$0")
BINDIR=/usr/bin BINDIR=/usr/local/bin
INITDIR=/Library/LaunchDaemons LAUNCH_AGENTS_DIR=/Library/LaunchAgents
LAUNCH_DAEMONS_DIR=/Library/LaunchDaemons
# Check if it needs to install Python dependencies:
if ! which pip &>/dev/null; then
easy_install pip
pip install netifaces requests six
fi
# Copying files.
cp $SRCDIR/ogagent $BINDIR cp $SRCDIR/ogagent $BINDIR
cp $SRCDIR/es.opengnsys.ogagent.plist $INITDIR cp $SRCDIR/ip.py $BINDIR/ip ## override 'ip' from iproute2mac-1.4.2 with a more recent one
cp $SRCDIR/es.opengnsys.agent.system.plist $LAUNCH_DAEMONS_DIR
cp $SRCDIR/es.opengnsys.agent.user.plist $LAUNCH_AGENTS_DIR

97
src/OGAgent.spec 100755
View File

@ -0,0 +1,97 @@
# -*- mode: python ; coding: utf-8 -*-
ogausr_a = Analysis(
['OGAgentUser.py'],
pathex=[],
binaries=[],
datas=[
# ('cfg', 'cfg'), ## add the entire directory
('img', 'img'), ## add the entire directory
],
hiddenimports=['win32timezone', 'socketserver', 'http.server', 'urllib'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
ogasvc_a = Analysis(
['opengnsys\\windows\\OGAgentService.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=['win32timezone', 'socketserver', 'http.server', 'urllib'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
MERGE(
(ogausr_a, 'OGAgentUser', 'OGAgentUser'), ## class, py name, exe name
(ogasvc_a, 'OGAgentService', 'OGAgentService')
)
ogausr_pyz = PYZ(ogausr_a.pure)
ogasvc_pyz = PYZ(ogasvc_a.pure)
ogausr_exe = EXE(
ogausr_pyz,
ogausr_a.scripts,
[],
exclude_binaries=True,
name='OGAgentUser',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['img\\oga.ico'],
)
ogasvc_exe = EXE(
ogasvc_pyz,
ogasvc_a.scripts,
[],
exclude_binaries=True,
name='OGAgentService',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['img\\oga.ico'],
manifest='OGAgent.manifest',
)
dist_name = 'OGAgent'
coll = COLLECT(
ogausr_exe,
ogausr_a.binaries,
ogausr_a.datas,
ogasvc_exe,
ogasvc_a.binaries,
ogasvc_a.datas,
strip=False,
upx=True,
upx_exclude=[],
name=dist_name,
)
import shutil
shutil.copytree ('cfg', '{}/{}/cfg'.format(DISTPATH, dist_name))

4
src/OGAgentUser.py 100644 → 100755
View File

@ -34,6 +34,7 @@ import base64
import json import json
import sys import sys
import time import time
import os
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
from about_dialog_ui import Ui_OGAAboutDialog from about_dialog_ui import Ui_OGAAboutDialog
@ -159,7 +160,8 @@ class OGASystemTray(QtWidgets.QSystemTrayIcon):
self.ipcport = int(cfg.get('ipc_port', IPC_PORT)) self.ipcport = int(cfg.get('ipc_port', IPC_PORT))
icon = QtGui.QIcon(':/images/img/oga.png') QtCore.QDir.addSearchPath('images', os.path.join(os.path.dirname(__file__), 'img'))
icon = QtGui.QIcon('images:oga.png')
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent) QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
self.menu = QtWidgets.QMenu(parent) self.menu = QtWidgets.QMenu(parent)

View File

@ -1 +1 @@
1.3.0 1.3.3

View File

@ -32,7 +32,7 @@
# pylint: disable=unused-wildcard-import, wildcard-import # pylint: disable=unused-wildcard-import, wildcard-import
from configparser import SafeConfigParser from configparser import ConfigParser
config = None config = None
@ -45,7 +45,7 @@ def readConfig(client=False):
This is this way so we can protect ogagent.cfg against reading for non admin users on all platforms. This is this way so we can protect ogagent.cfg against reading for non admin users on all platforms.
""" """
cfg = SafeConfigParser() cfg = ConfigParser()
if client is True: if client is True:
fname = 'ogclient.cfg' fname = 'ogclient.cfg'
else: else:

View File

@ -57,7 +57,8 @@ class HTTPServerHandler(BaseHTTPRequestHandler):
return return
def sendJsonResponse(self, data): def sendJsonResponse(self, data):
self.send_response(200) try: self.send_response(200)
except Exception as e: logger.warn (str(e))
data = json.dumps(data) data = json.dumps(data)
self.send_header('Content-type', 'application/json') self.send_header('Content-type', 'application/json')
self.send_header('Content-Length', str(len(data))) self.send_header('Content-Length', str(len(data)))
@ -117,7 +118,7 @@ class HTTPServerHandler(BaseHTTPRequestHandler):
logger.error('HTTP ' + fmt % args) logger.error('HTTP ' + fmt % args)
def log_message(self, fmt, *args): def log_message(self, fmt, *args):
logger.info('HTTP ' + fmt % args) logger.debug('HTTP ' + fmt % args)
class HTTPThreadingServer(ThreadingMixIn, HTTPServer): class HTTPThreadingServer(ThreadingMixIn, HTTPServer):
@ -132,7 +133,9 @@ class HTTPServerThread(threading.Thread):
self.certFile = createSelfSignedCert() self.certFile = createSelfSignedCert()
self.server = HTTPThreadingServer(address, HTTPServerHandler) self.server = HTTPThreadingServer(address, HTTPServerHandler)
self.server.socket = ssl.wrap_socket(self.server.socket, certfile=self.certFile, server_side=True) context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certfile=self.certFile)
self.server.socket = context.wrap_socket(self.server.socket, server_side=True)
logger.debug('Initialized HTTPS Server thread on {}'.format(address)) logger.debug('Initialized HTTPS Server thread on {}'.format(address))

View File

@ -42,45 +42,69 @@ import subprocess
import struct import struct
import array import array
import six import six
import json
from opengnsys import utils from opengnsys import utils
import netifaces
ip_a_s = None
## make sure /usr/local/bin is in PATH
## we need that to run /usr/local/bin/ip, which uses 'env' and pulls from PATH anyway
def check_path():
path = os.getenv ('PATH', '')
usr_local_bin = '/usr/local/bin'
if usr_local_bin not in path.split (os.pathsep):
os.environ['PATH'] = usr_local_bin + os.pathsep + path
def _getMacAddr(ifname): def _getMacAddr(ifname):
''' '''
Returns the mac address of an interface Returns the mac address of an interface
Mac is returned as unicode utf-8 encoded Mac is returned as unicode utf-8 encoded
''' '''
if isinstance(ifname, list): for interface in ip_a_s:
return dict([(name, _getMacAddr(name)) for name in ifname]) if interface.get ('ifname') != ifname: continue
if isinstance(ifname, six.text_type): return interface.get ('address')
ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7) return None
try:
return netifaces.ifaddresses(ifname)[18][0]['addr']
except Exception:
return None
def _getIpAddr(ifname): def _getIpAddr(ifname):
''' '''
Returns the IP address of an interface Returns the first IP address of an interface
IPv4 is preferred over IPv6
IP is returned as unicode utf-8 encoded IP is returned as unicode utf-8 encoded
''' '''
if isinstance(ifname, list):
return dict([(name, _getIpAddr(name)) for name in ifname]) ## loop and return the first IPv4 address found
if isinstance(ifname, six.text_type): for interface in ip_a_s:
ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7) if interface.get ('ifname') != ifname: continue
try: for addr_info in interface.get ('addr_info', []):
return netifaces.ifaddresses(ifname)[2][0]['addr'] ip_address = addr_info.get ('local')
except Exception: try:
return None ip_address.index ('.')
return ip_address
except: pass
## if nothing found, loop again and return the first IP found, which will be an IPv6
for interface in ip_a_s:
if interface.get ('ifname') != ifname: continue
for addr_info in interface.get ('addr_info', []):
return addr_info.get ('local')
return None
def _getInterfaces(): def _getInterfaces():
''' '''
Returns a list of interfaces names Returns a list of interfaces names
''' '''
return netifaces.interfaces() global ip_a_s
check_path()
result = subprocess.run (['/usr/local/bin/ip', '-json', 'address', 'show'], capture_output=True, text=True)
if result.returncode != 0: raise Exception (f'Command "ip" failed with exit code {result.returncode}')
ip_a_s = json.loads (result.stdout)
return [i.get('ifname') for i in ip_a_s]
def _getIpAndMac(ifname): def _getIpAndMac(ifname):
@ -152,7 +176,7 @@ def logoff():
import threading import threading
threading._DummyThread._Thread__stop = lambda x: 42 threading._DummyThread._Thread__stop = lambda x: 42
# Exec logout using AppleSctipt # Exec logout using AppleScript
subprocess.call('/usr/bin/osascript -e \'tell app "System Events" to «event aevtrlgo»\'', shell=True) subprocess.call('/usr/bin/osascript -e \'tell app "System Events" to «event aevtrlgo»\'', shell=True)
@ -244,7 +268,19 @@ def getSessionLanguage():
''' '''
Returns the user's session language Returns the user's session language
''' '''
return locale.getdefaultlocale()[0] lang = locale.getdefaultlocale()[0]
if lang is None:
return 'C'
else:
return lang
def get_session_type():
"""
Minimal implementation of this required function
:return: string
"""
return 'unknown'
def showPopup(title, message): def showPopup(title, message):

View File

@ -65,7 +65,7 @@ def check_secret(fnc):
else: else:
raise Exception('Unauthorized operation') raise Exception('Unauthorized operation')
except Exception as e: except Exception as e:
logger.error(e) logger.error(str(e))
raise Exception(e) raise Exception(e)
return wrapper return wrapper
@ -83,7 +83,7 @@ def execution_level(level):
else: else:
raise Exception('Unauthorized operation') raise Exception('Unauthorized operation')
except Exception as e: except Exception as e:
logger.error(e) logger.error(str(e))
raise Exception(e) raise Exception(e)
return wrapper return wrapper
@ -149,6 +149,7 @@ class OpenGnSysWorker(ServerWorker):
break break
# Raise error after timeout # Raise error after timeout
if not self.interface: if not self.interface:
## UnboundLocalError: cannot access local variable 'e' where it is not associated with a value
raise e raise e
# Loop to send initialization message # Loop to send initialization message
@ -332,11 +333,23 @@ class OpenGnSysWorker(ServerWorker):
:return: JSON object {"op": "launched"} :return: JSON object {"op": "launched"}
""" """
logger.debug('Processing script request') logger.debug('Processing script request')
# Decoding script (Windows scripts need a subprocess call per line) # Decoding script
script = urllib.parse.unquote(base64.b64decode(post_params.get('script')).decode('utf-8')) script = urllib.parse.unquote(base64.b64decode(post_params.get('script')).decode('utf-8'))
logger.debug('received script {}'.format(script))
if operations.os_type == 'Windows': if operations.os_type == 'Windows':
script = 'import subprocess; {0}'.format( ## for windows, we turn the script into utf16le, then to b64 again, and feed the blob to powershell
';'.join(['subprocess.check_output({0},shell=True)'.format(repr(c)) for c in script.split('\n')])) u16 = script.encode ('utf-16le') ## utf16
b64 = base64.b64encode (u16).decode ('utf-8') ## b64 (which returns bytes, so we need an additional decode(utf8))
script = """
import os
import tempfile
import subprocess
cp = subprocess.run ("powershell -WindowStyle Hidden -EncodedCommand {}", capture_output=True)
subprocs_log = os.path.join (tempfile.gettempdir(), 'opengnsys-subprocs.log')
with open (subprocs_log, 'ab') as fd: ## TODO improve this logging
fd.write (cp.stdout)
fd.write (cp.stderr)
""".format (b64)
else: else:
script = 'import subprocess; subprocess.check_output("""{0}""",shell=True)'.format(script) script = 'import subprocess; subprocess.check_output("""{0}""",shell=True)'.format(script)
# Executing script. # Executing script.

View File

@ -61,7 +61,7 @@ def exceptionToMessage(e):
if isinstance(arg, Exception): if isinstance(arg, Exception):
msg = msg + exceptionToMessage(arg) msg = msg + exceptionToMessage(arg)
else: else:
msg = msg + toUnicode(arg) + '. ' msg = msg + str(arg) + '. '
return msg return msg

View File

@ -120,5 +120,9 @@ class OGAgentSvc(win32serviceutil.ServiceFramework, CommonService):
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) == 1:
win32serviceutil.HandleCommandLine(OGAgentSvc) servicemanager.Initialize()
servicemanager.PrepareToHostSingle(OGAgentSvc)
servicemanager.StartServiceCtrlDispatcher()
else:
win32serviceutil.HandleCommandLine(OGAgentSvc)

View File

@ -43,11 +43,11 @@ OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in range(6))
class LocalLogger(object): class LocalLogger(object):
def __init__(self): def __init__(self):
# tempdir is different for "user application" and "service" # tempdir is different for "user application" and "service"
# service wil get c:\windows\temp, while user will get c:\users\XXX\temp # service wil get c:\windows\temp, while user will get c:\users\XXX\appdata\local\temp
logging.basicConfig( logging.basicConfig(
filename=os.path.join(tempfile.gettempdir(), 'opengnsys.log'), filename=os.path.join(tempfile.gettempdir(), 'opengnsys.log'),
filemode='a', filemode='a',
format='%(levelname)s %(asctime)s %(message)s', format='%(levelname)s %(asctime)s (%(threadName)s) %(message)s',
level=logging.DEBUG level=logging.DEBUG
) )
self.logger = logging.getLogger('opengnsys') self.logger = logging.getLogger('opengnsys')
@ -58,7 +58,7 @@ class LocalLogger(object):
# our loglevels are 10000 (other), 20000 (debug), .... # our loglevels are 10000 (other), 20000 (debug), ....
# logging levels are 10 (debug), 20 (info) # logging levels are 10 (debug), 20 (info)
# OTHER = logging.NOTSET # OTHER = logging.NOTSET
self.logger.log(level / 1000 - 10, message) self.logger.log(int(level / 1000 - 10), message)
if level < INFO or self.serviceLogger is False: # Only information and above will be on event log if level < INFO or self.serviceLogger is False: # Only information and above will be on event log
return return

View File

@ -64,7 +64,7 @@ def getNetworkInfo():
ip: ip of the interface ip: ip of the interface
''' '''
obj = win32com.client.Dispatch("WbemScripting.SWbemLocator") obj = win32com.client.Dispatch("WbemScripting.SWbemLocator")
wmobj = obj.ConnectServer("localhost", "root\cimv2") wmobj = obj.ConnectServer("localhost", "root\\cimv2")
adapters = wmobj.ExecQuery("Select * from Win32_NetworkAdapterConfiguration where IpEnabled=True") adapters = wmobj.ExecQuery("Select * from Win32_NetworkAdapterConfiguration where IpEnabled=True")
try: try:
for obj in adapters: for obj in adapters:
@ -102,12 +102,12 @@ def getWindowsVersion():
''' '''
Returns Windows version. Returns Windows version.
''' '''
import _winreg import winreg
reg = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion') reg = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion')
try: try:
data = '{} {}'.format(_winreg.QueryValueEx(reg, 'ProductName')[0], _winreg.QueryValueEx(reg, 'ReleaseId')[0]) data = '{} {}'.format(winreg.QueryValueEx(reg, 'ProductName')[0], winreg.QueryValueEx(reg, 'ReleaseId')[0])
except Exception: except Exception:
data = '{} {}'.format(_winreg.QueryValueEx(reg, 'ProductName')[0], _winreg.QueryValueEx(reg, 'CurrentBuildNumber')[0]) data = '{} {}'.format(winreg.QueryValueEx(reg, 'ProductName')[0], winreg.QueryValueEx(reg, 'CurrentBuildNumber')[0])
reg.Close() reg.Close()
return data return data

View File

@ -1,3 +1,6 @@
netifaces pywin32
pyqt6
requests requests
urllib3 six
pycryptodome
pyinstaller

View File

@ -1,139 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
@author: Ramón M. Gómez, ramongomez at us dot es
"""
# ModuleFinder can't handle runtime changes to __path__, but win32com uses them
try:
# py2exe 0.6.4 introduced a replacement modulefinder.
# This means we have to add package paths there, not to the built-in
# one. If this new modulefinder gets integrated into Python, then
# we might be able to revert this some day.
# if this doesn't work, try import modulefinder
try:
import py2exe.mf as modulefinder
except ImportError:
import modulefinder
import win32com
import sys
for p in win32com.__path__[1:]:
modulefinder.AddPackagePath("win32com", p)
for extra in ["win32com.shell"]: # ,"win32com.mapi"
__import__(extra)
m = sys.modules[extra]
for p in m.__path__[1:]:
modulefinder.AddPackagePath(extra, p)
except ImportError:
# no build path setup, no worries.
pass
import os
from distutils.core import setup
import py2exe
import sys
# update.sh changes this value
VERSION='1.3.0'
sys.argv.append('py2exe')
def get_requests_cert_file():
"""Add Python requests or certifi .pem file for installers."""
import requests
f = os.path.join(os.path.dirname(requests.__file__), 'cacert.pem')
if not os.path.exists(f):
import certifi
f = os.path.join(os.path.dirname(certifi.__file__), 'cacert.pem')
return f
class Target:
def __init__(self, **kw):
self.__dict__.update(kw)
# for the versioninfo resources
self.version = VERSION
self.name = 'OGAgentService'
self.description = 'OpenGnsys Agent Service'
self.author = 'Adolfo Gomez'
self.url = 'https://opengnsys.es/'
self.company_name = "OpenGnsys Project"
self.copyright = "(c) 2014 VirtualCable S.L.U."
self.name = "OpenGnsys Agent"
# Now you need to pass arguments to setup
# windows is a list of scripts that have their own UI and
# thus don't need to run in a console.
ogaservice = Target(
description='OpenGnsys Agent Service',
modules=['opengnsys.windows.OGAgentService'],
icon_resources=[(0, 'img\\oga.ico'), (1, 'img\\oga.ico')],
cmdline_style='pywin32'
)
# Some test_modules are hidden to py2exe by six, we ensure that they appear on "includes"
HIDDEN_BY_SIX = ['SocketServer', 'SimpleHTTPServer', 'urllib']
setup(
windows=[
{
'script': 'OGAgentUser-qt4.py',
'icon_resources': [(0, 'img\\oga.ico'), (1, 'img\\oga.ico')]
},
],
console=[
{
'script': 'OGAServiceHelper.py'
}
],
service=[ogaservice],
data_files=[('', [get_requests_cert_file()]), ('cfg', ['cfg/ogagent.cfg', 'cfg/ogclient.cfg'])],
options={
'py2exe': {
'bundle_files': 3,
'compressed': True,
'optimize': 2,
'includes': ['sip', 'PyQt4', 'win32com.shell', 'requests', 'encodings', 'encodings.utf_8'] + HIDDEN_BY_SIX,
'excludes': ['doctest', 'unittest'],
'dll_excludes': ['msvcp90.dll'],
'dist_dir': '..\\bin',
}
},
name='OpenGnsys Agent',
version=VERSION,
description='OpenGnsys Agent',
author='Adolfo Gomez',
author_email='agomez@virtualcable.es',
zipfile='OGAgent.zip',
)

40
src/update.py 100755
View File

@ -0,0 +1,40 @@
#!/usr/bin/python3
import os
import re
import sys
import subprocess
import fileinput
def update_version():
if os.path.isfile ('VERSION'):
with open ('VERSION', 'r') as version_file:
version = version_file.read().strip()
pattern = r'[0-9]*\.[0-9]*\.[0-9]*'
matches = re.findall (pattern, version)
win_version = matches[0]
with fileinput.FileInput ('about-dialog.ui', inplace=True) as file:
for line in file:
print (line.replace ('Version [^<]*', f'Version {version}'), end='')
with fileinput.FileInput ('opengnsys/__init__.py', inplace=True) as file:
for line in file:
print(line.replace ('VERSION=.*', f"VERSION='{version}'"), end='')
with open ('../windows/VERSION', 'w') as outfile:
outfile.write (win_version + '\n')
else:
print ('VERSION: No such file or directory')
sys.exit (1)
def process_ui():
subprocess.run (['pyuic6', 'about-dialog.ui', '-o', 'about_dialog_ui.py', '-x'])
subprocess.run (['pyuic6', 'message-dialog.ui', '-o', 'message_dialog_ui.py', '-x'])
if __name__ == "__main__":
os.chdir (os.path.dirname (os.path.abspath (__file__)))
update_version()
process_ui()

View File

@ -1,59 +0,0 @@
#!/bin/bash
#
# Copyright (c) 2014 Virtual Cable S.L.
# Copyright (c) 2024 Qindel Formación y Servicios S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
function update_version {
if [[ -r VERSION ]]; then
V="$(cat VERSION)"
sed -i "s/Version [^<]*/Version $V/" about-dialog.ui
sed -i "s/^VERSION='.*'$/VERSION='$V'/" setup.py
sed -i "s/^VERSION='.*'$/VERSION='$V'/" opengnsys/__init__.py
else
echo 'src/VERSION: No such file or directory'
exit 1
fi
}
function process_ui {
pyuic6 about-dialog.ui -o about_dialog_ui.py -x
pyuic6 message-dialog.ui -o message_dialog_ui.py -x
}
function process_resources {
## requires a virtualenv with pyside6
## you can create it by doing 'mkvirtualenv -p python3 ogpyside6'
## this will obviously go away in the future, but we need to merge the py3/qt6 change now
~/.virtualenvs/ogpyside6/bin/pyside6-rcc -o OGAgent_rc.py OGAgent.qrc
sed -i -e '/^from PySide6 import QtCore/s/PySide6/PyQt6/' OGAgent_rc.py
}
cd $(dirname "$0")
update_version
process_ui
process_resources

View File

@ -1,7 +0,0 @@
#!/bin/bash
cd "$(dirname "$0")"
export WINEARCH=win32
export WINEPREFIX=$PWD/wine
grep -o "[0-9]*\.[0-9]*\.[0-9]*" ../src/VERSION > VERSION
wine cmd /c c:\\ogagent\\build.bat
chmod -x ../OGAgentSetup*.exe

View File

@ -1,7 +0,0 @@
C:
CD \ogagent\src
python setup.py
CD ..
RENAME bin\OGAgentUser-qt4.exe OGAgentUser.exe
"C:\Program Files\NSIS\makensis.exe" ogagent.nsi

View File

@ -88,8 +88,8 @@ Section -Main SEC0000
SetShellVarContext all SetShellVarContext all
SetOutPath $INSTDIR SetOutPath $INSTDIR
SetOverwrite on SetOverwrite on
File /r bin\*.* File /r src\dist\OGAgent\*.*
File vcredist_x86.exe File windows\VC_redist.x64.exe
File src\VERSION File src\VERSION
WriteRegStr HKLM "${REGKEY}\Components" Main 1 WriteRegStr HKLM "${REGKEY}\Components" Main 1
SectionEnd SectionEnd
@ -111,7 +111,7 @@ Section -post SEC0001
WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" OGAgentTool $INSTDIR\OGAgentUser.exe WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" OGAgentTool $INSTDIR\OGAgentUser.exe
WriteRegDWORD HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" NoModify 1 WriteRegDWORD HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" NoModify 1
WriteRegDWORD HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" NoRepair 1 WriteRegDWORD HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" NoRepair 1
ExecWait '"$INSTDIR\vcredist_x86.exe" /passive /norestart' ExecWait '"$INSTDIR\VC_redist.x64.exe" /passive /norestart'
# Add the application to the firewall exception list - All Networks - All IP Version - Enabled # Add the application to the firewall exception list - All Networks - All IP Version - Enabled
# SimpleFC::AddApplication "OpenGnsys Agent Service" "$INSTDIR\OGAgentService.exe" 0 2 "" 1 # SimpleFC::AddApplication "OpenGnsys Agent Service" "$INSTDIR\OGAgentService.exe" 0 2 "" 1
# SimpleFC::AdvAddRule [name] [description] [protocol] [direction] # SimpleFC::AdvAddRule [name] [description] [protocol] [direction]
@ -128,8 +128,6 @@ Section -post SEC0001
WriteRegDWORD HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Power" HiberbootEnabled 0 WriteRegDWORD HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Power" HiberbootEnabled 0
# Install service # Install service
nsExec::Exec /OEM "$INSTDIR\OGAgentService.exe --startup auto install" # Add service after installation nsExec::Exec /OEM "$INSTDIR\OGAgentService.exe --startup auto install" # Add service after installation
# Update recovery options
nsExec::Exec /OEM "$INSTDIR\OGAServiceHelper.exe"
Exec "net start ogagent" Exec "net start ogagent"
Exec "$INSTDIR\OGAgentUser.exe" Exec "$INSTDIR\OGAgentUser.exe"
SectionEnd SectionEnd

View File

@ -1,67 +0,0 @@
#!/bin/sh
# We need:
# * Wine (32 bit)
# * winetricks (in some distributions)
export WINEARCH=win32 WINEPREFIX=$PWD/wine WINEDEBUG=fixme-all
WINE=wine
download() {
mkdir downloads
# Get needed software
cd downloads
wget -nd https://www.python.org/ftp/python/2.7.17/python-2.7.17.msi -O python-2.7.msi
wget -nd https://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/VCForPython27.msi
wget -nd https://bootstrap.pypa.io/get-pip.py
wget -nd https://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.4/PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x32.exe/download -O pyqt-install.exe
wget -nd https://prdownloads.sourceforge.net/nsis/nsis-3.05-setup.exe?download -O nsis-install.exe
wget -nd http://nsis.sourceforge.net/mediawiki/images/d/d7/NSIS_Simple_Firewall_Plugin_1.20.zip
cd ..
}
install_python() {
if which winetricks &>/dev/null; then
echo "Setting up wine prefix (using winetricks)"
winetricks
fi
cd downloads
echo "Installing python"
$WINE msiexec /qn /i python-2.7.msi
echo "Installing vc for python"
$WINE msiexec /qn /i VCForPython27.msi
echo "Installing pyqt (needs X)"
$WINE pyqt-install.exe
echo "Installing nsis (needs X?)"
$WINE nsis-install.exe
cd ..
}
setup_pip() {
echo "Seting up pip..."
$WINE C:\\Python27\\python -m pip install --upgrade pip
}
install_packages() {
echo "Installing pywin32"
$WINE C:\\Python27\\python -m pip install pywin32
echo "Installing py2exe"
$WINE C:\\Python27\\python -m pip install py2exe_py2
echo "Installing required packages"
$WINE C:\\Python27\\python -m pip install requests six
# Using easy_install instead of pip to install pycrypto
$WINE C:\\Python27\\Scripts\\easy_install http://www.voidspace.org.uk/python/pycrypto-2.6.1/pycrypto-2.6.1.win32-py2.7.exe
# Copy nsis required NSIS_Simple_Firewall_Plugin_1
echo "Copying simple firewall plugin for nsis installer"
unzip -o downloads/NSIS_Simple_Firewall_Plugin_1.20.zip SimpleFC.dll -d $WINEPREFIX/drive_c/Program\ Files/NSIS/Plugins/x86-ansi/
unzip -o downloads/NSIS_Simple_Firewall_Plugin_1.20.zip SimpleFC.dll -d $WINEPREFIX/drive_c/Program\ Files/NSIS/Plugins/x86-unicode/
}
download
install_python
setup_pip
install_packages

43
windows/setup.bat 100644
View File

@ -0,0 +1,43 @@
C:
cd \Users\Docker\Downloads
mkdir setup
rem creamos directorio setup, nos bajamos cosas y mas tarde lo borramos
rem pero el VC_redist hace falta para el empaquetado, asi que lo descargamos fuera de setup para no borrarlo
curl https://www.python.org/ftp/python/3.12.3/amd64/core.msi --output setup\core.msi
curl https://www.python.org/ftp/python/3.12.3/amd64/dev.msi --output setup\dev.msi
curl https://www.python.org/ftp/python/3.12.3/amd64/exe.msi --output setup\exe.msi
curl https://www.python.org/ftp/python/3.12.3/amd64/lib.msi --output setup\lib.msi
curl https://www.python.org/ftp/python/3.12.3/amd64/path.msi --output setup\path.msi
curl https://www.python.org/ftp/python/3.12.3/amd64/pip.msi --output setup\pip.msi
curl https://aka.ms/vs/17/release/vc_redist.x64.exe --location --output VC_redist.x64.exe
curl https://prdownloads.sourceforge.net/nsis/nsis-3.05-setup.exe?download --location --output setup\nsis-install.exe
curl http://nsis.sourceforge.net/mediawiki/images/d/d7/NSIS_Simple_Firewall_Plugin_1.20.zip --location --output setup\NSIS_Simple_Firewall_Plugin_1.20.zip
cd setup
msiexec /i core.msi TARGETDIR=C:\Python312
msiexec /i dev.msi TARGETDIR=C:\Python312
msiexec /i exe.msi TARGETDIR=C:\Python312
msiexec /i lib.msi TARGETDIR=C:\Python312
msiexec /i path.msi TARGETDIR=C:\Python312
msiexec /i pip.msi TARGETDIR=C:\Python312
cd ..
VC_redist.x64.exe /install /quiet /passive /norestart
ftype PythonScript=C:\Python312\python.exe "%1" %*
assoc .py=PythonScript
PATH=C:\Python312;C:\Python312\Scripts;%PATH%
setx PATH "C:\Python312;C:\Python312\Scripts;%PATH%"
python -m pip install --upgrade wheel pip
pip install -r F:\src\requirements.txt
setup\nsis-install.exe /S
powershell -command "Expand-Archive setup\NSIS_Simple_Firewall_Plugin_1.20.zip nsis-fp"
copy nsis-fp\SimpleFC.dll "C:\Program Files (x86)\NSIS\Plugins\x86-ansi\"
copy nsis-fp\SimpleFC.dll "C:\Program Files (x86)\NSIS\Plugins\x86-unicode\"
rmdir nsis-fp /s /q
rmdir setup /s /q