1
0
Commit Graph

1186 Commits

Author SHA1 Message Date
SomberNight
51dfb1ee3d android/get_apk_versioncode.py: add support for beta/alpha releases 2025-06-12 18:15:49 +00:00
ThomasV
7d9bcfa7af osx/extract_sigs: add more search paths for signed files
- pyinstaller 6.0 changed the file layout of macos binaries
    https://pyinstaller.org/en/stable/CHANGES.html#id81
    https://github.com/pyinstaller/pyinstaller/pull/7619
- this adapts the extract_sigs script to the new layout
2025-06-12 16:23:40 +00:00
SomberNight
c0b235a74e mac build: document "codesigning" and "notarization" 2025-06-12 13:20:27 +00:00
SomberNight
bc672fd2f4 mac build: compare_dmg: "hdiutil attach" to different paths
was experiencing some weird race, maybe hdiutil attach/detach is not blocking?
2025-06-12 13:09:26 +00:00
SomberNight
b6e4ec8f95 mac build: fix broken symlinks inside .app, due to rm-ed qt parts
probably since qt6 migration many symlinks inside the .app in bundled PyQt were broken:
```
$ cp -r $HOME/electrum/dist/Electrum.app Electrum-ghost3.app
cp: /Users/vagrant/electrum/dist/Electrum.app/Contents/Resources/PyQt6/Qt6/lib/QtMultimediaQuick.framework/QtMultimediaQuick: No such file or directory
cp: /Users/vagrant/electrum/dist/Electrum.app/Contents/Resources/PyQt6/Qt6/lib/QtQuickTimeline.framework/QtQuickTimeline: No such file or directory
cp: /Users/vagrant/electrum/dist/Electrum.app/Contents/Resources/PyQt6/Qt6/lib/QtQuickControls2.framework/QtQuickControls2: No such file or directory
```
We were stripping out lots of datas/binaries from Qt from the mac build artifact, leaving behind dangling symlinks.

Instead of adding more hacks on top of the current hacks to also rm the dangling links, I tried to clean up this blacklisting.

There was no issue re the Windows build, no symlinks there, but I like to keep these two spec files in sync.

-----

Some numbers:
- mac:
  - without any exclusions at all, the mac .dmg is 80 MiB.
  - with these exclusions it is 57 MiB.
- win:
  - (haven't built without exclusions.)
  - with the previous stripping strategy, exes were 68M/68M/50M
  - with these exclusions, exes are 66M/66M/50M
2025-06-11 21:35:44 +00:00
SomberNight
2645456130 build: pyinstaller: add type hint imports for spec namespace stuff
see cef4d530e3/PyInstaller/building/build_main.py (L1189)
2025-06-11 21:35:41 +00:00
SomberNight
a042eb9eac mac build: take control of pip's caching mechanism
just like in e.g. appimage build
2025-06-11 18:15:08 +00:00
SomberNight
fc574b4c2f mac build: readme: add more sanity checks for repro 2025-06-11 16:54:15 +00:00
SomberNight
8398a8ae2e mac build: don't compile C extensions for "propcache"
makes build not reproducible

random temporary paths leak into a compiled .so:
dist_ghost43/Electrum.app/Contents/Resources/propcache/_helpers_c.cpython-312-darwin.so
2025-06-11 16:54:12 +00:00
SomberNight
376d5eb6e0 mac build: call "git describe" without "--dirty"
as we dirty the git clone ourselves, well the locale submodule, when we rm the .po files
2025-06-11 16:54:09 +00:00
ThomasV
1d7a5cbe16 android/build.sh: do not log password 2025-06-11 15:03:17 +02:00
SomberNight
af01cba5f7 fix macos build: pyinstaller.spec: copy_metadata was undefined
regression from aecc22dc08
2025-06-10 18:33:29 +00:00
SomberNight
52e8675dd1 build: bump electrum-aionostr to 0.0.11 2025-06-10 18:31:46 +00:00
SomberNight
bac716c925 build: bump electrum-ecc to 0.0.5 2025-06-06 15:04:03 +00:00
SomberNight
ff777946fe build: win/mac: rename pyinstaller spec files
for clarity
also, "deterministic.spec" is a confusing name
2025-06-06 15:00:20 +00:00
f321x
f260cddaf3 bump aiohttp version to 3.12.9 2025-06-06 16:29:18 +02:00
SomberNight
c52a29fc22 build: appimage: fix trezor plugin for new trezorlib
- similar to prev commit that fixes it for the pyinstaller builds
- note that .dist-info/RECORD files are still not reproducible for many packages :((((

```
$ cd dist/
$ ./electrum-*-x86_64.AppImage1 --appimage-extract
$ mv squashfs-root/ squashfs-root1/
$ ./electrum-*-x86_64.AppImage2 --appimage-extract
$ mv squashfs-root/ squashfs-root2/
$ $(cd squashfs-root1; find -type f -exec sha256sum '{}' \; > ./../sha256sum1)
$ $(cd squashfs-root2; find -type f -exec sha256sum '{}' \; > ./../sha256sum2)
$ diff sha256sum1 sha256sum2 > d
$ cat d
507c507
< 269a133bd0d3c85265219c14154323ed934ac36ef9b389da609e43788de2bbcd  ./usr/lib/python3.12/site-packages/cffi-1.17.1.dist-info/RECORD
---
> d2c08987f207eed8e90476c576eeff67c0809d1bfd35234e583a2dc6895c5ff1  ./usr/lib/python3.12/site-packages/cffi-1.17.1.dist-info/RECORD
518c518
< d08bd76099191932f3cd289c19427ccab51d9c00a6c2e5126c57f83a84ec2246  ./usr/lib/python3.12/site-packages/aiohttp-3.12.4.dist-info/RECORD
---
> 281c5c7e2b28e56134fa8690a2a446e3ebc515f8cb3705cd5d06e83c53812fab  ./usr/lib/python3.12/site-packages/aiohttp-3.12.4.dist-info/RECORD
630c630
< 33665bc46c14e2c0f409ef2627170b093f6d7741e9516928928acbeeb717f155  ./usr/lib/python3.12/site-packages/propcache-0.3.1.dist-info/RECORD
---
> 4fddb29289418edb3ec392a09b8efa826333a60cf508055cac809375e497174e  ./usr/lib/python3.12/site-packages/propcache-0.3.1.dist-info/RECORD
1497c1497
< f80c1b47d15a7262ab2b81bcfc1f69816961b9f6b0fd3775ee5b5eb40d4b7a5b  ./usr/lib/python3.12/site-packages/pyqt6_sip-13.10.2.dist-info/RECORD
---
> 1b8fb8ea4d50d401cd4f6689531ab4922cb064b9caaec1aae6bd885c3a387eca  ./usr/lib/python3.12/site-packages/pyqt6_sip-13.10.2.dist-info/RECORD
3108c3108
< ca06bbe1dba05cd647e89411dd301fad15f42d0ff53ac2c509391cf0303c15c4  ./usr/lib/python3.12/site-packages/multidict-6.4.4.dist-info/RECORD
---
> 400741699b95d3c6bde3c64c7a4bb04b26cbdaee437a17b398d202794a1bd4cd  ./usr/lib/python3.12/site-packages/multidict-6.4.4.dist-info/RECORD
3233c3233
< f67e8f271725676df296fab7fab72099f3b853905f9ff677b5de261a52412411  ./usr/lib/python3.12/site-packages/hidapi-0.14.0.post4.dist-info/RECORD
---
> f4d43d5e4c3c2aa3e69fa40c7307b23b82ce4c5e4d5fb041a75b11ddc2ed7423  ./usr/lib/python3.12/site-packages/hidapi-0.14.0.post4.dist-info/RECORD
3271c3271
< aaa92bb84d0f56e82832903f1109dd4362c09ea4af0fefe3d0c95509bc0920ed  ./usr/lib/python3.12/site-packages/cbor2-5.6.5.dist-info/RECORD
---
> 8a21af0fadf22898266581df9987c97ee737a670bef9d71d5f53427619466b88  ./usr/lib/python3.12/site-packages/cbor2-5.6.5.dist-info/RECORD
```

-----

Example RECORD files:

```
$ diff squashfs-root1/./usr/lib/python3.12/site-packages/aiohttp-3.12.4.dist-info/RECORD squashfs-root2/./usr/lib/python3.12/site-packages/aiohttp-3.12.4.dist-info/RECORD
64c64
< aiohttp/_http_parser.cpython-312-x86_64-linux-gnu.so,sha256=SjcWt8faDDTOxBHetnDdlQU31sPj0Suc8pwY1_UnKkE,2868384
---
> aiohttp/_http_parser.cpython-312-x86_64-linux-gnu.so,sha256=xdf5Uy_NnFLmSmqNmeY7wytgwFdY7pHxZLOFFfl2Aq8,2868384
66c66
< aiohttp/_http_writer.cpython-312-x86_64-linux-gnu.so,sha256=g6PDJVuICGYq8rywuI6XFWLP6tw1KLWLpwSvJODWo18,510280
---
> aiohttp/_http_writer.cpython-312-x86_64-linux-gnu.so,sha256=U_Y0F0oYClrGRIKO_agXtAohjJ_jqfG375vESBZ56CY,510280
80c80
< aiohttp/_websocket/mask.cpython-312-x86_64-linux-gnu.so,sha256=u0PluBaJntMBktyD1AYv267FvbUO6gpj-Nt7erLj3Qg,257384
---
> aiohttp/_websocket/mask.cpython-312-x86_64-linux-gnu.so,sha256=tEnvrEzSPEtaFI8V_Ey08zy4SBmDhnm-QnxTxgDSYM4,257384
85c85
< aiohttp/_websocket/reader_c.cpython-312-x86_64-linux-gnu.so,sha256=wfcclW8jYOTVeLDRPO87dPvOBrtXiqxBsnEbP0yyGpI,1816496
---
> aiohttp/_websocket/reader_c.cpython-312-x86_64-linux-gnu.so,sha256=jfey8U3MSIp04r0bG_a1TFkIKSzMQ-FLSAWD3nY4wgg,1816496
```

```
$ diff squashfs-root1/./usr/lib/python3.12/site-packages/hidapi-0.14.0.post4.dist-info/RECORD squashfs-root2/./usr/lib/python3.12/site-packages/hidapi-0.14.0.post4.dist-info/RECORD
1c1
< hid.cpython-312-x86_64-linux-gnu.so,sha256=JefmLwo-XKZZmno3PBdmHnREed0Yw7IaPYCI2k1UmbM,1152024
---
> hid.cpython-312-x86_64-linux-gnu.so,sha256=38htTAcIC4nnTPIIjD0jkdtdwtGyWlBIhQwQ-g0K0iI,1152024
12c12
< hidraw.cpython-312-x86_64-linux-gnu.so,sha256=dJ4kVI0jXUj_Ba2cMcAL7Wjy8fkO21VPbhAmR-5n30Y,1156768
---
> hidraw.cpython-312-x86_64-linux-gnu.so,sha256=7908Jgbg7A-3JRtfKabPr_WJkMIjYgd5pX78_Yajo54,1156768
```

-----

error at runtime:
```
 22.51 | E | plugins.trezor.trezor | error importing trezor plugin deps
Traceback (most recent call last):
  File "/tmp/.mount_electrteDkNu/usr/lib/python3.12/importlib/metadata/__init__.py", line 397, in from_name
    return next(cls.discover(name=name))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
StopIteration

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/tmp/.mount_electrteDkNu/usr/lib/python3.12/site-packages/electrum/plugins/trezor/trezor.py", line 29, in <module>
    from .clientbase import TrezorClientBase, RecoveryDeviceInputMethod
  File "/tmp/.mount_electrteDkNu/usr/lib/python3.12/site-packages/electrum/plugins/trezor/clientbase.py", line 18, in <module>
    import trezorlib.device
  File "/tmp/.mount_electrteDkNu/usr/lib/python3.12/site-packages/trezorlib/device.py", line 27, in <module>
    from slip10 import SLIP10
  File "/tmp/.mount_electrteDkNu/usr/lib/python3.12/site-packages/slip10/__init__.py", line 6, in <module>
    __version__ = importlib.metadata.version(__package__ or __name__)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/.mount_electrteDkNu/usr/lib/python3.12/importlib/metadata/__init__.py", line 889, in version
    return distribution(distribution_name).version
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/.mount_electrteDkNu/usr/lib/python3.12/importlib/metadata/__init__.py", line 862, in distribution
    return Distribution.from_name(distribution_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/.mount_electrteDkNu/usr/lib/python3.12/importlib/metadata/__init__.py", line 399, in from_name
    raise PackageNotFoundError(name)
importlib.metadata.PackageNotFoundError: No package metadata was found for slip10

```
2025-06-05 18:46:12 +00:00
SomberNight
aecc22dc08 build: win/mac: fix trezor plugin for new trezorlib
trezor==0.13.10 pulls in new dep "slip10", which relies on importlib magic

see 19561f0429/slip10/__init__.py (L6)

```
 10.13 | E | plugins.trezor.trezor | error importing trezor plugin deps
Traceback (most recent call last):
  File "importlib/metadata/__init__.py", line 397, in from_name
StopIteration

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "electrum/plugins/trezor/trezor.py", line 29, in <module>
    from .clientbase import TrezorClientBase, RecoveryDeviceInputMethod
  File "PyInstaller/loader/pyimod02_importers.py", line 450, in exec_module
  File "electrum/plugins/trezor/clientbase.py", line 18, in <module>
    import trezorlib.device
  File "PyInstaller/loader/pyimod02_importers.py", line 450, in exec_module
  File "trezorlib/device.py", line 27, in <module>
  File "PyInstaller/loader/pyimod02_importers.py", line 450, in exec_module
  File "slip10/__init__.py", line 6, in <module>
  File "importlib/metadata/__init__.py", line 889, in version
  File "importlib/metadata/__init__.py", line 862, in distribution
  File "importlib/metadata/__init__.py", line 399, in from_name
importlib.metadata.PackageNotFoundError: No package metadata was found for slip10
```
2025-06-05 17:59:31 +00:00
SomberNight
2543c85712 windows build: workaround no longer needed with modern pip
upstream now carries our fix

ref https://github.com/spesmilo/electrum/issues/7739
ref https://github.com/pypa/distlib/pull/165
2025-06-05 17:59:17 +00:00
SomberNight
d17c4beee9 appimage build: add comment we should update to new appimagetool toolchain 2025-06-05 16:52:06 +00:00
SomberNight
fa271e3958 build: bump python versions in binaries
note: 3.12 just transitioned to security-only status,
so can't bump win/mac binaries without switching to 3.13
(as we don't compile our own cpython for those)
2025-06-05 16:51:56 +00:00
SomberNight
0340097754 android build: downgrade cython 2025-06-05 16:51:53 +00:00
SomberNight
04a0d30176 android build: update pinned versions in p4a recipes
note: some files have two versions in them, e.g.:
```
assert CffiRecipe._version == "1.15.1"
class CffiRecipePinned(util.InheritedRecipeMixin, CffiRecipe):
    version = "1.17.1"
```
The assert is left there as I think it might be useful to get a failure if we rebase p4a
and the upstream recipe version changes. There might be substantial changes in the upstream
recipe that we need to adapt to. In the happy case, if we rebase p4a, we just have to manually
update these asserts to the new versions at that time.
2025-06-05 16:51:43 +00:00
SomberNight
70aeaccaf8 android build: rm "certifi" recipe
I don't think this was ever needed(?). certifi is pure python
and it already gets pulled in from the main requirements.txt
2025-06-05 16:51:39 +00:00
SomberNight
ff4c794349 macos build: downgrade Qt to 6.7
this way we can keep the min supported macos version at 11 for now
2025-06-05 16:51:35 +00:00
SomberNight
c8a9083d28 macos build: include libsecp256k1 dylib 2025-06-05 16:51:21 +00:00
SomberNight
538fc37f03 build: bump pyinstaller 2025-06-05 16:51:18 +00:00
SomberNight
876a994731 build: bump libusb version (used in Windows and macOS builds)
At the time of this commit, 1.0.29 was just released and it came irregularly soon after 1.0.28,
hence this just bumps to 1.0.27.
2025-06-05 16:51:14 +00:00
SomberNight
dd42c12ec4 windows build: bump wine 2025-06-05 16:51:10 +00:00
SomberNight
ca17bf6ed3 build: downgrade protobuf
build issues on windows (new version wants a cpp compiler?)
2025-06-05 16:51:06 +00:00
SomberNight
0c42dfc5f1 build: rerun freeze_packages 2025-06-05 16:50:55 +00:00
SomberNight
608fedd7cd build: libsecp: rm hardcoded ABI version 2025-06-05 16:50:51 +00:00
SomberNight
13cd7a6af0 build: bump libsecp256k1 version (0.5.1->0.6.0) 2025-06-05 16:50:42 +00:00
SomberNight
aacaff61c9 build: try to rm transitive dependency on colorama
as it requires hatchling at build-time
and we don't actually need colorama anyway?
2025-06-05 16:50:38 +00:00
SomberNight
bf0ad20c60 build: bump python versions in binaries 2025-06-05 16:50:35 +00:00
SomberNight
37ca5f7eff build: appimage: bump base from debian buster(2019) to bullseye(2021) 2025-06-05 16:50:31 +00:00
SomberNight
491b808cc5 macos build: fix missing keepkeylib
```
  9.76 | D | plugins.keepkey.qt.Plugin | error importing keepkeylib
Traceback (most recent call last):
  File "electrum/plugins/keepkey/keepkey.py", line 77, in __init__
    from . import client
  File "PyInstaller/loader/pyimod02_importers.py", line 450, in exec_module
  File "electrum/plugins/keepkey/client.py", line 1, in <module>
    from .keepkeylib.keepkeylib.client import proto, BaseClient, ProtocolMixin
ModuleNotFoundError: No module named 'electrum.plugins.keepkey.keepkeylib.keepkeylib'
```
2025-06-05 16:48:33 +00:00
SomberNight
447052b4ff interface: add padding and some noise to protocol messages
basic countermeasures against traffic analysis
2025-05-29 17:29:30 +00:00
SomberNight
e75476430c requirements: bump min dnspython to 2.2.0
follow-up 713a20e309
https://github.com/spesmilo/electrum/pull/9833

On Windows, above commit broke dns_hacks.py with dnspython==2.0.0 and 2.1.0.
Newer dnspython works.
Root cause not immediately obvious. Probably not worth debugging, I will just bump the required version instead.

With dnspython==2.0.0, the log gets spammed and dns fails:
```
$ python3 -m pip install --user "dnspython==2.0.0"

 10.59 | E | asyncio | Exception in callback _ProactorBasePipeTransport._call_connection_lost(None)
handle: <Handle _ProactorBasePipeTransport._call_connection_lost(None)>
Traceback (most recent call last):
  File "...\Python310\lib\asyncio\events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "...\Python310\lib\asyncio\proactor_events.py", line 158, in _call_connection_lost
    self._protocol.connection_lost(exc)
  File "...\Python310\site-packages\dns\_asyncio_backend.py", line 38, in connection_lost
    self.recvfrom.set_exception(exc)
asyncio.exceptions.InvalidStateError: invalid state
```
With dnspython==2.1.0, no more log spam but all dns resolutions time out:
```
$ python3 -m pip install --user "dnspython==2.1.0"

 33.29 | I | dns_hacks | dnspython failed to resolve dns (AAAA) for 'testnet.qtornado.com' with error: Timeout('The DNS operation timed out after 31.591506242752075 seconds')
 33.29 | I | dns_hacks | dnspython failed to resolve dns (AAAA) for 'api.coingecko.com' with error: Timeout('The DNS operation timed out after 31.590490579605103 seconds')
 33.29 | I | dns_hacks | dnspython failed to resolve dns (A) for 'testnet.qtornado.com' with error: Timeout('The DNS operation timed out after 31.591506242752075 seconds')
 33.29 | I | dns_hacks | dnspython failed to resolve dns (A) for 'api.coingecko.com' with error: Timeout('The DNS operation timed out after 31.590490579605103 seconds')
 33.35 | I | dns_hacks | dnspython failed to resolve dns (AAAA) for 'blockstream.info' with error: Timeout('The DNS operation timed out after 31.59534502029419 seconds')
 33.35 | I | dns_hacks | dnspython failed to resolve dns (A) for 'blockstream.info' with error: Timeout('The DNS operation timed out after 31.594367265701294 seconds')
 33.38 | I | dns_hacks | dnspython failed to resolve dns (AAAA) for 'electrum.blockstream.info' with error: Timeout('The DNS operation timed out after 31.602211713790894 seconds')
 33.38 | I | dns_hacks | dnspython failed to resolve dns (A) for 'electrum.blockstream.info' with error: Timeout('The DNS operation timed out after 31.60122585296631 seconds')
```
2025-05-21 18:41:25 +00:00
SomberNight
96f861a570 ci: add linter task "ban unicode" to protect against malicious unicode
This script scans the whole codebase for unicode characters and
errors if it finds any, unless the character is specifically whitelisted.

The motivation is to protect against homoglyph attacks, invisible unicode characters,
bidirectional and other control characters, and other malicious unicode usage.

Given that we mostly expect to use ASCII characters in the source code,
the most robust and generic fix seems to be to just ban all unicode usage.

see https://trojansource.codes/ :
> Compilers, interpreters, and build pipelines supporting Unicode should throw errors or warnings
> for unterminated bidirectional control characters in comments or string literals,
> and for identifiers with mixed-script confusable characters.
> Language specifications should formally disallow unterminated bidirectional
> control characters in comments and string literals.
> Code editors and repository frontends should make bidirectional control characters
> and mixed-script confusable characters perceptible with visual symbols or warnings.

also https://github.com/maltfield/detect-malicious-unicode
2025-05-09 18:03:25 +00:00
SomberNight
351cc6abd9 Revert "interface: add padding and some noise to protocol messages"
Unforeseen issues. Needs more work..

This reverts commit 097eabed1f.
2025-05-08 18:34:07 +00:00
SomberNight
097eabed1f interface: add padding and some noise to protocol messages
basic countermeasures against traffic analysis
2025-05-08 14:35:44 +00:00
SomberNight
2600a3bc74 requirements: bump max aiorpcx
and bump pinned aiorpcx and electrum-aionostr
2025-05-08 14:31:11 +00:00
SomberNight
c75b10fe69 requirements: add upper bounds for electrum_ecc and electrum_aionostr 2025-05-08 13:33:16 +00:00
SomberNight
3a5815d854 build: fix locale/build_cleanlocale.sh for macos
fixes https://github.com/spesmilo/electrum/pull/9726/files#r2057787097 :
> The version of readlink installed on macOS does not support the -e flag:
> ```
> 💬 INFO:  preparing electrum-locale.
> readlink: illegal option -- e
> usage: readlink [-fn] [file ...]
> ```
>
> On a mac you can install a version of readlink that argbash expects:
> Using homebrew, `brew install coreutils` will install `greadlink` which supports the `-e` flag.

I don't think we actually need to resolve symlinks here.
There are already some examples of similar usage with realpath vs grealpath. Let's just do that.
2025-05-05 18:31:07 +00:00
Sander van Grieken
960e4ba583 android: update plugin exclude list 2025-04-23 14:50:22 +02:00
ghost43
3d3933afdb Merge pull request #9726 from SomberNight/202504_locale
mv git submodule electrum-locale from contrib to electrum/locale
2025-04-14 17:20:41 +00:00
SomberNight
950658183c contrib: push_locale.py: fix relative paths in messages_qml.pot
The Qt lupdate tool that extracts translatable strings from .qml files
writes paths relative to its output .ts file into the .ts file.
These paths are then retained as-is when converted to .pot format.

The last few commits moved around the working directory of the lupdate tool
(from electrum/locale to electrum/locale/build), which resulted in a change
of all relative paths in the final messages.pot we upload to crowdin.

E.g. from:
```
#: ../gui/qml/components/Addresses.qml:64
```
to:
```
#: ../../gui/qml/components/Addresses.qml:64
```

I think a change like this does not invalidate the translations. Still, it is annoying.

This commit adds an extra processing step to "fix" these strings to:
```
#: electrum/gui/qml/components/Addresses.qml:64
```
2025-04-14 17:18:40 +00:00
SomberNight
31b176169a contrib: mv locale-related scripts to contrib/locale/ 2025-04-14 17:18:37 +00:00
SomberNight
1144d9b8ea build: add script build_cleanlocale.sh 2025-04-14 17:18:34 +00:00