Compare commits
No commits in common. "3a113c9bafc1782aba44149e43d2460f9ce12c29" and "c2ef36c5a7a31ea39f51c91c7ba76430f0aaa048" have entirely different histories.
3a113c9baf
...
c2ef36c5a7
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,2 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
dist/
|
dist/
|
||||||
.venv
|
|
||||||
|
@ -333,8 +333,6 @@ cdr_finalize=UPDATE users SET inuse=(CASE WHEN inuse>0 THEN inuse-1 ELSE 0 END)
|
|||||||
Just choose one of your RFPs as your OMM.
|
Just choose one of your RFPs as your OMM.
|
||||||
Find out the MAC address and fill it in the according fields in the kea config before.
|
Find out the MAC address and fill it in the according fields in the kea config before.
|
||||||
|
|
||||||
To be able to have secure sip connections between the OMM and yate, generate a 16-characters long hexadecimal sip-secret.
|
|
||||||
|
|
||||||
Create a privileged user account and add the credentials to `fieldpoc_config.json`:
|
Create a privileged user account and add the credentials to `fieldpoc_config.json`:
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -343,7 +341,6 @@ Create a privileged user account and add the credentials to `fieldpoc_config.jso
|
|||||||
"host": "10.222.222.11",
|
"host": "10.222.222.11",
|
||||||
"username": "omm",
|
"username": "omm",
|
||||||
"password": "<password>"
|
"password": "<password>"
|
||||||
"sipsecret": "<secret>"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -42,7 +42,7 @@ class Config:
|
|||||||
return self.dect.check()
|
return self.dect.check()
|
||||||
|
|
||||||
|
|
||||||
class ExtensionConfig(ConfigBase):
|
class ExtensionConfig:
|
||||||
def __init__(self, c):
|
def __init__(self, c):
|
||||||
self.num = c[0]
|
self.num = c[0]
|
||||||
self._c = c[1]
|
self._c = c[1]
|
||||||
|
@ -41,16 +41,12 @@ class Controller:
|
|||||||
|
|
||||||
if data == "help":
|
if data == "help":
|
||||||
self.request.sendall("""Availiable commands:
|
self.request.sendall("""Availiable commands:
|
||||||
help Show this info
|
help Show this info
|
||||||
handlers Show currently running handlers
|
handlers Show currently running handlers
|
||||||
sync Start syncing
|
sync Start syncing
|
||||||
queues Show queue stats
|
queues Show queue stats
|
||||||
reload Reload extension config file
|
exit Disconnect
|
||||||
claim <ext> <token> claim dect extension
|
|
||||||
exit Disconnect
|
|
||||||
""".encode("utf-8"))
|
""".encode("utf-8"))
|
||||||
elif data == "":
|
|
||||||
continue
|
|
||||||
elif data == "quit" or data == "exit":
|
elif data == "quit" or data == "exit":
|
||||||
self.request.sendall("disconnecting\n".encode("utf-8"))
|
self.request.sendall("disconnecting\n".encode("utf-8"))
|
||||||
return
|
return
|
||||||
@ -60,14 +56,6 @@ exit Disconnect
|
|||||||
self.fp.queue_all({"type": "sync"})
|
self.fp.queue_all({"type": "sync"})
|
||||||
elif data == "queues":
|
elif data == "queues":
|
||||||
self.request.sendall(("\n".join(["{} {}".format(name, queue.qsize()) for name, queue in self.fp.queues.items()]) + "\n").encode("utf-8"))
|
self.request.sendall(("\n".join(["{} {}".format(name, queue.qsize()) for name, queue in self.fp.queues.items()]) + "\n").encode("utf-8"))
|
||||||
elif data == "reload":
|
|
||||||
self.fp.reload_config()
|
|
||||||
elif data.startswith("claim"):
|
|
||||||
data = data.split(" ")
|
|
||||||
if len(data) == 3:
|
|
||||||
self.fp.queue_all({"type": "claim", "extension": data[1], "token": data[2]})
|
|
||||||
else:
|
|
||||||
self.request.sendall("error: You have to specify calling extension and token\n".encode("utf-8"))
|
|
||||||
else:
|
else:
|
||||||
self.request.sendall("Unknown command, type 'help'\n".encode("utf-8"))
|
self.request.sendall("Unknown command, type 'help'\n".encode("utf-8"))
|
||||||
|
|
||||||
|
127
fieldpoc/dect.py
127
fieldpoc/dect.py
@ -3,7 +3,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import mitel_ommclient2
|
import mitel_ommclient2
|
||||||
import time
|
import time
|
||||||
import hashlib
|
|
||||||
|
|
||||||
logger = logging.getLogger("fieldpoc.dect")
|
logger = logging.getLogger("fieldpoc.dect")
|
||||||
|
|
||||||
@ -19,46 +18,23 @@ class Dect:
|
|||||||
ommsync=True,
|
ommsync=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def temp_num_prefix(self):
|
|
||||||
return next(self.fp.extensions.extensions_by_type("temp")).num
|
|
||||||
|
|
||||||
def load_temp_extensions(self):
|
|
||||||
current_temp_extension = 0
|
|
||||||
used_temp_extensions = self.c.find_users(lambda u: u.num.startswith(self.temp_num_prefix))
|
|
||||||
for u in used_temp_extensions:
|
|
||||||
temp_num = u.num
|
|
||||||
self.fp.temp_extensions[temp_num] = {
|
|
||||||
"name": f"Temp {temp_num[4:]}",
|
|
||||||
"type": "dect",
|
|
||||||
"trunk": False,
|
|
||||||
"dialout_allowed": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_temp_number(self):
|
def get_temp_number(self):
|
||||||
|
temp_num_prefix = next(self.fp.extensions.extensions_by_type("temp")).num
|
||||||
current_temp_extension = 0
|
current_temp_extension = 0
|
||||||
used_temp_extensions = [num[len(self.temp_num_prefix):] for num, ext in self.fp.temp_extensions.items()]
|
used_temp_extensions = [u.num[len(temp_num_prefix):] for u in self.c.find_users(lambda u: u.num.startswith(temp_num_prefix))]
|
||||||
|
|
||||||
while "{:0>4}".format(current_temp_extension) in used_temp_extensions:
|
while "{:0>4}".format(current_temp_extension) in used_temp_extensions:
|
||||||
current_temp_extension += 1
|
current_temp_extension += 1
|
||||||
|
|
||||||
return "{}{:0>4}".format(self.temp_num_prefix, current_temp_extension)
|
return "{}{:0>4}".format(temp_num_prefix, current_temp_extension)
|
||||||
|
|
||||||
def get_sip_password_for_number(self, num):
|
def get_sip_password_for_number(self, num):
|
||||||
return hashlib.sha256(bytes.fromhex((self.fp.config.dect.sipsecret + str(num))[-16:])).hexdigest()[:16]
|
return num
|
||||||
|
|
||||||
def create_and_bind_user(self, d, num):
|
|
||||||
u = self.c.create_user(num)
|
|
||||||
self.c.attach_user_device(u.uid, d.ppn)
|
|
||||||
self.c.set_user_relation_fixed(u.uid)
|
|
||||||
self.c.set_user_sipauth(u.uid, num, self.get_sip_password_for_number(num))
|
|
||||||
return u
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
logger.info("initialising connection to OMM")
|
logger.info("initialising connection to OMM")
|
||||||
self._init_client()
|
self._init_client()
|
||||||
self.load_temp_extensions()
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
msg = self.fp.queues["dect"].get()
|
msg = self.fp.queues["dect"].get()
|
||||||
@ -67,90 +43,13 @@ class Dect:
|
|||||||
if msg.get("type") == "stop":
|
if msg.get("type") == "stop":
|
||||||
break
|
break
|
||||||
elif msg.get("type") == "sync":
|
elif msg.get("type") == "sync":
|
||||||
logger.info("syncing")
|
unbound_devices = self.c.find_devices(lambda d: d.relType == mitel_ommclient2.types.PPRelTypeType("Unbound"))
|
||||||
|
|
||||||
extensions = self.fp.extensions.extensions_by_type("dect")
|
for d in unbound_devices:
|
||||||
extensions_by_num = {e.num: e for e in extensions}
|
print(d)
|
||||||
extensions_by_ipei = {e._c['dect_ipei']: e for _, e in extensions_by_num.items() if e._c.get('dect_ipei')}
|
temp_num = self.get_temp_number()
|
||||||
created_tmp_ext = False
|
u = self.c.create_user(temp_num)
|
||||||
|
print(u)
|
||||||
users_by_ext = {}
|
self.c.attach_user_device(u.uid, d.ppn)
|
||||||
users_by_uid = {}
|
self.c.set_user_relation_fixed(u.uid)
|
||||||
for user in self.c.get_users():
|
self.c.set_user_sipauth(u.uid, temp_num, self.get_sip_password_for_number(temp_num))
|
||||||
e = extensions_by_num.get(user.num)
|
|
||||||
if not e:
|
|
||||||
# user in omm, but not as dect in nerd
|
|
||||||
if user.num.startswith(next(self.fp.extensions.extensions_by_type("temp")).num):
|
|
||||||
users_by_ext[user.num] = user
|
|
||||||
users_by_uid[user.uid] = user
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
# TODO: delete in omm
|
|
||||||
continue
|
|
||||||
elif e._c['name'] != user.name:
|
|
||||||
self.c.set_user_name(user.uid, e._c['name'])
|
|
||||||
if e._c.get('dect_ipei') and user.relType != mitel_ommclient2.types.PPRelTypeType("Unbound"):
|
|
||||||
d = self.c.get_device(user.ppn)
|
|
||||||
if d.ipei != e._c['dect_ipei']:
|
|
||||||
logger.debug(f"Detaching {user} {d}")
|
|
||||||
self.c.detach_user_device(user.uid, user.ppn)
|
|
||||||
|
|
||||||
self.c.set_user_sipauth(user.uid, e.num, self.get_sip_password_for_number(e.num))
|
|
||||||
users_by_ext[user.num] = user
|
|
||||||
users_by_uid[user.uid] = user
|
|
||||||
|
|
||||||
for d in self.c.get_devices():
|
|
||||||
e = extensions_by_ipei.get(d.ipei)
|
|
||||||
if e:
|
|
||||||
# device is in nerd
|
|
||||||
u = users_by_ext.get(e.num)
|
|
||||||
if u and d.relType == mitel_ommclient2.types.PPRelTypeType("Unbound"):
|
|
||||||
logger.debug(f'Binding user for {d}')
|
|
||||||
self.c.attach_user_device(u.uid, d.ppn)
|
|
||||||
self.c.set_user_relation_fixed(u.uid)
|
|
||||||
elif d.relType != mitel_ommclient2.types.PPRelTypeType("Unbound"):
|
|
||||||
ui = users_by_uid.get(d.uid)
|
|
||||||
if ui.num != e.num:
|
|
||||||
logger.debug(f'User for {d} has wrong number')
|
|
||||||
if self.fp.temp_extensions.get(ui.num):
|
|
||||||
self.fp.temp_extensions.pop(ui.num)
|
|
||||||
self.c.set_user_num(d.uid, e.num)
|
|
||||||
self.c.set_user_sipauth(d.uid, e.num, self.get_sip_password_for_number(e.num))
|
|
||||||
self.c.set_user_name(user.uid, e._c['name'])
|
|
||||||
else:
|
|
||||||
logger.debug(f'Creating and binding user for {d}')
|
|
||||||
user = self.create_and_bind_user(d, e.num)
|
|
||||||
self.c.set_user_name(user.uid, e._c['name'])
|
|
||||||
|
|
||||||
elif d.relType == mitel_ommclient2.types.PPRelTypeType("Unbound"):
|
|
||||||
temp_num = self.get_temp_number()
|
|
||||||
logger.debug(f'Creating and binding tmp-user for {d}: {temp_num}')
|
|
||||||
user = self.create_and_bind_user(d, temp_num)
|
|
||||||
self.c.set_user_name(user.uid, f"Temp {temp_num[4:]}")
|
|
||||||
self.fp.temp_extensions[temp_num] = {
|
|
||||||
"name": f"Temp {temp_num[4:]}",
|
|
||||||
"type": "dect",
|
|
||||||
"trunk": False,
|
|
||||||
"dialout_allowed": False,
|
|
||||||
}
|
|
||||||
created_tmp_ext = True
|
|
||||||
|
|
||||||
if created_tmp_ext:
|
|
||||||
self.fp.queues['routing'].put({"type": "sync"})
|
|
||||||
|
|
||||||
elif msg.get("type") == "claim":
|
|
||||||
e = None
|
|
||||||
for ext in self.fp.extensions.extensions_by_type("dect"):
|
|
||||||
if ext._c.get('dect_claim_token') and ext._c['dect_claim_token'] == msg.get("token")[4:]:
|
|
||||||
e = ext
|
|
||||||
break
|
|
||||||
|
|
||||||
if e:
|
|
||||||
user = next(self.c.find_users(lambda u: u.num == msg.get("extension")))
|
|
||||||
if self.fp.temp_extensions.get(user.num):
|
|
||||||
self.fp.temp_extensions.pop(user.num)
|
|
||||||
self.c.set_user_num(user.uid, e.num)
|
|
||||||
self.c.set_user_sipauth(user.uid, e.num, self.get_sip_password_for_number(e.num))
|
|
||||||
self.c.set_user_name(user.uid, e._c['name'])
|
|
||||||
|
|
||||||
self.c.connection.close()
|
|
||||||
|
@ -17,7 +17,6 @@ logger = logging.getLogger("fieldpoc.fieldpoc")
|
|||||||
class FieldPOC:
|
class FieldPOC:
|
||||||
config = None
|
config = None
|
||||||
extensions = None
|
extensions = None
|
||||||
temp_extensions = {}
|
|
||||||
|
|
||||||
def __init__(self, config_file_path, extensions_file_path):
|
def __init__(self, config_file_path, extensions_file_path):
|
||||||
logger.info("loading configuration")
|
logger.info("loading configuration")
|
||||||
@ -76,10 +75,6 @@ class FieldPOC:
|
|||||||
|
|
||||||
logger.info("started components")
|
logger.info("started components")
|
||||||
|
|
||||||
def reload_config(self):
|
|
||||||
self._load_extensions()
|
|
||||||
self.queue_all({"type": "sync"})
|
|
||||||
|
|
||||||
def _load_config(self):
|
def _load_config(self):
|
||||||
self.config = config.Config(json.loads(self.config_file_path.read_text()))
|
self.config = config.Config(json.loads(self.config_file_path.read_text()))
|
||||||
|
|
||||||
|
@ -27,12 +27,11 @@ class YwsdYateModel(YateModel):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, diffsync, ids, attrs):
|
def create(cls, diffsync, ids, attrs):
|
||||||
with diffsync.engine.connect() as conn:
|
with diffsync.engine.connect() as conn:
|
||||||
result = conn.execute(
|
conn.execute(
|
||||||
Yate.table.insert().values(
|
Yate.table.insert().values(
|
||||||
guru3_identifier=ids["guru3_identifier"], **attrs
|
guru3_identifier=ids["guru3_identifier"], **attrs
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
attrs["yate_id"] = result.inserted_primary_key[0]
|
|
||||||
return super().create(diffsync, ids=ids, attrs=attrs)
|
return super().create(diffsync, ids=ids, attrs=attrs)
|
||||||
|
|
||||||
def update(self, attrs):
|
def update(self, attrs):
|
||||||
@ -85,7 +84,7 @@ class YwsdExtensionModel(ExtensionModel):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, diffsync, ids, attrs):
|
def create(cls, diffsync, ids, attrs):
|
||||||
with diffsync.engine.connect() as conn:
|
with diffsync.engine.connect() as conn:
|
||||||
result = conn.execute(
|
conn.execute(
|
||||||
Extension.table.insert().values(
|
Extension.table.insert().values(
|
||||||
extension=ids["extension"],
|
extension=ids["extension"],
|
||||||
type=attrs["extension_type"],
|
type=attrs["extension_type"],
|
||||||
@ -99,7 +98,6 @@ class YwsdExtensionModel(ExtensionModel):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
attrs["extension_id"] = result.inserted_primary_key[0]
|
|
||||||
return super().create(diffsync, ids=ids, attrs=attrs)
|
return super().create(diffsync, ids=ids, attrs=attrs)
|
||||||
|
|
||||||
def update(self, attrs):
|
def update(self, attrs):
|
||||||
@ -212,7 +210,7 @@ class YwsdForkRankModel(ForkRankModel):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, diffsync, ids, attrs):
|
def create(cls, diffsync, ids, attrs):
|
||||||
with diffsync.engine.connect() as conn:
|
with diffsync.engine.connect() as conn:
|
||||||
result = conn.execute(
|
conn.execute(
|
||||||
ForkRank.table.insert().values(
|
ForkRank.table.insert().values(
|
||||||
extension_id=diffsync.get(
|
extension_id=diffsync.get(
|
||||||
"extension", ids["extension"]
|
"extension", ids["extension"]
|
||||||
@ -221,7 +219,6 @@ class YwsdForkRankModel(ForkRankModel):
|
|||||||
**attrs,
|
**attrs,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
attrs["forkrank_id"] = result.inserted_primary_key[0]
|
|
||||||
return super().create(diffsync, ids=ids, attrs=attrs)
|
return super().create(diffsync, ids=ids, attrs=attrs)
|
||||||
|
|
||||||
def update(self, attrs):
|
def update(self, attrs):
|
||||||
@ -278,7 +275,7 @@ class YwsdForkRankMemberModel(ForkRankMemberModel):
|
|||||||
conn.execute(
|
conn.execute(
|
||||||
ForkRank.member_table.update()
|
ForkRank.member_table.update()
|
||||||
.where(
|
.where(
|
||||||
sqlalchemy.and_(
|
_and(
|
||||||
ForkRank.member_table.c.forkrank_id
|
ForkRank.member_table.c.forkrank_id
|
||||||
== self.diffsync.get("forkrank", self.forkrank).forkrank_id,
|
== self.diffsync.get("forkrank", self.forkrank).forkrank_id,
|
||||||
ForkRank.member_table.c.extension_id
|
ForkRank.member_table.c.extension_id
|
||||||
@ -293,7 +290,7 @@ class YwsdForkRankMemberModel(ForkRankMemberModel):
|
|||||||
with self.diffsync.engine.connect() as conn:
|
with self.diffsync.engine.connect() as conn:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
ForkRank.member_table.delete().where(
|
ForkRank.member_table.delete().where(
|
||||||
sqlalchemy.and_(
|
_and(
|
||||||
ForkRank.member_table.c.forkrank_id
|
ForkRank.member_table.c.forkrank_id
|
||||||
== self.diffsync.get("forkrank", self.forkrank).forkrank_id,
|
== self.diffsync.get("forkrank", self.forkrank).forkrank_id,
|
||||||
ForkRank.member_table.c.extension_id
|
ForkRank.member_table.c.extension_id
|
||||||
@ -313,15 +310,11 @@ class BackendNerd(diffsync.DiffSync):
|
|||||||
|
|
||||||
top_level = ["yate", "extension", "user", "forkrank", "forkrankmember"]
|
top_level = ["yate", "extension", "user", "forkrank", "forkrankmember"]
|
||||||
|
|
||||||
def __init__(self, fp, *args, **kwargs):
|
|
||||||
self.fp = fp
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def load(self, data):
|
def load(self, data):
|
||||||
#yate_dect = self.yate(
|
yate_dect = self.yate(
|
||||||
# guru3_identifier="dect", hostname="dect", voip_listener="local"
|
guru3_identifier="dect", hostname="dect", voip_listener="local"
|
||||||
#)
|
)
|
||||||
#self.add(yate_dect)
|
self.add(yate_dect)
|
||||||
yate_sip = self.yate(
|
yate_sip = self.yate(
|
||||||
guru3_identifier="sip", hostname="sip", voip_listener="local"
|
guru3_identifier="sip", hostname="sip", voip_listener="local"
|
||||||
)
|
)
|
||||||
@ -346,8 +339,6 @@ class BackendNerd(diffsync.DiffSync):
|
|||||||
active=True,
|
active=True,
|
||||||
)
|
)
|
||||||
self.add(frm)
|
self.add(frm)
|
||||||
elif value["type"] in ["temp"]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
extension = self.extension(
|
extension = self.extension(
|
||||||
extension=key,
|
extension=key,
|
||||||
@ -370,7 +361,7 @@ class BackendNerd(diffsync.DiffSync):
|
|||||||
user = self.user(
|
user = self.user(
|
||||||
username=key,
|
username=key,
|
||||||
displayname=value["name"],
|
displayname=value["name"],
|
||||||
password=value.get("sip_password", self.fp._dect.get_sip_password_for_number(key)),
|
password=value.get("sip_password", key),
|
||||||
user_type=user_type[value["type"]],
|
user_type=user_type[value["type"]],
|
||||||
trunk=value["trunk"],
|
trunk=value["trunk"],
|
||||||
static_target=value.get("static_target", ""),
|
static_target=value.get("static_target", ""),
|
||||||
@ -482,8 +473,8 @@ class Routing:
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while True:
|
while True:
|
||||||
msg = self.fp.queues["routing"].get()
|
msg = self.fp.queues["dect"].get()
|
||||||
self.fp.queues["routing"].task_done()
|
self.fp.queues["dect"].task_done()
|
||||||
|
|
||||||
if msg.get("type") == "stop":
|
if msg.get("type") == "stop":
|
||||||
break
|
break
|
||||||
@ -491,10 +482,8 @@ class Routing:
|
|||||||
|
|
||||||
logger.info("syncing")
|
logger.info("syncing")
|
||||||
|
|
||||||
state_fieldpoc = BackendNerd(self.fp)
|
state_fieldpoc = BackendNerd()
|
||||||
extensions = self.fp.extensions._c.copy()
|
state_fieldpoc.load(self.fp.extensions._c)
|
||||||
extensions['extensions'].update(self.fp.temp_extensions)
|
|
||||||
state_fieldpoc.load(extensions)
|
|
||||||
state_yate = BackendYwsd()
|
state_yate = BackendYwsd()
|
||||||
state_yate.load("postgresql+psycopg2://{}:{}@{}/{}".format(
|
state_yate.load("postgresql+psycopg2://{}:{}@{}/{}".format(
|
||||||
self.fp.config.database.username,
|
self.fp.config.database.username,
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
"host": "10.222.222.11",
|
"host": "10.222.222.11",
|
||||||
"username": "omm",
|
"username": "omm",
|
||||||
"password": "xxx"
|
"password": "xxx"
|
||||||
"sipsecret": "51df84aace052b0e75b8c1da5a6da9e2"
|
|
||||||
},
|
},
|
||||||
"yate": {
|
"yate": {
|
||||||
"host": "127.0.0.1",
|
"host": "127.0.0.1",
|
||||||
|
@ -95,14 +95,14 @@
|
|||||||
"name": "Temporary Numbers",
|
"name": "Temporary Numbers",
|
||||||
"trunk": false,
|
"trunk": false,
|
||||||
"dialout_allowed": true,
|
"dialout_allowed": true,
|
||||||
"type": "temp"
|
"type": "static"
|
||||||
},
|
},
|
||||||
"9997": {
|
"9999": {
|
||||||
"name": "DECT Claim Extensions",
|
"name": "DECT Claim Extensions",
|
||||||
"type": "static",
|
"type": "static",
|
||||||
"dialout_allowed": false,
|
"dialout_allowed": false,
|
||||||
"trunk": true,
|
"trunk": true,
|
||||||
"static_target": "external/nodata//run/current-system/sw/bin/dect_claim"
|
"static_target": "external/nodata//opt/nerdsync/claim.py"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user