Commit Graph

4918 Commits

Author SHA1 Message Date
Thomas Winget
3b6953badc clarify strict-connect usage and enforce minimum of 2 nodes 2022-10-24 21:58:50 -04:00
Thomas Winget
1231d4e6c4 add 'networkReady' to endpoint status 2022-10-24 20:46:00 -04:00
Jeff Becker
bd5da10885
fixes issue #2029
when read/writing a .loki privkey file we dont rewind a llarp_buffer_t
after use. this is an argument in favor of just removing that type
from the code entirely.

fixes by using 2 distinct locally scoped llarp_buffer_t, one for read,
one for write.
2022-10-24 14:10:52 -04:00
Jason Rhinelander
d011f8fb4a
Bump clang-format to 14 2022-10-20 19:53:52 -03:00
Jason Rhinelander
02108f0504
Merge pull request #2021 from tewinget/route-metric-preference
select gateway with lowest metric
2022-10-20 14:43:45 -03:00
majestrate
86c3b2f4ae
Merge pull request #2019 from jagerman/config-reformat
Config setting documentation edits
2022-10-20 08:35:42 -04:00
Thomas Winget
0c0ba29bae use first gateway, not last... 2022-10-19 22:29:09 -04:00
Jason Rhinelander
c3a515da49
Config setting documentation edits
Rewords/reformats the documentation of various configuration options.
2022-10-18 12:05:13 -03:00
jeff
011bd2e84f format 2022-10-17 19:05:30 -04:00
Jeff Becker
e5b7ea5f2d
dont do queries while down 2022-10-17 09:38:19 -04:00
Jeff Becker
082756c64c
cancel pending queries on down.
after calling Down() any pending queries will not be properly
canceled and results in a crash when we destruct the queries on our side.
2022-10-17 09:10:34 -04:00
Jeff Becker
306d54e285
when running as a client squelch warnings about snode status and make sure we connect out 2022-10-17 08:33:50 -04:00
majestrate
8f532dec89
Merge pull request #2015 from jagerman/oxend-rpc-updates
Oxend RPC updates
2022-10-16 19:11:11 -04:00
jeff
6813dd659c lint 2022-10-16 14:53:23 -04:00
Jason Rhinelander
c5e787b8cb Oxend error ping + unfunded tracking
Currently (from a recent PR) we aren't pinging oxend if not active, but
that behaviour ended up being quite wrong because lokinet needs to ping
even when decommissioned or deregistered (when decommissioned we need
the ping to get commissioned again, and if not registered we need the
ping to get past the "lokinet isn't pinging" nag screen to prepare a
registration).

This considerably revises the pinging behaviour:

- We ping oxend *unless* there is a specific error with our connections
  (i.e. we *should* be establishing peer connections but don't have any)
- If we do have such an error, we send a new oxend "error" ping to
  report the error to oxend and get oxend to hold off on sending uptime
  proofs.

Along the way this also changes how we handle the current node state:
instead of just tracking deregistered/decommissioned, we now track three
states:

- LooksRegistered -- which means the SN is known to the network (but not
  necessarily active or fully staked)
- LooksFunded -- which means it is known *and* is fully funded, but not
  necessarily active
- LooksDecommissioned -- which means it is known, funded, and not
  currently active (which implies decommissioned).

The funded (or more precisely, unfunded) state is now tracked in
rc_lookup_handler in a "greenlist" -- i.e. new SNs that are so new (i.e.
"green") that they aren't even fully staked or active yet.
2022-10-14 20:55:21 -03:00
Jason Rhinelander
bd869b3b07 Log demotion
Demote a couple spammy messages to trace level.
2022-10-14 20:37:19 -03:00
Jason Rhinelander
0e576ff59e Clean up oxend service node list handling
This aligns service node updating logic a bit closer to what happens in
storage server, and should make it a bit more resilient, hopefully
tracking down the (off-Github) reported issue where lokinet sometimes
doesn't see itself as active.

- Initiate a service node list update in the 30s timer lokinet ping
  timer (in case we miss a block notify for some reason); although this
  is expensive, the next point mitigates it:

- Retrieve the block hash with the SN state update, and feed it back
  into the next get_service_nodes call (as "poll_block_hash") so that
  oxend just sends back a mostly-empty response when the block hasn't
  changed, allowing both oxend and lokinet to skip nearly all of the
  work of a service node list update when the block hasn't changed since
  the last poll.  (This was already partially implemenated--we were
  already looking for "unchanged"--but without a block hash to get from
  and pass back to oxend we'd never actually get an "unchanged" result).

- Tighten up the service node list handling by moving the "unchanged"
  handling into the get_service_nodes response handler: this way the
  HandleNewServiceNodeList function is only handling the list but not
  the logic as to whether there actually is a new list or not.
2022-10-14 18:02:53 -03:00
Jason Rhinelander
36792d4337
Fix multi-field < ordering
Lots and lots of places in the code had broken < operators because they
are returning something like:

    foo < other.foo or bar < other.bar;

but this breaks both the strict weak ordering requirements that are
required for the "Compare" requirement for things like
std::map/set/priority_queue.

For example:

    a = {.foo=1, .bar=3}
    b = {.foo=3, .bar=1}

does not have an ordering over a and b (both `a < b` and `b < a` are
satisfied at the same time).

This needs to be instead something like:

    foo < other.foo or (foo == other.foo and bar < other.bar)

but that's a bit clunkier, and it is easier to use std::tie for tuple's
built-in < comparison which does the right thing:

    std::tie(foo, bar) < std::tie(other.foo, other.bar)

(Initially I noticed this in SockAddr/sockaddr_in6, but upon further
investigation this extends to the major of multi-field `operator<`'s.)

This fixes it by using std::tie (or something similar) everywhere we are
doing multi-field inequalities.
2022-10-13 16:29:13 -03:00
Jason Rhinelander
bc071231c8
Add a net::ToString() to help stringify ipaddr_t
Android, in particular, has problems with fmt's built-in variant
handling for this type for some reason.
2022-10-06 22:21:41 -03:00
Jason Rhinelander
768e953522
De-prioritize some windivert logging
We don't really need to log at info level for every
windivert-intercepted incoming and outgoing packet.
2022-10-06 21:32:58 -03:00
Jason Rhinelander
6f31d5108b
Windows fix: iterate over IPv4/IPv6 interfaces separately
If we get back an IPv6 address as the first gateway then we won't have
the expected IPv4 gateway that the route poker needs to operate.

This iterates through them separately so that we treat the IPv4 and IPv6
sides of an address as separate interfaces which should allow the route
poker to find the one it wants (and just skip the IPv6 one).
2022-10-06 21:32:58 -03:00
Jason Rhinelander
e398b5bff8
Fix interface enumeration on posix
The last interface wouldn't be considered.
2022-10-06 21:32:57 -03:00
Jason Rhinelander
fe0f916a09
DRY private range selection; add missing ranges
DRY a chunk of repeated code for finding a free private range.

Also fix it so that it will consider 10.255.0.1/16 and 192.168.255.1/24
(previously it would only check up to octet 254).
2022-10-06 21:32:57 -03:00
Jason Rhinelander
d10c4b9d17
Add more logging around route poking conditions
Log why we aren't doing anything if we aren't going to do anything.
2022-10-06 21:32:57 -03:00
Jason Rhinelander
4bf80833f4
Add InterfaceInfo formatter 2022-10-06 21:32:57 -03:00
Jason Rhinelander
bd8dffc730
Merge pull request #1991 from jagerman/lokinet-vpn-cleanups
Lokinet vpn cleanups
2022-10-06 16:09:56 -03:00
Jason Rhinelander
82e2e6fb10
bake in bootstrap fallbacks at build time
also change the defailt/fallback bootstrap file for testnet to be a list
of RCs (albeit of size 1) rather than just an RC dict.
2022-10-06 13:59:49 -03:00
Thomas Winget
82c95a2486
don't ping core if active with too few peers
If running as a service node, we ping core on a regular interval to
inform it we're running and in a good state.  If we're an active
(not decommissioned or deregistered) service node and have too few
peers and thus we're not actually connected to lokinet, we should skip
that ping so core doesn't think we're ok.
2022-10-06 13:59:49 -03:00
Thomas Winget
cc1bcf86fa
Fallback bootstrap router build parameter
Adds a fallback bootstrap file path parameter to CMake, specify
-DBOOTSTRAP_SYSTEM_PATH="/path/to/file" to use.

Adds a list of (currently 1) obsolete bootstrap RouterIDs to check
bootstrap RCs against.  Will not use bootstrap RCs if they're on that
list.

Log an error periodically if we appear to be an active service node but
have fewer than a set number (5) known peers.

Bumps oxen-logging version for literal _format.
2022-10-06 13:59:47 -03:00
Jason Rhinelander
20281ccc60
Clean up/simplify SN list handling 2022-10-06 13:58:33 -03:00
Jason Rhinelander
ebdb37ac77
Significantly decrease decomm warning frequency
30s is insanely fast for a error level log statement and spams logs
incessantly; reduce it to repeating once every 5 minutes.
2022-10-06 13:58:26 -03:00
Jason Rhinelander
9f258700b7
Lower log level for renewals to debug
Otherwise you see a lot of unnecessary subscription renewals every 30s
in the logs.
2022-10-06 11:56:07 -03:00
Jason Rhinelander
c4c5d128c3
Add ring buffer sink to macos build 2022-10-06 11:56:06 -03:00
Thomas Winget
eaf30de1fd
omq rpc log subscription
respects whether RPC is enabled, removes the log sink otherwise

bumps oxen-mq and oxen-logging
2022-10-06 11:56:04 -03:00
Jason Rhinelander
fae527517d
Try to appease android's crappy toolchain 2022-10-05 17:34:55 -03:00
Jason Rhinelander
f641c08e80
llarp/profiling: refactor to use oxenc producer/consumer
No more llarp_buffer_t here!

(I was tracking down a segfault which led me in here and it was easier
to rewrite this to use bt_dict_{consumer,producer} than to decipher all
the cursed llarp_buffer_t and bencode callback nest).
2022-10-05 16:35:16 -03:00
Jason Rhinelander
d335527a70
Add binary file slurp/dump utility functions
We have basically this same bit of code in tons of places; consolidate
it into llarp::util::slurp_file/llarp::util::dump_file.

Also renames all the extra junk that crept into llarp/util/fs.hpp out of
there into llarp/util/file.hpp instead.
2022-10-05 16:34:32 -03:00
Jason Rhinelander
66c79b232a
Fix log warning 2022-09-22 13:22:43 -03:00
Jason Rhinelander
71ea4f4fa2
RPC: Relax token/range argument handling
- Accept empty string or `null` for token to mean "no token."
- Accept `null` for range to mean "default range."
- Don't use a default range (::0/0) in lokinet-vpn because this will
  fail if IPv6 ranges aren't supported on the platform (e.g. on
  Windows), and isn't necessary: if we omit it then the rpc code already
  uses ::0/0 or 0.0.0.0/0 by default, as needed.
2022-09-21 13:38:17 -03:00
Jason Rhinelander
1f9779cdcd
Windows compilation fix 2022-09-19 20:26:41 -03:00
Jason Rhinelander
93c01623b0
Format fixes & fix warning in format script
- Don't escape '#' in the greps in format.sh: they warn about a spurious
  `\` in the latest grep.
- reformat
2022-09-19 20:26:40 -03:00
Jason Rhinelander
ec91a6db05
ReconfigureDNS fixes, fixes macos exit mode
- ReconfigureDNS wasn't returning the old servers; made it void instead
  (the Apple code can just store a copy of the original upstream
  servers instead).
- Reconfiguring DNS reset the unbound context but didn't replace it, so
  a Down()/Up() would crash.
- Simplify Resolver() destructor to just call Down(), and make it final
  just so that no one tries to inherit from us (so that calling a
  virtual function from the destructor is safe).
- Rename CancelPendingQueries() to Down(); the former cancelled but also
  shut down the object, so the name seemed a bit misleading.
- Rename SetInternalState in Resolver_Base to ResetResolver, so that we
  aren't conflicting with ResetInternalState from Endpoint (which was a
  problem because TunEndpoint inherited from both; it could be resolved
  through the different argument type if we removed the default, but
  that seems gross).
- Make Resolver use a bare unbound context pointer rather than a
  shared_ptr; since Resolver (now) entirely manages it already we don't
  need an extra management layer, and it saves a bunch of `.get()`s.
2022-09-19 20:26:40 -03:00
Jason Rhinelander
2ccc518849
Fix apple dns, part 817 2022-09-19 20:26:40 -03:00
Jeff Becker
bd09f7716d
override method 2022-09-19 20:26:40 -03:00
Jeff Becker
13d1301e08
rewire up dns reconfiguration for macos 2022-09-19 20:26:40 -03:00
Jason Rhinelander
52c6cd497f
Apple DNS fix WIP 2022-09-19 20:26:40 -03:00
Jason Rhinelander
2aae56b0e0
Apple DNS configuration fix: don't obliterate trampoline
On Apple, the network extension is outside the tunnel routing, so we
cannot have libunbound talk directly to upstream (it would leak DNS when
exit mode is enabled).  Instead unbound *always* talks to a localhost
port where we have a "dns trampoline" that takes UDP packets and shoves
them through the tunnel.

We were doing that already, but recent changes here were overwriting the
libunbound settings with.

This also moves the upstream DNS configuration part of `Up()` into its
own method.
2022-09-19 20:26:40 -03:00
Jason Rhinelander
4d920bb2e2
Fix macos
We don't have a resolver on macos, so we were running through this loop
with fails == 0 == m_Impls.size() and throwing, crashing the process.

Early return to avoid the failure and fix macos crash.
2022-09-19 20:26:39 -03:00
Jeff Becker
b81ae95246
remove hunk of win32 specific code, it is dead in this codepath 2022-09-19 20:26:39 -03:00
Jason Rhinelander
27d58044c7
macos compilation fixes 2022-09-19 20:26:39 -03:00
Jason Rhinelander
a82907bc6c
Set various -W flags on apple
Apple supports anything here that Clang supports and should have them
set the same as everywhere else.

Most importantly this gives apple the -Wno-deprecated-declarations flag
which has been driving me nuts on macos.

This also version-gates the -Wno-deprecated-declarations so that it
will turn on again when we bump the version beyond .10.
2022-09-19 20:26:39 -03:00
Jason Rhinelander
291f311259
Fix linked list iteration for windows ip/gateways
We were requiring `->Next` be true, which means we skipped the last (and
often only) entry of the linked lists and so never properly found the
gateway.
2022-09-19 20:26:39 -03:00
Jason Rhinelander
9097435f64
Refactor/fix GetAdaptersAddresses
- We need to pass a flag to get Windows to include gateway info.
- Refactor it to use microsoft's recommended magic default 15000 buffer
  size and repeat in a loop a few times until it works.  Developers,
  developers, developers, developers!
2022-09-19 20:26:39 -03:00
Jason Rhinelander
d1e997177d
Add missing != operator to nuint_t 2022-09-19 20:26:38 -03:00
Jason Rhinelander
613459401d
Linux route poker fixes
- don't add routes when not in exit mode
- don't call Up() from rpc code (the RouterPoker itself decides when to
  call Up())
2022-09-19 20:26:38 -03:00
Jason Rhinelander
e9554c7c5e
Don't do route poking when disabled
IsEnabled() is a bit broader than just IsServiceNode, so use it instead.
2022-09-19 20:26:38 -03:00
Jason Rhinelander
45b3365002
Simplifications
- a `static` is less verbose and otherwise identical to an empty
  namespace for a single declaration like this.
- operator== on two optionals already does exactly what the `is_equal`
  lambda here is doing.
- formatting
2022-09-19 20:26:38 -03:00
Jason Rhinelander
517911b499
Fix crash on shutdown
For some (wrong) reason this chunk of code was here.  Removed it to stop
a crash on shutdown.
2022-09-19 20:26:38 -03:00
Jeff Becker
0fb639db53
idempotent dns 2022-09-19 20:26:38 -03:00
Jeff Becker
26c1336517
limit route poker 2022-09-19 20:26:38 -03:00
Jeff Becker
61f66ac1ec
fix up win32 route poker a bit 2022-09-19 20:26:38 -03:00
Jason Rhinelander
15144f193c
cleanups/simplifications 2022-09-19 20:26:38 -03:00
Jason Rhinelander
9ddf7413af
Windows DNS fixes
- windivert was being set up *before* DNS is set up, so the DNS port was
  nullopt and thus we couldn't properly identify upstream DNS traffic.
- close() doesn't close a socket on Windows, so the socket-bind-close
  approach to get a free UDP port wasn't actually closing, and thus
  unbound upstream constrained to the given port were completely
  failing.
- The unbound thread was accessing the same shared_ptr instance as the
  outer code, which isn't thread-safe; changed it to copy a weak_ptr
  into the lambda instead.
- Exclude upstream DNS traffic in the filter rather than capturing and
  reinjecting it.
2022-09-19 20:26:37 -03:00
Jason Rhinelander
c470349fb3
Log upstream DNS servers when setting it up 2022-09-19 20:26:37 -03:00
Jason Rhinelander
ab2177bee9
Restore Apple hax 2022-09-19 20:26:37 -03:00
Jason Rhinelander
de4bce1d96
Disable upstream TCP DNS
windivert, in particular, will get filtered by this and it almost
certainly won't work.
2022-09-19 20:26:37 -03:00
Jason Rhinelander
bb85ec4595
De-inline vpn/win32.hpp 2022-09-19 20:26:37 -03:00
Jason Rhinelander
2a27698016
Fix backwards from/to in PacketSource_Wrapper
This resulted in DNS responses in Windows having reversed direction when
reinjected, and thus not arriving as expected.
2022-09-19 20:26:37 -03:00
Jason Rhinelander
7a0d4a905e
fix speeling 2022-09-19 20:26:37 -03:00
Jason Rhinelander
07231dd9e1
Fixed crash in DNS resolving
The inner lambda here wasn't keeping the `Query` (`this`) alive, so
`src` wasn't valid anymore.  This changes it to copy the `src`
shared_ptr into the lambda instead of capturing `this`, and fixes it.
2022-09-19 20:26:36 -03:00
Jason Rhinelander
d4739d5d47
Fix sockaddr_len initial value 2022-09-19 20:26:36 -03:00
Jason Rhinelander
b856b78de3
format 2022-09-19 20:26:36 -03:00
Jason Rhinelander
05ed9d6de0
llarp/dns logging refactor
Convert everything in llarp/dns to new-style logging.
2022-09-19 20:26:36 -03:00
Jason Rhinelander
49223a7853
bind/close to find free UDP port
The current code isn't working and gives a 0 (which then fails unbound
initialization).  This replaces it by doing a socket+bind to find a free
port then immediately closes (but passes the port we got into unbound).
2022-09-19 20:26:36 -03:00
Jason Rhinelander
9921dd6c77
Simplify dll loading via static function pointers
- Replaces RAII handling of DLLs with global function pointers.  (We
  don't unload the dll this way, but that seems unnecessary anyway).
- Simplifies code by just needing to call an init function, but not
  needing to pass around an object holding the function pointers.
- Adds a templated dll loader that takes the dll and a list of
  name/pointer pairs to load the dll and set the pointers in one shot.
2022-09-19 20:26:36 -03:00
Jeff Becker
281fbe57f7
promote log statement 2022-09-19 20:26:36 -03:00
Jason Rhinelander
aee618e0d7
Fail if wintun can't retrieve its version 2022-09-19 20:26:36 -03:00
Jason Rhinelander
84ad0ab4d3
Slightly DRY thread-setting code, fix warning
There were warnings from the rc variable being unused; this DRYes it to
use the same code as linux (including the failure check on rc).
2022-09-19 20:26:36 -03:00
Jason Rhinelander
4065413977
Simplify/fix ip_header layout
ip_header wasn't 20 bytes on windows compilations for some unholy
reason.  This restructures it to avoid the template and just use two
different structs for le/be with a condition_t for the ifdef, which
resolves it (and *also* apparently avoids the need for the pack).

Also add a static_assert to check the size.

Also do the same for ipv6.
2022-09-19 20:26:35 -03:00
Jason Rhinelander
58eec9ed11
Avoid strict aliasing warning on function pointers
Cast via an ordinary function pointer rather than a function pointer
reference to avoid the warning.

Also make the pointer in `Func_t` explicit rather than implicit (deduced
into the `Func_t` type) to make it clearer what is going on here.
2022-09-19 20:26:35 -03:00
Jason Rhinelander
dd16158081
DNS: default to 127.3.2.1 & high port on Linux
Lots of tools struggle with non-default DNS port, so keep a listener on
127.3.2.1:53 (by default).

This required various changes to the config handling to hold a vector
(instead of an optional) of defaults and values, and now allows passing
in an array of defaults instead of just a single default.
2022-09-19 20:26:35 -03:00
Jeff Becker
beb07bf46f
small optimizations and fixes
- Ensure ip header struct is packed
- Use fmt
- add missing header
2022-09-19 20:26:28 -03:00
Jason Rhinelander
cfd80f6a17
Fix buffer_printer overflow 2022-09-19 20:25:52 -03:00
Jason Rhinelander
f168b7cf72
llarp_buffer_t: rename badly named operator==
It didn't do equality, it did "does the remaining space start with the
argument" (and so the replacement in the previous commit was broken).

This renames it to avoid the confusion and restores to what it was doing
on dev.
2022-09-19 20:25:51 -03:00
Jason Rhinelander
b9c9ee1ca7
Fix read problem in linux
errno is only set if read returns < 0 and won't be set to 0 if read
succeeds, so we were bailing here frequently on successful reads
(whenever errno happened to be non-0).
2022-09-19 20:25:51 -03:00
Jason Rhinelander
15443568db
Apply some lipstick to llarp_buffer_t
This class is cursed, but also broken under gcc-12.  Apply some lipstick
to get it moving again (but we really need to refactor this because it
is a mess).
2022-09-19 20:25:51 -03:00
Jeff Becker
a02679b87a
revise ./contrib/format.sh 2022-09-08 14:25:00 -04:00
Jeff Becker
7f27760c97
disable lokinet-bootstrap on windows builds 2022-09-08 14:25:00 -04:00
Jeff
871c3e3281
changeset for windows port
* wintun vpn platform for windows
* bundle config snippets into nsis installer for exit node, keyfile persisting, reduced hops mode.
* use wintun for vpn platform
* isolate all windows platform specific code into their own compilation units and libraries
* split up internal libraries into more specific components
* rename liblokinet.a target to liblokinet-amalgum.a to elimiate ambiguity with liblokinet.so
* DNS platform for win32
* rename llarp/ev/ev_libuv.{c,h}pp to llarp/ev/libuv.{c,h}pp as the old name was idiotic
* split up net platform into win32 and posix specific compilation units
* rename lokinet_init.c to easter_eggs.cpp as that is what they are for and it does not need to be a c compilation target
* add cmake option STRIP_SYMBOLS for seperating out debug symbols for windows builds
* intercept dns traffic on all interfaces on windows using windivert and feed it into lokinet
2022-09-08 14:24:59 -04:00
Jeff
d846bab0e1
unbreak android config loading 2022-09-08 14:23:53 -04:00
Jeff
baddad9564
remove compat wrapper 2022-09-08 14:23:53 -04:00
Jeff
253d22db4f
restucture dbus parts
* move dbus into llarp/linux/dbus.hpp and llarp/linux/dbus.cpp
* provide platform abstraction for setting dns in preparation for network manager
2022-09-08 14:23:53 -04:00
Jeff
2d586145ee
wire up dns srv records 2022-09-08 14:23:52 -04:00
Jeff
74362149eb
refactor dns subsystem
we want to be able to have multiple locally bound dns sockets in lokinet so
i restructured most of the dns subsystem in order to make this easier.

specifically, we have a new structure to dns subsystem:

* dns::QueryJob_Base

base type for holding a dns query and response with virtual methods
in charge of sending a reply to whoever requested.

* dns::PacketSource_Base

base type for reading and writing dns messages to and from wherever they came from

* dns::Resolver_Base

base type for filtering and handling of dns messages asynchronously.

* dns::Server

contextualized per endpoint dns object, responsible for all dns related isms.

this change hides all impelementation details of all of the dns components.
adds some more helper functions for parsing dns and dealing with OwnedBuffer.

overall dns becomes less of a pain with this new structure. probably.
2022-09-08 14:23:52 -04:00
Jeff Becker
bf2488d9e8
zero copy compare 2022-09-08 11:22:36 -04:00
majestrate
a8c0f76e1c
Merge pull request #1971 from majestrate/docs-and-such-2022-08-06
more docs
2022-09-03 08:03:28 -04:00
Jason Rhinelander
ceed8e3238
Remove unused parameter names 2022-08-30 15:53:40 -03:00
Jeff
4c897f583c
fix up log statements
* make socket bind errors have a distinct message reported when caught using their own exception type
* omit printing banner in setup when we run from the lokinet executable (but not the liblokinet.so entry point)
2022-08-22 16:50:46 -04:00
Jeff
badf72838e
move all code directory readmes into docs/project-structure.md 2022-08-21 12:18:09 -04:00
Jason Rhinelander
2b7b1fcc79 Working signed macOS GUI build 2022-08-12 21:13:39 -03:00