From add01ebc689112f91d6c0cefc1819e29dea76604 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Tue, 9 Sep 2025 21:43:51 -0600 Subject: [PATCH] Gateways, domains, and new service interface (#3001) * add support for inbound proxies * backend changes * fix file type * proxy -> tunnel, implement backend apis * wip start-tunneld * add domains and gateways, remove routers, fix docs links * dont show hidden actions * show and test dns * edit instead of chnage acme and change gateway * refactor: domains page * refactor: gateways page * domains and acme refactor * certificate authorities * refactor public/private gateways * fix fe types * domains mostly finished * refactor: add file control to form service * add ip util to sdk * domains api + migration * start service interface page, WIP * different options for clearnet domains * refactor: styles for interfaces page * minor * better placeholder for no addresses * start sorting addresses * best address logic * comments * fix unnecessary export * MVP of service interface page * domains preferred * fix: address comments * only translations left * wip: start-tunnel & fix build * forms for adding domain, rework things based on new ideas * fix: dns testing * public domain, max width, descriptions for dns * nix StartOS domains, implement public and private domains at interface scope * restart tor instead of reset * better icon for restart tor * dns * fix sort functions for public and private domains * with todos * update types * clean up tech debt, bump dependencies * revert to ts-rs v9 * fix all types * fix dns form * add missing translations * it builds * fix: comments (#3009) * fix: comments * undo default --------- Co-authored-by: Matt Hill * fix: refactor legacy components (#3010) * fix: comments * fix: refactor legacy components * remove default again --------- Co-authored-by: Matt Hill * more translations * wip * fix deadlock * coukd work * simple renaming * placeholder for empty service interfaces table * honor hidden form values * remove logs * reason instead of description * fix dns * misc fixes * implement toggling gateways for service interface * fix showing dns records * move status column in service list * remove unnecessary truthy check * refactor: refactor forms components and remove legacy Taiga UI package (#3012) * handle wh file uploads * wip: debugging tor * socks5 proxy working * refactor: fix multiple comments (#3013) * refactor: fix multiple comments * styling changes, add documentation to sidebar * translations for dns page * refactor: subtle colors * rearrange service page --------- Co-authored-by: Matt Hill * fix file_stream and remove non-terminating test * clean up logs * support for sccache * fix gha sccache * more marketplace translations * install wizard clarity * stub hostnameInfo in migration * fix address info after setup, fix styling on SI page, new 040 release notes * remove tor logs from os * misc fixes * reset tor still not functioning... * update ts * minor styling and wording * chore: some fixes (#3015) * fix gateway renames * different handling for public domains * styling fixes * whole navbar should not be clickable on service show page * timeout getState request * remove links from changelog * misc fixes from pairing * use custom name for gateway in more places * fix dns parsing * closes #3003 * closes #2999 * chore: some fixes (#3017) * small copy change * revert hardcoded error for testing * dont require port forward if gateway is public * use old wan ip when not available * fix .const hanging on undefined * fix test * fix doc test * fix renames * update deps * allow specifying dependency metadata directly * temporarily make dependencies not cliackable in marketplace listings * fix socks bind * fix test --------- Co-authored-by: Aiden McClelland Co-authored-by: waterplea --- .github/workflows/startos-iso.yaml | 10 + .gitignore | 3 - Makefile | 92 +- build/dpkg-deps/generate.sh | 4 +- build/dpkg-deps/unstable.depends | 3 +- .../container-runtime-failure.service | 2 +- container-runtime/deb-install.sh | 7 +- container-runtime/package-lock.json | 2 +- .../src/Adapters/EffectCreator.ts | 9 +- container-runtime/src/Adapters/RpcListener.ts | 20 +- .../Systems/SystemForEmbassy/index.ts | 19 +- container-runtime/update-image.sh | 6 +- core/Cargo.lock | 3627 ++++++++-- core/build-containerbox.sh | 4 +- core/build-registrybox.sh | 4 +- core/build-startbox.sh | 11 +- core/build-ts.sh | 2 +- core/build-tunnelbox.sh | 36 + core/builder-alias.sh | 3 + core/install-cli.sh | 2 +- core/models/Cargo.toml | 11 +- core/models/bindings/ServiceInterfaceId.ts | 2 +- core/models/src/data_url.rs | 90 +- core/models/src/errors.rs | 14 +- core/models/src/id/gateway.rs | 58 + core/models/src/id/host.rs | 17 - core/models/src/id/mod.rs | 19 +- core/models/src/id/package.rs | 17 - core/models/src/id/service_interface.rs | 17 - core/startos/Cargo.toml | 77 +- core/startos/src/account.rs | 40 +- core/startos/src/action.rs | 13 +- core/startos/src/auth.rs | 98 +- core/startos/src/backup/backup_bulk.rs | 8 +- core/startos/src/backup/mod.rs | 2 +- core/startos/src/backup/os.rs | 26 +- core/startos/src/backup/restore.rs | 4 +- core/startos/src/backup/target/cifs.rs | 6 +- core/startos/src/backup/target/mod.rs | 12 +- core/startos/src/bins/mod.rs | 61 +- core/startos/src/bins/registry.rs | 30 + core/startos/src/bins/start_cli.rs | 2 +- core/startos/src/bins/start_init.rs | 15 +- core/startos/src/bins/startd.rs | 4 +- core/startos/src/bins/tunnel.rs | 117 + core/startos/src/context/cli.rs | 178 +- core/startos/src/context/config.rs | 26 +- core/startos/src/context/diagnostic.rs | 4 +- core/startos/src/context/init.rs | 4 +- core/startos/src/context/install.rs | 2 +- core/startos/src/context/rpc.rs | 49 +- core/startos/src/context/setup.rs | 11 +- core/startos/src/control.rs | 2 +- core/startos/src/db/mod.rs | 4 +- core/startos/src/db/model/mod.rs | 6 +- core/startos/src/db/model/package.rs | 7 +- core/startos/src/db/model/private.rs | 10 +- core/startos/src/db/model/public.rs | 134 +- core/startos/src/db/prelude.rs | 13 + core/startos/src/dependencies.rs | 51 +- core/startos/src/developer/mod.rs | 65 +- core/startos/src/diagnostic.rs | 9 +- core/startos/src/disk/fsck/btrfs.rs | 2 +- core/startos/src/disk/fsck/ext4.rs | 4 +- core/startos/src/disk/fsck/mod.rs | 4 +- core/startos/src/disk/mod.rs | 11 +- core/startos/src/disk/mount/backup.rs | 2 +- .../startos/src/disk/mount/filesystem/cifs.rs | 2 +- .../src/disk/mount/filesystem/httpdirfs.rs | 2 +- core/startos/src/disk/mount/filesystem/mod.rs | 7 +- .../src/disk/mount/filesystem/overlayfs.rs | 7 +- core/startos/src/disk/mount/guard.rs | 2 +- core/startos/src/disk/mount/util.rs | 2 +- core/startos/src/disk/util.rs | 6 +- core/startos/src/firmware.rs | 4 +- core/startos/src/hostname.rs | 6 +- core/startos/src/init.rs | 77 +- core/startos/src/install/mod.rs | 16 +- core/startos/src/lib.rs | 17 +- core/startos/src/logs.rs | 11 +- core/startos/src/lxc/mod.rs | 2 +- core/startos/src/middleware/auth.rs | 245 +- core/startos/src/middleware/db.rs | 2 +- core/startos/src/middleware/mod.rs | 1 + .../auth.rs => middleware/signature.rs} | 191 +- core/startos/src/net/acme.rs | 14 +- core/startos/src/net/dns.rs | 702 +- core/startos/src/net/forward.rs | 336 +- .../net/{network_interface.rs => gateway.rs} | 987 ++- core/startos/src/net/host/address.rs | 250 +- core/startos/src/net/host/binding.rs | 90 +- core/startos/src/net/host/mod.rs | 53 +- core/startos/src/net/mod.rs | 23 +- core/startos/src/net/net_controller.rs | 299 +- core/startos/src/net/service_interface.rs | 19 +- core/startos/src/net/socks.rs | 176 + core/startos/src/net/ssl.rs | 6 +- core/startos/src/net/static_server.rs | 12 +- core/startos/src/net/tor.rs | 1407 ++-- core/startos/src/net/tunnel.rs | 138 + core/startos/src/net/vhost.rs | 144 +- core/startos/src/net/web_server.rs | 72 +- core/startos/src/net/wifi.rs | 15 +- core/startos/src/notifications.rs | 52 +- core/startos/src/os_install/gpt.rs | 4 +- core/startos/src/os_install/mbr.rs | 8 +- core/startos/src/os_install/mod.rs | 10 +- core/startos/src/registry/admin.rs | 10 +- core/startos/src/registry/asset.rs | 8 +- core/startos/src/registry/context.rs | 201 +- core/startos/src/registry/db.rs | 8 +- core/startos/src/registry/device_info.rs | 2 +- core/startos/src/registry/info.rs | 3 +- core/startos/src/registry/mod.rs | 7 +- core/startos/src/registry/os/asset/add.rs | 30 +- core/startos/src/registry/os/asset/get.rs | 18 +- core/startos/src/registry/os/asset/mod.rs | 2 +- core/startos/src/registry/os/asset/sign.rs | 22 +- core/startos/src/registry/os/index.rs | 5 +- core/startos/src/registry/os/mod.rs | 2 +- core/startos/src/registry/os/version/mod.rs | 51 +- .../startos/src/registry/os/version/signer.rs | 2 +- core/startos/src/registry/package/add.rs | 12 +- core/startos/src/registry/package/category.rs | 4 +- core/startos/src/registry/package/get.rs | 2 +- core/startos/src/registry/package/index.rs | 7 +- core/startos/src/registry/package/mod.rs | 2 +- core/startos/src/registry/package/signer.rs | 2 +- .../src/registry/{signer/mod.rs => signer.rs} | 7 +- core/startos/src/rpc_continuations.rs | 4 +- .../s9pk/merkle_archive/directory_contents.rs | 13 +- .../src/s9pk/merkle_archive/expected.rs | 2 +- .../src/s9pk/merkle_archive/file_contents.rs | 2 +- core/startos/src/s9pk/merkle_archive/hash.rs | 2 +- core/startos/src/s9pk/merkle_archive/mod.rs | 12 +- .../src/s9pk/merkle_archive/source/http.rs | 2 +- .../src/s9pk/merkle_archive/source/mod.rs | 2 +- core/startos/src/s9pk/merkle_archive/test.rs | 2 +- core/startos/src/s9pk/mod.rs | 2 +- core/startos/src/s9pk/rpc.rs | 8 +- core/startos/src/s9pk/v1/builder.rs | 22 +- core/startos/src/s9pk/v1/reader.rs | 4 +- core/startos/src/s9pk/v2/compat.rs | 4 +- core/startos/src/s9pk/v2/manifest.rs | 11 +- core/startos/src/s9pk/v2/mod.rs | 7 +- core/startos/src/s9pk/v2/pack.rs | 127 +- core/startos/src/service/action.rs | 16 +- core/startos/src/service/cli.rs | 2 +- core/startos/src/service/effects/action.rs | 4 +- core/startos/src/service/effects/callbacks.rs | 1 - core/startos/src/service/effects/control.rs | 16 +- .../startos/src/service/effects/dependency.rs | 2 +- core/startos/src/service/effects/health.rs | 5 +- core/startos/src/service/effects/mod.rs | 4 +- core/startos/src/service/effects/net/bind.rs | 3 +- core/startos/src/service/effects/net/ssl.rs | 10 +- core/startos/src/service/effects/prelude.rs | 4 +- .../src/service/effects/subcontainer/mod.rs | 10 +- .../src/service/effects/subcontainer/sync.rs | 16 +- .../effects/subcontainer/sync_dummy.rs | 2 +- core/startos/src/service/effects/version.rs | 2 +- core/startos/src/service/mod.rs | 14 +- .../src/service/persistent_container.rs | 15 +- core/startos/src/service/rpc.rs | 2 +- core/startos/src/service/service_actor.rs | 6 +- core/startos/src/service/service_map.rs | 12 +- core/startos/src/service/transition/backup.rs | 4 +- .../startos/src/service/transition/restart.rs | 2 +- core/startos/src/setup.rs | 36 +- core/startos/src/shutdown.rs | 13 +- .../signer => sign}/commitment/blake3.rs | 4 +- .../commitment/merkle_archive.rs | 6 +- .../signer => sign}/commitment/mod.rs | 0 .../signer => sign}/commitment/request.rs | 2 +- .../src/{registry/signer => }/sign/ed25519.rs | 2 +- .../src/{registry/signer => }/sign/mod.rs | 9 +- core/startos/src/ssh.rs | 10 +- core/startos/src/system.rs | 10 +- core/startos/src/tunnel/api.rs | 130 + core/startos/src/tunnel/client.conf.template | 10 + core/startos/src/tunnel/context.rs | 224 + core/startos/src/tunnel/db.rs | 197 + core/startos/src/tunnel/forward.rs | 1 + core/startos/src/tunnel/mod.rs | 82 + .../src/tunnel/server-peer.conf.template | 4 + core/startos/src/tunnel/server.conf.template | 5 + core/startos/src/tunnel/wg.rs | 220 + core/startos/src/update/mod.rs | 28 +- core/startos/src/upload.rs | 8 +- core/startos/src/util/actor/background.rs | 26 + core/startos/src/util/actor/concurrent.rs | 10 +- core/startos/src/util/actor/mod.rs | 6 +- core/startos/src/util/collections/eq_map.rs | 20 +- core/startos/src/util/collections/eq_set.rs | 425 ++ core/startos/src/util/collections/mod.rs | 42 + core/startos/src/util/crypto.rs | 4 +- core/startos/src/util/future.rs | 2 +- core/startos/src/util/http_reader.rs | 4 +- core/startos/src/util/io.rs | 73 +- core/startos/src/util/iter.rs | 31 + core/startos/src/util/logger.rs | 2 +- core/startos/src/util/mod.rs | 7 +- core/startos/src/util/rpc.rs | 8 +- core/startos/src/util/rpc_client.rs | 4 +- core/startos/src/util/serde.rs | 9 +- core/startos/src/util/sync.rs | 232 +- core/startos/src/version/mod.rs | 23 +- .../src/version/update_details/v0_4_0.md | 82 +- core/startos/src/version/v0_3_5_1.rs | 2 +- core/startos/src/version/v0_3_5_2.rs | 2 +- core/startos/src/version/v0_3_6_alpha_0.rs | 69 +- core/startos/src/version/v0_3_6_alpha_1.rs | 2 +- core/startos/src/version/v0_3_6_alpha_10.rs | 15 +- core/startos/src/version/v0_3_6_alpha_11.rs | 2 +- core/startos/src/version/v0_3_6_alpha_12.rs | 2 +- core/startos/src/version/v0_3_6_alpha_13.rs | 2 +- core/startos/src/version/v0_3_6_alpha_14.rs | 2 +- core/startos/src/version/v0_3_6_alpha_15.rs | 2 +- core/startos/src/version/v0_3_6_alpha_16.rs | 2 +- core/startos/src/version/v0_3_6_alpha_17.rs | 2 +- core/startos/src/version/v0_3_6_alpha_18.rs | 2 +- core/startos/src/version/v0_3_6_alpha_2.rs | 2 +- core/startos/src/version/v0_3_6_alpha_3.rs | 2 +- core/startos/src/version/v0_3_6_alpha_4.rs | 2 +- core/startos/src/version/v0_3_6_alpha_5.rs | 2 +- core/startos/src/version/v0_3_6_alpha_6.rs | 2 +- core/startos/src/version/v0_3_6_alpha_7.rs | 2 +- core/startos/src/version/v0_3_6_alpha_8.rs | 14 +- core/startos/src/version/v0_3_6_alpha_9.rs | 2 +- core/startos/src/version/v0_4_0_alpha_0.rs | 4 +- core/startos/src/version/v0_4_0_alpha_1.rs | 2 +- core/startos/src/version/v0_4_0_alpha_10.rs | 111 + core/startos/src/version/v0_4_0_alpha_2.rs | 2 +- core/startos/src/version/v0_4_0_alpha_3.rs | 2 +- core/startos/src/version/v0_4_0_alpha_4.rs | 2 +- core/startos/src/version/v0_4_0_alpha_5.rs | 2 +- core/startos/src/version/v0_4_0_alpha_6.rs | 2 +- core/startos/src/version/v0_4_0_alpha_7.rs | 2 +- core/startos/src/version/v0_4_0_alpha_8.rs | 2 +- core/startos/src/version/v0_4_0_alpha_9.rs | 4 +- debian/postinst | 19 +- image-recipe/build.sh | 6 - patch-db | 2 +- sdk/base/lib/Effects.ts | 1 + sdk/base/lib/actions/input/builder/value.ts | 98 +- sdk/base/lib/actions/input/inputSpecTypes.ts | 3 - sdk/base/lib/actions/setupActions.ts | 42 +- sdk/base/lib/dependencies/dependencies.ts | 4 +- .../lib/dependencies/setupDependencies.ts | 5 +- sdk/base/lib/osBindings/ActionInput.ts | 2 + ...tInterfaceParams.ts => AddTunnelParams.ts} | 2 +- .../BindingGatewaySetEnabledParams.ts | 8 + sdk/base/lib/osBindings/DepInfo.ts | 5 +- ...faceSetInboundParams.ts => DnsSettings.ts} | 6 +- sdk/base/lib/osBindings/DomainSettings.ts | 4 + .../osBindings/{ProcedureId.ts => EventId.ts} | 2 +- .../lib/osBindings/ForgetGatewayParams.ts | 4 + .../{UnsetInboundParams.ts => GatewayId.ts} | 2 +- sdk/base/lib/osBindings/Host.ts | 5 +- sdk/base/lib/osBindings/HostAddress.ts | 11 - sdk/base/lib/osBindings/HostnameInfo.ts | 7 +- sdk/base/lib/osBindings/IpHostname.ts | 3 +- sdk/base/lib/osBindings/IpInfo.ts | 2 + sdk/base/lib/osBindings/LoginParams.ts | 3 +- sdk/base/lib/osBindings/Metadata.ts | 4 + sdk/base/lib/osBindings/MetadataSrc.ts | 5 + sdk/base/lib/osBindings/NetInfo.ts | 4 +- sdk/base/lib/osBindings/NetworkInfo.ts | 5 +- .../lib/osBindings/NetworkInterfaceInfo.ts | 5 +- ....ts => NetworkInterfaceSetPublicParams.ts} | 5 +- sdk/base/lib/osBindings/PackageDataEntry.ts | 3 +- ...{DomainConfig.ts => PublicDomainConfig.ts} | 6 +- sdk/base/lib/osBindings/RemoveTunnelParams.ts | 4 + .../lib/osBindings/RenameGatewayParams.ts | 4 + sdk/base/lib/osBindings/Sessions.ts | 9 +- sdk/base/lib/osBindings/UnsetPublicParams.ts | 4 + sdk/base/lib/osBindings/index.ts | 21 +- sdk/base/lib/s9pk/index.ts | 22 +- .../lib/test/startosTypeValidation.test.ts | 1 + sdk/base/lib/util/Hostname.ts | 25 - sdk/base/lib/util/getServiceInterface.ts | 7 +- sdk/base/lib/util/index.ts | 2 +- sdk/base/lib/util/ip.ts | 85 + sdk/package/lib/StartSdk.ts | 4 +- sdk/package/lib/backup/Backups.ts | 2 +- sdk/package/lib/util/SubContainer.ts | 18 +- sdk/package/lib/util/fileHelper.ts | 8 +- sdk/package/lib/util/index.ts | 1 - sdk/package/package-lock.json | 4 +- sdk/package/package.json | 2 +- system-images/README.md | 3 - system-images/binfmt/.gitignore | 1 - system-images/binfmt/Dockerfile | 1 - system-images/binfmt/Makefile | 15 - system-images/binfmt/manifest.json | 1 - system-images/compat/.gitignore | 5 - system-images/compat/Cargo.lock | 6120 ----------------- system-images/compat/Cargo.toml | 33 - system-images/compat/Dockerfile | 1 - system-images/compat/Makefile | 24 - system-images/compat/build.sh | 27 - system-images/compat/src/backup.rs | 79 - system-images/compat/src/config/mod.rs | 117 - .../compat/src/config/rule_parser.pest | 76 - system-images/compat/src/config/rules.rs | 1261 ---- system-images/compat/src/main.rs | 337 - system-images/utils/.gitignore | 1 - system-images/utils/Dockerfile | 3 - system-images/utils/Makefile | 15 - web/package-lock.json | 5937 ++++++++-------- web/package.json | 32 +- .../install-wizard/src/app/app.component.html | 5 +- .../install-wizard/src/app/app.component.scss | 3 + .../install-wizard/src/app/app.component.ts | 7 +- .../install-wizard/src/app/app.module.ts | 8 +- .../install-wizard/src/app/app.utils.ts | 2 +- .../src/components/menu/menu.component.html | 18 +- .../components/menu/menu.component.module.ts | 8 +- .../src/pages/show/about.component.ts | 12 +- .../dependencies/dependencies.component.ts | 5 +- .../dependencies/dependency-item.component.ts | 17 +- .../src/pages/show/flavors.component.ts | 15 +- .../src/pages/show/item.component.ts | 5 +- .../src/pages/show/link.component.ts | 3 +- .../src/pages/show/links.component.ts | 10 +- .../src/pages/show/versions.component.ts | 6 +- .../setup-wizard/src/app/app.module.ts | 8 +- .../src/app/components/cifs.component.ts | 62 +- .../app/components/documentation.component.ts | 4 +- .../src/app/pages/loading.page.ts | 51 +- .../src/app/pages/success.page.ts | 12 +- .../src/app/services/api.service.ts | 1 + .../src/app/services/live-api.service.ts | 7 + .../src/app/services/mock-api.service.ts | 7 +- .../assets/img/service-icons/bitcoin-core.svg | 2 + .../assets/img/service-icons/bitcoind.svg | 9 - .../initializing/initializing.component.ts | 51 +- .../shared/src/components/prompt.component.ts | 77 +- .../src/directives/docs-link.directive.ts | 8 +- .../shared/src/i18n/dictionaries/de.ts | 153 +- .../shared/src/i18n/dictionaries/en.ts | 167 +- .../shared/src/i18n/dictionaries/es.ts | 151 +- .../shared/src/i18n/dictionaries/fr.ts | 151 +- .../shared/src/i18n/dictionaries/pl.ts | 151 +- web/projects/shared/src/i18n/i18n.pipe.ts | 8 +- .../shared/src/services/dialog.service.ts | 10 +- web/projects/shared/styles/taiga.scss | 21 +- web/projects/ui/src/app/app.module.ts | 15 +- web/projects/ui/src/app/app.providers.ts | 6 +- .../app/components/refresh-alert.component.ts | 28 +- .../app/routes/diagnostic/logs.component.ts | 2 +- .../login/ca-wizard/ca-wizard.component.html | 2 +- .../login/ca-wizard/ca-wizard.component.scss | 2 +- .../login/ca-wizard/ca-wizard.component.ts | 2 +- .../src/app/routes/login/login.component.html | 20 +- .../ui/src/app/routes/login/login.module.ts | 16 +- .../ui/src/app/routes/login/login.page.scss | 4 + .../portal/components/form.component.ts | 6 +- .../form/containers/array.component.ts | 230 + .../form/containers/control.component.ts | 154 + .../form/containers/control.directive.ts | 45 + .../form/containers/group.component.ts | 119 + .../form/containers/object.component.ts | 118 + .../union.component.ts} | 46 +- .../components/form/control.directive.ts | 31 - .../routes/portal/components/form/control.ts | 45 - .../form/controls/color.component.ts | 46 + .../components/form/controls/control.ts | 41 + .../components/form/controls/controls.ts | 22 + .../form/controls/datetime.component.ts | 144 + .../form/controls/file.component.ts | 118 + .../multiselect.component.ts} | 46 +- .../form/controls/number.component.ts | 54 + .../form/controls/select.component.ts | 80 + .../form/controls/text.component.ts | 104 + .../form/controls/textarea.component.ts | 52 + .../form/controls/toggle.component.ts | 33 + .../form/form-array/form-array.component.html | 57 - .../form/form-array/form-array.component.scss | 50 - .../form/form-array/form-array.component.ts | 89 - .../form/form-color/form-color.component.html | 30 - .../form/form-color/form-color.component.scss | 33 - .../form/form-color/form-color.component.ts | 16 - .../form-control/form-control.component.html | 58 - .../form-control/form-control.component.scss | 11 - .../form-control/form-control.component.ts | 72 - .../form-control/form-control.providers.ts | 30 - .../form-datetime.component.html | 54 - .../form-datetime/form-datetime.component.ts | 37 - .../form/form-file/form-file.component.html | 33 - .../form/form-file/form-file.component.scss | 46 - .../form/form-file/form-file.component.ts | 15 - .../form/form-group/form-group.component.html | 30 - .../form/form-group/form-group.component.scss | 35 - .../form/form-group/form-group.component.ts | 36 - .../form/form-group/form-group.providers.ts | 31 - .../form-multiselect.component.html | 18 - .../form-number/form-number.component.html | 22 - .../form/form-number/form-number.component.ts | 12 - .../form-object/form-object.component.html | 23 - .../form-object/form-object.component.scss | 41 - .../form/form-object/form-object.component.ts | 39 - .../form-select/form-select.component.html | 17 - .../form/form-select/form-select.component.ts | 32 - .../form/form-text/form-text.component.html | 48 - .../form/form-text/form-text.component.scss | 8 - .../form/form-text/form-text.component.ts | 17 - .../form-textarea.component.html | 20 - .../form-textarea/form-textarea.component.ts | 13 - .../form-toggle/form-toggle.component.html | 13 - .../form/form-toggle/form-toggle.component.ts | 14 - .../form/form-union/form-union.component.html | 11 - .../form/form-union/form-union.component.scss | 8 - .../portal/components/form/form.module.ts | 110 - .../portal/components/form/invalid.service.ts | 19 - .../form/{ => pipes}/filter-hidden.pipe.ts | 1 - .../components/form/{ => pipes}/hint.pipe.ts | 1 - .../form/{ => pipes}/mustache.pipe.ts | 1 - .../components/header/about.component.ts | 4 +- .../components/header/menu.component.ts | 2 +- .../portal/components/interfaces/acme.pipe.ts | 11 - .../{ => addresses}/actions.component.ts | 94 +- .../addresses/addresses.component.ts | 110 + .../interfaces/addresses/item.component.ts | 89 + .../interfaces/clearnet.component.ts | 301 - .../interfaces/gateways.component.ts | 114 + .../interfaces/interface.component.ts | 113 +- .../interfaces/interface.service.ts | 520 ++ .../components/interfaces/interface.utils.ts | 134 - .../components/interfaces/local.component.ts | 52 - .../portal/components/interfaces/mask.pipe.ts | 2 +- .../interfaces/private-domains.component.ts | 183 + .../public-domains/dns.component.ts | 167 + .../interfaces/public-domains/pd.component.ts | 82 + .../public-domains/pd.item.component.ts | 116 + .../interfaces/public-domains/pd.service.ts | 261 + .../components/interfaces/status.component.ts | 26 - .../interfaces/tor-domains.component.ts | 192 + .../components/interfaces/tor.component.ts | 233 - .../portal/components/table.component.ts | 2 +- .../portal/components/tabs.component.ts | 4 +- .../routes/backups/modals/edit.component.ts | 42 +- .../routes/backups/modals/jobs.component.ts | 2 +- .../backups/modals/targets.component.ts | 2 +- .../routes/portal/routes/logs/logs.routes.ts | 4 - .../routes/logs/routes/outlet.component.ts | 6 - .../routes/logs/routes/tor.component.ts | 32 - .../components/controls.component.ts | 4 +- .../marketplace/components/tile.component.ts | 2 +- .../marketplace/modals/preview.component.ts | 5 +- .../marketplace/services/alerts.service.ts | 6 +- .../portal/routes/metrics/time.component.ts | 3 +- .../routes/notifications/item.component.ts | 1 + .../routes/notifications/table.component.ts | 17 +- .../components/dependencies.component.ts | 41 +- .../components/health-checks.component.ts | 2 +- .../components/interface-item.component.ts | 69 +- .../components/interfaces.component.ts | 40 +- .../services/components/progress.component.ts | 2 +- .../services/components/status.component.ts | 16 +- .../services/components/task.component.ts | 127 +- .../services/components/tasks.component.ts | 10 +- .../services/components/uptime.component.ts | 4 +- .../services/dashboard/dashboard.component.ts | 37 +- .../services/dashboard/service.component.ts | 12 +- .../services/dashboard/services.service.ts | 31 - .../services/dashboard/status.component.ts | 6 +- .../services/dashboard/ui-launch.component.ts | 8 +- .../services/modals/action-input.component.ts | 13 +- .../action-success-member.component.ts | 45 +- .../action-success-single.component.ts | 33 +- .../routes/services/routes/about.component.ts | 28 +- .../services/routes/actions.component.ts | 1 + .../services/routes/interface.component.ts | 62 +- .../services/routes/outlet.component.ts | 109 +- .../services/routes/service.component.ts | 13 +- .../routes/sideload/sideload.component.ts | 9 +- .../system/routes/acme/acme.component.ts | 290 - .../authorities/authorities.component.ts | 63 + .../routes/authorities/authority.service.ts | 181 + .../routes/authorities/item.component.ts | 99 + .../routes/authorities/table.component.ts | 27 + .../system/routes/backups/backup.component.ts | 4 +- .../routes/backups/backups.component.ts | 7 +- .../routes/backups/physical.component.ts | 7 +- .../routes/backups/restore.component.ts | 2 +- .../system/routes/backups/status.component.ts | 10 +- .../routes/system/routes/dns/dns.component.ts | 215 + .../routes/system/routes/domains/constants.ts | 134 - .../routes/domains/domains.component.ts | 197 - .../system/routes/domains/info.component.ts | 16 - .../system/routes/domains/table.component.ts | 134 - .../system/routes/email/email.component.ts | 60 +- .../routes/gateways/gateways.component.ts | 156 + .../system/routes/gateways/item.component.ts | 207 + .../system/routes/gateways/table.component.ts | 29 + .../routes/general/general.component.ts | 47 +- .../system/routes/general/wipe.component.ts | 12 +- .../routes/password/password.component.ts | 15 +- .../routes/system/routes/proxies/constants.ts | 33 - .../system/routes/proxies/info.component.ts | 34 - .../routes/proxies/proxies.component.ts | 78 - .../system/routes/proxies/table.component.ts | 204 - .../system/routes/router/info.component.ts | 43 - .../system/routes/router/primary-ip.pipe.ts | 15 - .../system/routes/router/router.component.ts | 68 - .../system/routes/router/table.component.ts | 143 - .../routes/sessions/sessions.component.ts | 26 +- .../system/routes/sessions/table.component.ts | 8 +- .../routes/system/routes/ssh/ssh.component.ts | 78 +- .../system/routes/ssh/table.component.ts | 11 +- .../routes/startos-ui/startos-ui.component.ts | 65 +- .../system/routes/wifi/table.component.ts | 2 +- .../system/routes/wifi/wifi.component.ts | 4 +- .../portal/routes/system/system.component.ts | 6 +- .../portal/routes/system/system.const.ts | 28 +- .../portal/routes/system/system.routes.ts | 32 +- .../portal/routes/updates/item.component.ts | 54 +- .../ui/src/app/services/action.service.ts | 14 +- .../ui/src/app/services/api/api.fixures.ts | 121 +- .../ui/src/app/services/api/api.types.ts | 224 +- .../app/services/api/embassy-api.service.ts | 106 +- .../services/api/embassy-live-api.service.ts | 170 +- .../services/api/embassy-mock-api.service.ts | 438 +- .../ui/src/app/services/api/mock-patch.ts | 104 +- .../ui/src/app/services/config.service.ts | 191 +- .../ui/src/app/services/controls.service.ts | 2 +- .../ui/src/app/services/dep-error.service.ts | 53 +- .../ui/src/app/services/form.service.ts | 17 + .../ui/src/app/services/gateway.service.ts | 45 + .../src/app/services/marketplace.service.ts | 3 +- .../src/app/services/patch-db/data-model.ts | 21 +- .../services/pkg-status-rendering.service.ts | 8 +- .../ui/src/app/services/proxy.service.ts | 10 +- web/projects/ui/src/app/utils/acme.ts | 12 +- .../ui/src/app/utils/system-utilities.ts | 4 +- web/projects/ui/src/styles.scss | 30 +- 537 files changed, 19940 insertions(+), 20551 deletions(-) create mode 100755 core/build-tunnelbox.sh create mode 100644 core/builder-alias.sh create mode 100644 core/models/src/id/gateway.rs create mode 100644 core/startos/src/bins/tunnel.rs rename core/startos/src/{registry/auth.rs => middleware/signature.rs} (53%) rename core/startos/src/net/{network_interface.rs => gateway.rs} (53%) create mode 100644 core/startos/src/net/socks.rs create mode 100644 core/startos/src/net/tunnel.rs rename core/startos/src/registry/{signer/mod.rs => signer.rs} (96%) rename core/startos/src/{registry/signer => sign}/commitment/blake3.rs (96%) rename core/startos/src/{registry/signer => sign}/commitment/merkle_archive.rs (98%) rename core/startos/src/{registry/signer => sign}/commitment/mod.rs (100%) rename core/startos/src/{registry/signer => sign}/commitment/request.rs (98%) rename core/startos/src/{registry/signer => }/sign/ed25519.rs (94%) rename core/startos/src/{registry/signer => }/sign/mod.rs (98%) create mode 100644 core/startos/src/tunnel/api.rs create mode 100644 core/startos/src/tunnel/client.conf.template create mode 100644 core/startos/src/tunnel/context.rs create mode 100644 core/startos/src/tunnel/db.rs create mode 100644 core/startos/src/tunnel/forward.rs create mode 100644 core/startos/src/tunnel/mod.rs create mode 100644 core/startos/src/tunnel/server-peer.conf.template create mode 100644 core/startos/src/tunnel/server.conf.template create mode 100644 core/startos/src/tunnel/wg.rs create mode 100644 core/startos/src/util/collections/eq_set.rs create mode 100644 core/startos/src/util/iter.rs create mode 100644 core/startos/src/version/v0_4_0_alpha_10.rs rename sdk/base/lib/osBindings/{ForgetInterfaceParams.ts => AddTunnelParams.ts} (57%) create mode 100644 sdk/base/lib/osBindings/BindingGatewaySetEnabledParams.ts rename sdk/base/lib/osBindings/{NetworkInterfaceSetInboundParams.ts => DnsSettings.ts} (54%) create mode 100644 sdk/base/lib/osBindings/DomainSettings.ts rename sdk/base/lib/osBindings/{ProcedureId.ts => EventId.ts} (75%) create mode 100644 sdk/base/lib/osBindings/ForgetGatewayParams.ts rename sdk/base/lib/osBindings/{UnsetInboundParams.ts => GatewayId.ts} (66%) delete mode 100644 sdk/base/lib/osBindings/HostAddress.ts create mode 100644 sdk/base/lib/osBindings/Metadata.ts create mode 100644 sdk/base/lib/osBindings/MetadataSrc.ts rename sdk/base/lib/osBindings/{BindingSetPublicParams.ts => NetworkInterfaceSetPublicParams.ts} (54%) rename sdk/base/lib/osBindings/{DomainConfig.ts => PublicDomainConfig.ts} (55%) create mode 100644 sdk/base/lib/osBindings/RemoveTunnelParams.ts create mode 100644 sdk/base/lib/osBindings/RenameGatewayParams.ts create mode 100644 sdk/base/lib/osBindings/UnsetPublicParams.ts delete mode 100644 sdk/base/lib/util/Hostname.ts create mode 100644 sdk/base/lib/util/ip.ts delete mode 100644 system-images/README.md delete mode 100644 system-images/binfmt/.gitignore delete mode 100644 system-images/binfmt/Dockerfile delete mode 100644 system-images/binfmt/Makefile delete mode 100644 system-images/binfmt/manifest.json delete mode 100644 system-images/compat/.gitignore delete mode 100644 system-images/compat/Cargo.lock delete mode 100644 system-images/compat/Cargo.toml delete mode 100644 system-images/compat/Dockerfile delete mode 100644 system-images/compat/Makefile delete mode 100755 system-images/compat/build.sh delete mode 100644 system-images/compat/src/backup.rs delete mode 100644 system-images/compat/src/config/mod.rs delete mode 100644 system-images/compat/src/config/rule_parser.pest delete mode 100644 system-images/compat/src/config/rules.rs delete mode 100644 system-images/compat/src/main.rs delete mode 100644 system-images/utils/.gitignore delete mode 100644 system-images/utils/Dockerfile delete mode 100644 system-images/utils/Makefile create mode 100644 web/projects/shared/assets/img/service-icons/bitcoin-core.svg delete mode 100644 web/projects/shared/assets/img/service-icons/bitcoind.svg create mode 100644 web/projects/ui/src/app/routes/portal/components/form/containers/array.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/form/containers/control.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/form/containers/control.directive.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/form/containers/group.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/form/containers/object.component.ts rename web/projects/ui/src/app/routes/portal/components/form/{form-union/form-union.component.ts => containers/union.component.ts} (58%) delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/control.directive.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/control.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/form/controls/color.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/form/controls/control.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/form/controls/controls.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/form/controls/datetime.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/form/controls/file.component.ts rename web/projects/ui/src/app/routes/portal/components/form/{form-multiselect/form-multiselect.component.ts => controls/multiselect.component.ts} (57%) create mode 100644 web/projects/ui/src/app/routes/portal/components/form/controls/number.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/form/controls/select.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/form/controls/text.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/form/controls/textarea.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/form/controls/toggle.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-array/form-array.component.html delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-array/form-array.component.scss delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-array/form-array.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-color/form-color.component.html delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-color/form-color.component.scss delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-color/form-color.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-control/form-control.component.html delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-control/form-control.component.scss delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-control/form-control.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-control/form-control.providers.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-datetime/form-datetime.component.html delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-datetime/form-datetime.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-file/form-file.component.html delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-file/form-file.component.scss delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-file/form-file.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-group/form-group.component.html delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-group/form-group.component.scss delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-group/form-group.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-group/form-group.providers.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-multiselect/form-multiselect.component.html delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-number/form-number.component.html delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-number/form-number.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-object/form-object.component.html delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-object/form-object.component.scss delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-object/form-object.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-select/form-select.component.html delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-select/form-select.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-text/form-text.component.html delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-text/form-text.component.scss delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-text/form-text.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-textarea/form-textarea.component.html delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-textarea/form-textarea.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-toggle/form-toggle.component.html delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-toggle/form-toggle.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-union/form-union.component.html delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form-union/form-union.component.scss delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/form.module.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/form/invalid.service.ts rename web/projects/ui/src/app/routes/portal/components/form/{ => pipes}/filter-hidden.pipe.ts (95%) rename web/projects/ui/src/app/routes/portal/components/form/{ => pipes}/hint.pipe.ts (96%) rename web/projects/ui/src/app/routes/portal/components/form/{ => pipes}/mustache.pipe.ts (93%) delete mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/acme.pipe.ts rename web/projects/ui/src/app/routes/portal/components/interfaces/{ => addresses}/actions.component.ts (59%) create mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/addresses/addresses.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/addresses/item.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/clearnet.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/gateways.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/interface.service.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/interface.utils.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/local.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/private-domains.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/dns.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.item.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.service.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/status.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/tor-domains.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/components/interfaces/tor.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/logs/routes/tor.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/services/dashboard/services.service.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/acme/acme.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authorities.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authority.service.ts create mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/item.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/table.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/dns/dns.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/domains/constants.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/domains/domains.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/domains/info.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/domains/table.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/gateways.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/item.component.ts create mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/table.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/proxies/constants.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/proxies/info.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/proxies/proxies.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/proxies/table.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/router/info.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/router/primary-ip.pipe.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/router/router.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/system/routes/router/table.component.ts create mode 100644 web/projects/ui/src/app/services/gateway.service.ts diff --git a/.github/workflows/startos-iso.yaml b/.github/workflows/startos-iso.yaml index 839cc35a3..3732ac863 100644 --- a/.github/workflows/startos-iso.yaml +++ b/.github/workflows/startos-iso.yaml @@ -93,8 +93,18 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Configure sccache + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + - name: Make run: make ARCH=${{ matrix.arch }} compiled-${{ matrix.arch }}.tar + env: + SCCACHE_GHA_ENABLED: on + SCCACHE_GHA_VERSION: 0 - uses: actions/upload-artifact@v4 with: diff --git a/.gitignore b/.gitignore index 766d876e8..616604555 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,5 @@ .DS_Store .idea -system-images/binfmt/binfmt.tar -system-images/compat/compat.tar -system-images/util/util.tar /*.img /*.img.gz /*.img.xz diff --git a/Makefile b/Makefile index cdf40c7e2..786cc89ca 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +ls-files = $(shell git ls-files --cached --others --exclude-standard $1) +PROFILE = release + PLATFORM_FILE := $(shell ./check-platform.sh) ENVIRONMENT_FILE := $(shell ./check-environment.sh) GIT_HASH_FILE := $(shell ./check-git-hash.sh) @@ -9,23 +12,27 @@ IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo WEB_UIS := web/dist/raw/ui/index.html web/dist/raw/setup-wizard/index.html web/dist/raw/install-wizard/index.html COMPRESSED_WEB_UIS := web/dist/static/ui/index.html web/dist/static/setup-wizard/index.html web/dist/static/install-wizard/index.html FIRMWARE_ROMS := ./firmware/$(PLATFORM) $(shell jq --raw-output '.[] | select(.platform[] | contains("$(PLATFORM)")) | "./firmware/$(PLATFORM)/" + .id + ".rom.gz"' build/lib/firmware.json) -BUILD_SRC := $(shell git ls-files build) build/lib/depends build/lib/conflicts $(FIRMWARE_ROMS) -DEBIAN_SRC := $(shell git ls-files debian/) -IMAGE_RECIPE_SRC := $(shell git ls-files image-recipe/) +BUILD_SRC := $(call ls-files, build) build/lib/depends build/lib/conflicts $(FIRMWARE_ROMS) +DEBIAN_SRC := $(call ls-files, debian/) +IMAGE_RECIPE_SRC := $(call ls-files, image-recipe/) STARTD_SRC := core/startos/startd.service $(BUILD_SRC) -COMPAT_SRC := $(shell git ls-files system-images/compat/) -UTILS_SRC := $(shell git ls-files system-images/utils/) -BINFMT_SRC := $(shell git ls-files system-images/binfmt/) -CORE_SRC := $(shell git ls-files core) $(shell git ls-files --recurse-submodules patch-db) $(GIT_HASH_FILE) -WEB_SHARED_SRC := $(shell git ls-files web/projects/shared) $(shell git ls-files web/projects/marketplace) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist/index.js sdk/baseDist/package.json web/patchdb-ui-seed.json sdk/dist/package.json -WEB_UI_SRC := $(shell git ls-files web/projects/ui) -WEB_SETUP_WIZARD_SRC := $(shell git ls-files web/projects/setup-wizard) -WEB_INSTALL_WIZARD_SRC := $(shell git ls-files web/projects/install-wizard) +CORE_SRC := $(call ls-files, core) $(shell git ls-files --recurse-submodules patch-db) $(GIT_HASH_FILE) +WEB_SHARED_SRC := $(call ls-files, web/projects/shared) $(call ls-files, web/projects/marketplace) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist/index.js sdk/baseDist/package.json web/patchdb-ui-seed.json sdk/dist/package.json +WEB_UI_SRC := $(call ls-files, web/projects/ui) +WEB_SETUP_WIZARD_SRC := $(call ls-files, web/projects/setup-wizard) +WEB_INSTALL_WIZARD_SRC := $(call ls-files, web/projects/install-wizard) PATCH_DB_CLIENT_SRC := $(shell git ls-files --recurse-submodules patch-db/client) GZIP_BIN := $(shell which pigz || which gzip) TAR_BIN := $(shell which gtar || which tar) -COMPILED_TARGETS := core/target/$(ARCH)-unknown-linux-musl/release/startbox core/target/$(ARCH)-unknown-linux-musl/release/containerbox system-images/compat/docker-images/$(ARCH).tar system-images/utils/docker-images/$(ARCH).tar system-images/binfmt/docker-images/$(ARCH).tar container-runtime/rootfs.$(ARCH).squashfs -ALL_TARGETS := $(STARTD_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) $(COMPILED_TARGETS) cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo cargo-deps/aarch64-unknown-linux-musl/release/pi-beep; fi) $(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]; then echo cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console; fi') $(PLATFORM_FILE) +COMPILED_TARGETS := core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox core/target/$(ARCH)-unknown-linux-musl/release/containerbox container-runtime/rootfs.$(ARCH).squashfs +ALL_TARGETS := $(STARTD_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) $(COMPILED_TARGETS) cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs $(PLATFORM_FILE) \ + $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then \ + echo cargo-deps/aarch64-unknown-linux-musl/release/pi-beep; \ + fi) \ + $(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]; then \ + echo cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console; \ + echo cargo-deps/$(ARCH)-unknown-linux-musl/release/flamegraph; \ + fi') REBUILD_TYPES = 1 ifeq ($(REMOTE),) @@ -59,8 +66,6 @@ touch: metadata: $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) clean: - rm -f system-images/**/*.tar - rm -rf system-images/compat/target rm -rf core/target rm -rf core/startos/bindings rm -rf web/.angular @@ -95,17 +100,20 @@ test: | test-core test-sdk test-container-runtime test-core: $(CORE_SRC) $(ENVIRONMENT_FILE) ./core/run-tests.sh -test-sdk: $(shell git ls-files sdk) sdk/base/lib/osBindings/index.ts +test-sdk: $(call ls-files, sdk) sdk/base/lib/osBindings/index.ts cd sdk && make test -test-container-runtime: container-runtime/node_modules/.package-lock.json $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json +test-container-runtime: container-runtime/node_modules/.package-lock.json $(call ls-files, container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json cd container-runtime && npm test cli: - cd core && ./install-cli.sh + ./core/install-cli.sh registry: - cd core && ./build-registrybox.sh + ./core/build-registrybox.sh + +tunnel: + ./core/build-tunnelbox.sh deb: results/$(BASENAME).deb @@ -126,12 +134,14 @@ results/$(BASENAME).$(IMAGE_TYPE) results/$(BASENAME).squashfs: $(IMAGE_RECIPE_S install: $(ALL_TARGETS) $(call mkdir,$(DESTDIR)/usr/bin) $(call mkdir,$(DESTDIR)/usr/sbin) - $(call cp,core/target/$(ARCH)-unknown-linux-musl/release/startbox,$(DESTDIR)/usr/bin/startbox) + $(call cp,core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox,$(DESTDIR)/usr/bin/startbox) $(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/startd) $(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-cli) - $(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-sdk) if [ "$(PLATFORM)" = "raspberrypi" ]; then $(call cp,cargo-deps/aarch64-unknown-linux-musl/release/pi-beep,$(DESTDIR)/usr/bin/pi-beep); fi - if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]'; then $(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); fi + if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]'; then \ + $(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); \ + $(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/flamegraph,$(DESTDIR)/usr/bin/flamegraph); \ + fi $(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs,$(DESTDIR)/usr/bin/startos-backup-fs) $(call ln,/usr/bin/startos-backup-fs,$(DESTDIR)/usr/sbin/mount.backup-fs) @@ -149,10 +159,6 @@ install: $(ALL_TARGETS) $(call cp,GIT_HASH.txt,$(DESTDIR)/usr/lib/startos/GIT_HASH.txt) $(call cp,VERSION.txt,$(DESTDIR)/usr/lib/startos/VERSION.txt) - $(call mkdir,$(DESTDIR)/usr/lib/startos/system-images) - $(call cp,system-images/compat/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/compat.tar) - $(call cp,system-images/utils/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/utils.tar) - $(call cp,firmware/$(PLATFORM),$(DESTDIR)/usr/lib/startos/firmware) update-overlay: $(ALL_TARGETS) @@ -164,10 +170,10 @@ update-overlay: $(ALL_TARGETS) $(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) PLATFORM=$(PLATFORM) $(call ssh,"sudo systemctl start startd") -wormhole: core/target/$(ARCH)-unknown-linux-musl/release/startbox +wormhole: core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox @echo "Paste the following command into the shell of your StartOS server:" @echo - @wormhole send core/target/$(ARCH)-unknown-linux-musl/release/startbox 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo /usr/lib/startos/scripts/chroot-and-upgrade \"cd /usr/bin && rm startbox && wormhole receive --accept-file %s && chmod +x startbox\"\n", $$3 }' + @wormhole send core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo /usr/lib/startos/scripts/chroot-and-upgrade \"cd /usr/bin && rm startbox && wormhole receive --accept-file %s && chmod +x startbox\"\n", $$3 }' wormhole-deb: results/$(BASENAME).deb @echo "Paste the following command into the shell of your StartOS server:" @@ -187,10 +193,10 @@ update: $(ALL_TARGETS) $(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/startos/next PLATFORM=$(PLATFORM) $(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync "apt-get install -y $(shell cat ./build/lib/depends)"') -update-startbox: core/target/$(ARCH)-unknown-linux-musl/release/startbox # only update binary (faster than full update) +update-startbox: core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox # only update binary (faster than full update) @if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi $(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create') - $(call cp,core/target/$(ARCH)-unknown-linux-musl/release/startbox,/media/startos/next/usr/bin/startbox) + $(call cp,core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox,/media/startos/next/usr/bin/startbox) $(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync true') update-deb: results/$(BASENAME).deb # better than update, but only available from debian @@ -235,20 +241,20 @@ sdk/base/lib/osBindings/index.ts: $(shell if [ "$(REBUILD_TYPES)" -ne 0 ]; then rsync -ac --delete core/startos/bindings/ sdk/base/lib/osBindings/ touch sdk/base/lib/osBindings/index.ts -core/startos/bindings/index.ts: $(shell git ls-files core) $(ENVIRONMENT_FILE) +core/startos/bindings/index.ts: $(call ls-files, core) $(ENVIRONMENT_FILE) rm -rf core/startos/bindings ./core/build-ts.sh ls core/startos/bindings/*.ts | sed 's/core\/startos\/bindings\/\([^.]*\)\.ts/export { \1 } from ".\/\1";/g' | grep -v '"./index"' | tee core/startos/bindings/index.ts npm --prefix sdk exec -- prettier --config ./sdk/base/package.json -w ./core/startos/bindings/*.ts touch core/startos/bindings/index.ts -sdk/dist/package.json sdk/baseDist/package.json: $(shell git ls-files sdk) sdk/base/lib/osBindings/index.ts +sdk/dist/package.json sdk/baseDist/package.json: $(call ls-files, sdk) sdk/base/lib/osBindings/index.ts (cd sdk && make bundle) touch sdk/dist/package.json touch sdk/baseDist/package.json # TODO: make container-runtime its own makefile? -container-runtime/dist/index.js: container-runtime/node_modules/.package-lock.json $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json +container-runtime/dist/index.js: container-runtime/node_modules/.package-lock.json $(call ls-files, container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json npm --prefix container-runtime run build container-runtime/dist/node_modules/.package-lock.json container-runtime/dist/package.json container-runtime/dist/package-lock.json: container-runtime/package.json container-runtime/package-lock.json sdk/dist/package.json container-runtime/install-dist-deps.sh @@ -258,24 +264,15 @@ container-runtime/dist/node_modules/.package-lock.json container-runtime/dist/pa container-runtime/rootfs.$(ARCH).squashfs: container-runtime/debian.$(ARCH).squashfs container-runtime/container-runtime.service container-runtime/update-image.sh container-runtime/deb-install.sh container-runtime/dist/index.js container-runtime/dist/node_modules/.package-lock.json core/target/$(ARCH)-unknown-linux-musl/release/containerbox ARCH=$(ARCH) REQUIRES=linux ./build/os-compat/run-compat.sh ./container-runtime/update-image.sh -build/lib/depends build/lib/conflicts: build/dpkg-deps/* +build/lib/depends build/lib/conflicts: $(ENVIRONMENT_FILE) build/dpkg-deps/* build/dpkg-deps/generate.sh $(FIRMWARE_ROMS): build/lib/firmware.json download-firmware.sh $(PLATFORM_FILE) ./download-firmware.sh $(PLATFORM) -system-images/compat/docker-images/$(ARCH).tar: $(COMPAT_SRC) - cd system-images/compat && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar - -system-images/utils/docker-images/$(ARCH).tar: $(UTILS_SRC) - cd system-images/utils && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar - -system-images/binfmt/docker-images/$(ARCH).tar: $(BINFMT_SRC) - cd system-images/binfmt && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar - -core/target/$(ARCH)-unknown-linux-musl/release/startbox: $(CORE_SRC) $(COMPRESSED_WEB_UIS) web/patchdb-ui-seed.json $(ENVIRONMENT_FILE) - ARCH=$(ARCH) ./core/build-startbox.sh - touch core/target/$(ARCH)-unknown-linux-musl/release/startbox +core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox: $(CORE_SRC) $(COMPRESSED_WEB_UIS) web/patchdb-ui-seed.json $(ENVIRONMENT_FILE) + ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build-startbox.sh + touch core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox core/target/$(ARCH)-unknown-linux-musl/release/containerbox: $(CORE_SRC) $(ENVIRONMENT_FILE) ARCH=$(ARCH) ./core/build-containerbox.sh @@ -339,3 +336,6 @@ cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console: cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs: ARCH=$(ARCH) PREINSTALL="apk add fuse3 fuse3-dev fuse3-static musl-dev pkgconfig" ./build-cargo-dep.sh --git https://github.com/Start9Labs/start-fs.git startos-backup-fs + +cargo-deps/$(ARCH)-unknown-linux-musl/release/flamegraph: + ARCH=$(ARCH) PREINSTALL="apk add musl-dev pkgconfig" ./build-cargo-dep.sh flamegraph \ No newline at end of file diff --git a/build/dpkg-deps/generate.sh b/build/dpkg-deps/generate.sh index 53a7b87e5..6aafeefc3 100755 --- a/build/dpkg-deps/generate.sh +++ b/build/dpkg-deps/generate.sh @@ -8,8 +8,8 @@ IFS="-" read -ra FEATURES <<< "$ENVIRONMENT" feature_file_checker=' /^#/ { next } -/^\+ [a-z0-9]+$/ { next } -/^- [a-z0-9]+$/ { next } +/^\+ [a-z0-9-]+$/ { next } +/^- [a-z0-9-]+$/ { next } { exit 1 } ' diff --git a/build/dpkg-deps/unstable.depends b/build/dpkg-deps/unstable.depends index fe5dee256..ebf3b58b4 100644 --- a/build/dpkg-deps/unstable.depends +++ b/build/dpkg-deps/unstable.depends @@ -1,2 +1,3 @@ + gdb -+ heaptrack \ No newline at end of file ++ heaptrack ++ linux-perf \ No newline at end of file diff --git a/container-runtime/container-runtime-failure.service b/container-runtime/container-runtime-failure.service index 295132bab..78b422243 100644 --- a/container-runtime/container-runtime-failure.service +++ b/container-runtime/container-runtime-failure.service @@ -3,4 +3,4 @@ Description=StartOS Container Runtime Failure Handler [Service] Type=oneshot -ExecStart=/usr/bin/start-cli rebuild \ No newline at end of file +ExecStart=/usr/bin/start-container rebuild \ No newline at end of file diff --git a/container-runtime/deb-install.sh b/container-runtime/deb-install.sh index 2bf638258..688bd1003 100644 --- a/container-runtime/deb-install.sh +++ b/container-runtime/deb-install.sh @@ -2,6 +2,7 @@ set -e + mkdir -p /run/systemd/resolve echo "nameserver 8.8.8.8" > /run/systemd/resolve/stub-resolv.conf @@ -13,6 +14,7 @@ source ~/.bashrc nvm install 22 ln -s $(which node) /usr/bin/node +sed -i '/\(^\|#\)DNSStubListener=/c\DNSStubListener=no' /etc/systemd/resolved.conf sed -i '/\(^\|#\)Storage=/c\Storage=persistent' /etc/systemd/journald.conf sed -i '/\(^\|#\)Compress=/c\Compress=yes' /etc/systemd/journald.conf sed -i '/\(^\|#\)SystemMaxUse=/c\SystemMaxUse=1G' /etc/systemd/journald.conf @@ -20,4 +22,7 @@ sed -i '/\(^\|#\)ForwardToSyslog=/c\ForwardToSyslog=no' /etc/systemd/journald.co systemctl enable container-runtime.service -rm -rf /run/systemd \ No newline at end of file +rm -rf /run/systemd + +rm /etc/resolv.conf +echo "nameserver 10.0.3.1" > /etc/resolv.conf \ No newline at end of file diff --git a/container-runtime/package-lock.json b/container-runtime/package-lock.json index 39ac8e2d9..c0a97dc6d 100644 --- a/container-runtime/package-lock.json +++ b/container-runtime/package-lock.json @@ -38,7 +38,7 @@ }, "../sdk/dist": { "name": "@start9labs/start-sdk", - "version": "0.4.0-beta.36", + "version": "0.4.0-beta.37", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", diff --git a/container-runtime/src/Adapters/EffectCreator.ts b/container-runtime/src/Adapters/EffectCreator.ts index 6512b0d6c..c549a28ab 100644 --- a/container-runtime/src/Adapters/EffectCreator.ts +++ b/container-runtime/src/Adapters/EffectCreator.ts @@ -35,13 +35,13 @@ const SOCKET_PATH = "/media/startos/rpc/host.sock" let hostSystemId = 0 export type EffectContext = { - procedureId: string | null + eventId: string | null callbacks?: CallbackHolder constRetry?: () => void } const rpcRoundFor = - (procedureId: string | null) => + (eventId: string | null) => ( method: K, params: Record, @@ -52,7 +52,7 @@ const rpcRoundFor = JSON.stringify({ id, method, - params: { ...params, procedureId: procedureId || undefined }, + params: { ...params, eventId: eventId ?? undefined }, }) + "\n", ) }) @@ -103,8 +103,9 @@ const rpcRoundFor = } export function makeEffects(context: EffectContext): Effects { - const rpcRound = rpcRoundFor(context.procedureId) + const rpcRound = rpcRoundFor(context.eventId) const self: Effects = { + eventId: context.eventId, child: (name) => makeEffects({ ...context, callbacks: context.callbacks?.child(name) }), constRetry: context.constRetry, diff --git a/container-runtime/src/Adapters/RpcListener.ts b/container-runtime/src/Adapters/RpcListener.ts index 8033478da..e8876aea1 100644 --- a/container-runtime/src/Adapters/RpcListener.ts +++ b/container-runtime/src/Adapters/RpcListener.ts @@ -242,11 +242,11 @@ export class RpcListener { .when(runType, async ({ id, params }) => { const system = this.system const procedure = jsonPath.unsafeCast(params.procedure) - const { input, timeout, id: procedureId } = params + const { input, timeout, id: eventId } = params const result = this.getResult( procedure, system, - procedureId, + eventId, timeout, input, ) @@ -256,11 +256,11 @@ export class RpcListener { .when(sandboxRunType, async ({ id, params }) => { const system = this.system const procedure = jsonPath.unsafeCast(params.procedure) - const { input, timeout, id: procedureId } = params + const { input, timeout, id: eventId } = params const result = this.getResult( procedure, system, - procedureId, + eventId, timeout, input, ) @@ -275,7 +275,7 @@ export class RpcListener { const callbacks = this.callbacks?.getChild("main") || this.callbacks?.child("main") const effects = makeEffects({ - procedureId: null, + eventId: null, callbacks, }) return handleRpc( @@ -304,7 +304,7 @@ export class RpcListener { } await this._system.exit( makeEffects({ - procedureId: params.id, + eventId: params.id, }), target, ) @@ -320,14 +320,14 @@ export class RpcListener { const system = await this.getDependencies.system() this.callbacks = new CallbackHolder( makeEffects({ - procedureId: params.id, + eventId: params.id, }), ) const callbacks = this.callbacks.child("init") console.error("Initializing...") await system.init( makeEffects({ - procedureId: params.id, + eventId: params.id, callbacks, }), params.kind, @@ -399,7 +399,7 @@ export class RpcListener { private getResult( procedure: typeof jsonPath._TYPE, system: System, - procedureId: string, + eventId: string, timeout: number | null | undefined, input: any, ) { @@ -410,7 +410,7 @@ export class RpcListener { } const callbacks = this.callbacks?.child(procedure) const effects = makeEffects({ - procedureId, + eventId, callbacks, }) diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index 05ff5a427..7a2de376b 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts @@ -509,13 +509,18 @@ export class SystemForEmbassy implements System { ): Promise { if (actionId === "config") { const config = await this.getConfig(effects, timeoutMs) - return { spec: config.spec, value: config.config } + return { + eventId: effects.eventId!, + spec: config.spec, + value: config.config, + } } else if (actionId === "properties") { return null } else { const oldSpec = this.manifest.actions?.[actionId]?.["input-spec"] if (!oldSpec) return null return { + eventId: effects.eventId!, spec: transformConfigSpec(oldSpec as OldConfigSpec), value: null, } @@ -1233,14 +1238,14 @@ async function updateConfig( const url: string = filled === null || filled.addressInfo === null ? "" - : catchFn(() => - utils.hostnameInfoToAddress( - specValue.target === "lan-address" + : catchFn( + () => + (specValue.target === "lan-address" ? filled.addressInfo!.localHostnames[0] || - filled.addressInfo!.onionHostnames[0] + filled.addressInfo!.onionHostnames[0] : filled.addressInfo!.onionHostnames[0] || - filled.addressInfo!.localHostnames[0], - ), + filled.addressInfo!.localHostnames[0] + ).hostname.value, ) || "" mutConfigValue[key] = url } diff --git a/container-runtime/update-image.sh b/container-runtime/update-image.sh index f1f28f896..0a8ca4ec9 100755 --- a/container-runtime/update-image.sh +++ b/container-runtime/update-image.sh @@ -39,8 +39,10 @@ sudo cp container-runtime.service tmp/combined/lib/systemd/system/container-runt sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime.service sudo cp container-runtime-failure.service tmp/combined/lib/systemd/system/container-runtime-failure.service sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime-failure.service -sudo cp ../core/target/$ARCH-unknown-linux-musl/release/containerbox tmp/combined/usr/bin/start-cli -sudo chown 0:0 tmp/combined/usr/bin/start-cli +sudo cp ../core/target/$ARCH-unknown-linux-musl/release/containerbox tmp/combined/usr/bin/start-container +echo -e '#!/bin/bash\nexec start-container "$@"' | sudo tee tmp/combined/usr/bin/start-cli # TODO: remove +sudo chmod +x tmp/combined/usr/bin/start-cli +sudo chown 0:0 tmp/combined/usr/bin/start-container echo container-runtime | sha256sum | head -c 32 | cat - <(echo) | sudo tee tmp/combined/etc/machine-id cat deb-install.sh | sudo systemd-nspawn --console=pipe -D tmp/combined $QEMU /bin/bash sudo truncate -s 0 tmp/combined/etc/machine-id diff --git a/core/Cargo.lock b/core/Cargo.lock index 4078946be..fc37c5d09 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" - [[package]] name = "addr2line" version = "0.24.2" @@ -32,7 +26,7 @@ dependencies = [ "cfg-if", "cipher 0.3.0", "cpufeatures", - "ctr", + "ctr 0.8.0", "opaque-debug", ] @@ -45,6 +39,7 @@ dependencies = [ "cfg-if", "cipher 0.4.4", "cpufeatures", + "zeroize", ] [[package]] @@ -101,6 +96,52 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "amplify" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f7fb4ac7c881e54a8e7015e399b6112a2a5bc958b6c89ac510840ff20273b31" +dependencies = [ + "amplify_derive", + "amplify_num", + "ascii", + "getrandom 0.2.16", + "getrandom 0.3.3", + "wasm-bindgen", +] + +[[package]] +name = "amplify_derive" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a6309e6b8d89b36b9f959b7a8fa093583b94922a0f6438a24fb08936de4d428" +dependencies = [ + "amplify_syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "amplify_num" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99bcb75a2982047f733547042fc3968c0f460dfcf7d90b90dea3b2744580e9ad" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "amplify_syn" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7736fb8d473c0d83098b5bac44df6a561e20470375cd8bcae30516dc889fd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -117,10 +158,25 @@ dependencies = [ ] [[package]] -name = "anstream" -version = "0.6.19" +name = "anes" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "ansi-width" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219e3ce6f2611d83b51ec2098a12702112c29e57203a6b0a0929b2cddb486608" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "anstream" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -148,39 +204,45 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ "derive_arbitrary", ] +[[package]] +name = "archery" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae2ed21cd55021f05707a807a5fc85695dafb98832921f6cfa06db67ca5b869" + [[package]] name = "arrayref" version = "0.3.9" @@ -199,6 +261,62 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "arti-client" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "async-trait", + "cfg-if", + "derive-deftly 1.2.0", + "derive_builder_fork_arti", + "derive_more", + "educe", + "fs-mistrust", + "futures", + "hostname-validator", + "humantime", + "humantime-serde", + "libc", + "once_cell", + "postage", + "rand 0.9.2", + "safelog", + "serde", + "thiserror 2.0.16", + "time", + "tor-async-utils", + "tor-basic-utils", + "tor-chanmgr", + "tor-circmgr", + "tor-config", + "tor-config-path", + "tor-dirmgr", + "tor-error", + "tor-guardmgr", + "tor-hsclient", + "tor-hscrypto", + "tor-hsservice", + "tor-keymgr", + "tor-linkspec", + "tor-llcrypto", + "tor-memquota", + "tor-netdir", + "tor-netdoc", + "tor-persist", + "tor-proto", + "tor-protover", + "tor-rtcompat", + "tracing", + "void", +] + +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + [[package]] name = "ascii-canvas" version = "3.0.0" @@ -214,7 +332,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" dependencies = [ - "asn1-rs-derive", + "asn1-rs-derive 0.5.1", "asn1-rs-impl", "displaydoc", "nom 7.1.3", @@ -224,6 +342,21 @@ dependencies = [ "time", ] +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive 0.6.0", + "asn1-rs-impl", + "displaydoc", + "nom 7.1.3", + "num-traits", + "rusticata-macros", + "thiserror 2.0.16", +] + [[package]] name = "asn1-rs-derive" version = "0.5.1" @@ -232,7 +365,19 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", + "synstructure", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", "synstructure", ] @@ -244,9 +389,15 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + [[package]] name = "async-acme" version = "0.6.0" @@ -260,7 +411,7 @@ dependencies = [ "pem", "rcgen", "ring", - "rustls 0.23.29", + "rustls 0.23.31", "rustls-pemfile 2.2.0", "serde", "serde_json", @@ -275,7 +426,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.4.1", "event-listener-strategy", "futures-core", "pin-project-lite", @@ -306,16 +457,20 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.25" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40f6024f3f856663b45fd0c9b6f2024034a702f453549449e0d84a305900dad4" +checksum = "06575e6a9673580f52661c92107baabffbf41e2141373441cbcdc47cb733003c" dependencies = [ "brotli", "flate2", "futures-core", + "futures-io", "memchr", "pin-project-lite", "tokio", + "xz2", + "zstd", + "zstd-safe", ] [[package]] @@ -333,10 +488,25 @@ dependencies = [ ] [[package]] -name = "async-io" +name = "async-global-executor" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" dependencies = [ "async-lock", "cfg-if", @@ -345,28 +515,39 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.0.7", + "rustix 1.0.8", "slab", - "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "async-lock" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.4.1", "event-listener-strategy", "pin-project-lite", ] [[package]] -name = "async-process" -version = "2.3.1" +name = "async-native-tls" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" +checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" +dependencies = [ + "futures-util", + "native-tls", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "async-process" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00" dependencies = [ "async-channel 2.5.0", "async-io", @@ -375,10 +556,9 @@ dependencies = [ "async-task", "blocking", "cfg-if", - "event-listener 5.4.0", + "event-listener 5.4.1", "futures-lite", - "rustix 1.0.7", - "tracing", + "rustix 1.0.8", ] [[package]] @@ -389,14 +569,14 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "async-signal" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" +checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1" dependencies = [ "async-io", "async-lock", @@ -404,10 +584,37 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.0.7", + "rustix 1.0.8", "signal-hook-registry", "slab", - "windows-sys 0.59.0", + "windows-sys 0.60.2", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", ] [[package]] @@ -429,7 +636,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -440,13 +647,42 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", +] + +[[package]] +name = "async_executors" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a982d2f86de6137cc05c9db9a915a19886c97911f9790d04f174cede74be01a5" +dependencies = [ + "async-std", + "blanket", + "futures-core", + "futures-task", + "futures-util", + "pin-project", + "rustc_version", + "tokio", +] + +[[package]] +name = "asynchronous-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", ] [[package]] @@ -458,6 +694,21 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -472,9 +723,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08b5d4e069cbc868041a64bd68dc8cb39a0d79585cd6c5a24caa8c2d622121be" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" dependencies = [ "aws-lc-sys", "zeroize", @@ -607,7 +858,7 @@ dependencies = [ "flate2", "lz4_flex", "solana-nohash-hasher", - "thiserror 2.0.12", + "thiserror 2.0.16", "tracing", "xxhash-rust", "xz2", @@ -647,12 +898,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base32" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" - [[package]] name = "base32" version = "0.5.1" @@ -723,7 +968,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "cexpr", "clang-sys", "itertools 0.12.1", @@ -736,7 +981,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.104", + "syn 2.0.106", "which", ] @@ -778,12 +1023,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" -dependencies = [ - "serde", -] +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "bitmaps" @@ -815,6 +1057,15 @@ dependencies = [ "wyz 0.5.1", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "blake2b_simd" version = "1.0.3" @@ -841,6 +1092,17 @@ dependencies = [ "rayon-core", ] +[[package]] +name = "blanket" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0b121a9fe0df916e362fb3271088d071159cdf11db0e4182d02152850756eff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -873,10 +1135,16 @@ dependencies = [ ] [[package]] -name = "brotli" -version = "8.0.1" +name = "bounded-vec-deque" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +checksum = "2225b558afc76c596898f5f1b3fc35cfce0eb1b13635cbd7d1b2a7177dc10ccd" + +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -885,14 +1153,25 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.0" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "regex-automata 0.4.10", + "serde", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -900,10 +1179,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] -name = "bytemuck" -version = "1.23.1" +name = "by_address" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytemuck" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" [[package]] name = "byteorder" @@ -949,10 +1234,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21" [[package]] -name = "cc" -version = "1.2.29" +name = "caret" +version = "0.5.3" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" + +[[package]] +name = "cast" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ "jobserver", "libc", @@ -970,9 +1266,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -1048,6 +1344,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -1063,9 +1360,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.41" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" dependencies = [ "clap_builder", "clap_derive", @@ -1073,9 +1370,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.41" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" dependencies = [ "anstream", "anstyle", @@ -1085,14 +1382,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.41" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1110,6 +1407,17 @@ dependencies = [ "cc", ] +[[package]] +name = "coarsetime" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4" +dependencies = [ + "libc", + "wasix", + "wasm-bindgen", +] + [[package]] name = "color-eyre" version = "0.6.5" @@ -1245,12 +1553,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "convert_case" version = "0.6.0" @@ -1280,6 +1582,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" +dependencies = [ + "futures", +] + [[package]] name = "cookie_store" version = "0.21.1" @@ -1288,7 +1599,7 @@ checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" dependencies = [ "cookie", "document-features", - "idna 1.0.3", + "idna", "log", "publicsuffix", "serde", @@ -1340,13 +1651,65 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-cycles-per-byte" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1029452fa751c93f8834962dd74807d69f0a6c7624d5b06625b393aeb6a14fc2" +dependencies = [ + "cfg-if", + "criterion", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -1396,14 +1759,14 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "crossterm_winapi", - "derive_more 2.0.1", + "derive_more", "document-features", "futures-core", "mio", "parking_lot", - "rustix 1.0.7", + "rustix 1.0.8", "signal-hook", "signal-hook-mio", "winapi", @@ -1476,6 +1839,15 @@ dependencies = [ "cipher 0.3.0", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1491,9 +1863,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.2.0" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373b7c5dbd637569a2cca66e8d66b8c446a1e7bf064ea321d265d7b3dfe7c97e" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", @@ -1513,7 +1885,17 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", ] [[package]] @@ -1522,8 +1904,22 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", ] [[package]] @@ -1537,7 +1933,18 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.104", + "syn 2.0.106", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote", + "syn 1.0.109", ] [[package]] @@ -1546,9 +1953,9 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1581,11 +1988,11 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58cb0719583cbe4e81fb40434ace2f0d22ccc3e39a74bb3796c22b451b4f139d" dependencies = [ - "darling", + "darling 0.20.11", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1606,7 +2013,7 @@ version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ - "asn1-rs", + "asn1-rs 0.6.2", "displaydoc", "nom 7.1.3", "num-bigint", @@ -1614,6 +2021,20 @@ dependencies = [ "rusticata-macros", ] +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs 0.7.1", + "cookie-factory", + "displaydoc", + "nom 7.1.3", + "num-traits", + "rusticata-macros", +] + [[package]] name = "der_derive" version = "0.7.3" @@ -1622,7 +2043,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1636,27 +2057,101 @@ dependencies = [ ] [[package]] -name = "derive_arbitrary" -version = "1.4.1" +name = "derive-deftly" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "e8ea84d0109517cc2253d4a679bdda1e8989e9bd86987e9e4f75ffdda0095fd1" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", + "derive-deftly-macros 0.14.6", + "heck 0.5.0", ] [[package]] -name = "derive_more" -version = "0.99.20" +name = "derive-deftly" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +checksum = "957bb73a3a9c0bbcac67e129b81954661b3cfcb9e28873d8441f91b54852e77a" dependencies = [ - "convert_case 0.4.0", + "derive-deftly-macros 1.2.0", + "heck 0.5.0", +] + +[[package]] +name = "derive-deftly-macros" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357422a457ccb850dc8f1c1680e0670079560feaad6c2e247e3f345c4fab8a3f" +dependencies = [ + "heck 0.5.0", + "indexmap 2.11.0", + "itertools 0.14.0", + "proc-macro-crate", "proc-macro2", "quote", - "rustc_version", - "syn 2.0.104", + "sha3", + "strum", + "syn 2.0.106", + "void", +] + +[[package]] +name = "derive-deftly-macros" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea41269bd490d251b9eca50ccb43117e641cc68b129849757c15ece88fe0574" +dependencies = [ + "heck 0.5.0", + "indexmap 2.11.0", + "itertools 0.14.0", + "proc-macro-crate", + "proc-macro2", + "quote", + "sha3", + "strum", + "syn 2.0.106", + "void", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "derive_builder_core_fork_arti" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24c1b715c79be6328caa9a5e1a387a196ea503740f0722ec3dd8f67a9e72314d" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_fork_arti" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3eae24d595f4d0ecc90a9a5a6d11c2bd8dafe2375ec4a1ec63250e5ade7d228" +dependencies = [ + "derive_builder_macro_fork_arti", +] + +[[package]] +name = "derive_builder_macro_fork_arti" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69887769a2489cd946bf782eb2b1bb2cb7bc88551440c94a765d4f040c08ebf3" +dependencies = [ + "derive_builder_core_fork_arti", + "syn 1.0.109", ] [[package]] @@ -1677,7 +2172,8 @@ dependencies = [ "convert_case 0.7.1", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", + "unicode-xid", ] [[package]] @@ -1701,6 +2197,24 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -1711,6 +2225,18 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.2", + "windows-sys 0.60.2", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -1718,7 +2244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.6", "winapi", ] @@ -1730,7 +2256,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1739,6 +2265,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69dde51e8fef5e12c1d65e0929b03d66e4c0c18282bc30ed2ca050ad6f44dd82" +[[package]] +name = "dns-lookup" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5597a4b7fe5275fc9dcf88ce26326bc8e4cb87d0130f33752d4c5f717793cf" +dependencies = [ + "cfg-if", + "libc", + "socket2 0.6.0", + "windows-sys 0.60.2", +] + [[package]] name = "document-features" version = "0.2.11" @@ -1755,13 +2293,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] -name = "drain" -version = "0.1.2" +name = "downcast-rs" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d105028bd2b5dfcb33318fd79a445001ead36004dd8dffef1bdd7e493d8bc1e" -dependencies = [ - "tokio", -] +checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf" [[package]] name = "dunce" @@ -1771,9 +2306,36 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "dyn-clone" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "dynasm" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7d4c414c94bc830797115b8e5f434d58e7e80cb42ba88508c14bc6ea270625" +dependencies = [ + "bitflags 2.9.3", + "byteorder", + "lazy_static", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "dynasmrt" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602f7458a3859195fb840e6e0cce5f4330dd9dfbfece0edaf31fe427af346f55" +dependencies = [ + "byteorder", + "dynasm", + "fnv", + "memmap2", +] [[package]] name = "ecdsa" @@ -1829,8 +2391,9 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ - "curve25519-dalek 4.2.0", + "curve25519-dalek 4.1.3", "ed25519 2.2.3", + "merlin", "rand_core 0.6.4", "serde", "sha2 0.10.9", @@ -1839,6 +2402,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "educe" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0042ff8246a363dbe77d2ceedb073339e85a804b9a47636c6e016a9a32c05f" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "either" version = "1.15.0" @@ -1910,6 +2485,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "enum-as-inner" version = "0.6.1" @@ -1919,7 +2500,20 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", +] + +[[package]] +name = "enum-ordinalize" +version = "3.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf1fa3f06bbff1ea5b1a9c7b14aa992a39657db60a2759457328d7e058f49ee" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] @@ -1940,7 +2534,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1949,6 +2543,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "equix" +version = "0.2.5" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "arrayvec 0.7.6", + "hashx", + "num-traits", + "thiserror 2.0.16", + "visibility", +] + [[package]] name = "errno" version = "0.2.8" @@ -1999,9 +2605,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue 2.5.0", "parking", @@ -2014,7 +2620,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.4.1", "pin-project-lite", ] @@ -2046,6 +2652,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "2.3.0" @@ -2073,22 +2691,41 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.3.0" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "figment" +version = "0.10.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic 0.6.1", + "serde", + "toml 0.8.23", + "uncased", + "version_check", +] [[package]] name = "filetime" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] +[[package]] +name = "fixed-capacity-vec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b31a14f5ee08ed1a40e1252b35af18bed062e3f39b69aab34decde36bc43e40" + [[package]] name = "fixedbitset" version = "0.4.2" @@ -2107,15 +2744,10 @@ dependencies = [ ] [[package]] -name = "flume" -version = "0.11.1" +name = "fluid-let" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] +checksum = "749cff877dc1af878a0b31a41dd221a753634401ea0ef2f87b62d3171522485a" [[package]] name = "fnv" @@ -2146,9 +2778,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -2162,12 +2794,56 @@ dependencies = [ "itertools 0.8.2", ] +[[package]] +name = "fs-mistrust" +version = "0.10.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "derive_builder_fork_arti", + "dirs", + "libc", + "pwd-grp", + "serde", + "thiserror 2.0.16", + "walkdir", +] + [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "fslock" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "fslock-arti-fork" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b21bd626aaab7b904b20bef6d9e06298914a0c8d9fb8b010483766b2e532791" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "fslock-guard" +version = "0.2.4" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "fslock-arti-fork", + "thiserror 2.0.16", + "winapi", +] + [[package]] name = "funty" version = "1.1.0" @@ -2241,9 +2917,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ "fastrand", "futures-core", @@ -2260,7 +2936,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2274,6 +2950,17 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "futures-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" +dependencies = [ + "futures-io", + "rustls 0.23.31", + "rustls-pki-types", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -2322,7 +3009,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75cec8bb4d3d32542cfcb9517f78366b52c17931e30d7ee1682c13686c19cee7" dependencies = [ "futures", - "futures-rustls", + "futures-rustls 0.25.1", "hyper", "log", "serde", @@ -2362,8 +3049,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -2388,9 +3077,27 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "glob-match" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985c9503b412198aa4197559e9a318524ebc4519c229bfa05a535828c950b9d" + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] [[package]] name = "gpt" @@ -2398,7 +3105,7 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3696fafb1ecdcc2ae3ce337de73e9202806068594b77d22fdf2f3573c5ec2219" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "crc", "simple-bytes", "uuid", @@ -2416,10 +3123,22 @@ dependencies = [ ] [[package]] -name = "h2" -version = "0.4.11" +name = "growable-bloom-filter" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "d174ccb4ba660d431329e7f0797870d0a4281e36353ec4b4a3c5eab6c2cfb6f1" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "xxhash-rust", +] + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -2427,7 +3146,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.10.0", + "indexmap 2.11.0", "slab", "tokio", "tokio-util", @@ -2477,9 +3196,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", @@ -2492,7 +3211,21 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", +] + +[[package]] +name = "hashx" +version = "0.3.4" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "arrayvec 0.7.6", + "blake2", + "dynasmrt", + "fixed-capacity-vec", + "hex", + "rand_core 0.9.3", + "thiserror 2.0.16", ] [[package]] @@ -2549,6 +3282,25 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hickory-client" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c466cd63a4217d5b2b8e32f23f58312741ce96e3c84bf7438677d2baff0fc555" +dependencies = [ + "cfg-if", + "data-encoding", + "futures-channel", + "futures-util", + "hickory-proto 0.25.2", + "once_cell", + "radix_trie", + "rand 0.9.2", + "thiserror 2.0.16", + "tokio", + "tracing", +] + [[package]] name = "hickory-proto" version = "0.24.4" @@ -2562,7 +3314,7 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna 1.0.3", + "idna", "ipnet", "once_cell", "rand 0.8.5", @@ -2577,6 +3329,32 @@ dependencies = [ "url", ] +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.9.2", + "ring", + "serde", + "thiserror 2.0.16", + "tinyvec", + "tokio", + "tracing", + "url", +] + [[package]] name = "hickory-resolver" version = "0.24.4" @@ -2585,7 +3363,7 @@ checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" dependencies = [ "cfg-if", "futures-util", - "hickory-proto", + "hickory-proto 0.24.4", "ipconfig", "lru-cache", "once_cell", @@ -2601,10 +3379,33 @@ dependencies = [ ] [[package]] -name = "hifijson" -version = "0.2.2" +name = "hickory-server" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9958ab3ce3170c061a27679916bd9b969eceeb5e8b120438e6751d0987655c42" +checksum = "d53e5fe811b941c74ee46b8818228bfd2bc2688ba276a0eaeb0f2c95ea3b2585" +dependencies = [ + "async-trait", + "bytes", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-util", + "hickory-proto 0.25.2", + "ipnet", + "prefix-trie", + "serde", + "thiserror 2.0.16", + "time", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hifijson" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a7763b98ba8a24f59e698bf9ab197e7676c640d6455d1580b4ce7dc560f0f0d" [[package]] name = "hkdf" @@ -2633,6 +3434,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "hostname-validator" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" + [[package]] name = "http" version = "1.3.1" @@ -2686,14 +3493,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" [[package]] -name = "hyper" -version = "1.6.0" +name = "humantime-serde" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" dependencies = [ + "humantime", + "serde", +] + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", @@ -2701,6 +3519,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -2715,7 +3534,7 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.29", + "rustls 0.23.31", "rustls-pki-types", "tokio", "tokio-rustls 0.26.2", @@ -2753,9 +3572,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "base64 0.22.1", "bytes", @@ -2769,7 +3588,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.0", "system-configuration", "tokio", "tower-service", @@ -2904,19 +3723,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.4.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -2946,13 +3755,14 @@ dependencies = [ [[package]] name = "imbl" -version = "4.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ae128b3bc67ed43ec0a7bb1c337a9f026717628b3c4033f07ded1da3e854951" +checksum = "33afdc5d333c1a43f1f640bfc6ad3788729e5b2f18472e5d33a9187315257f8e" dependencies = [ + "archery", "bitmaps", "imbl-sized-chunks", - "rand_core 0.6.4", + "rand_core 0.9.3", "rand_xoshiro", "serde", "version_check", @@ -2969,20 +3779,21 @@ dependencies = [ [[package]] name = "imbl-value" -version = "0.3.0" -source = "git+https://github.com/Start9Labs/imbl-value.git#229506ca83ae26bd78968719e5a78631deb39250" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2a5f88a75295785a3b4a752db1d45a3b83b9f3a4b13dc70a5aaa6c16d859b3" dependencies = [ "imbl", "serde", "serde_json", + "ts-rs", "yasi", ] [[package]] name = "imbl-value" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c04359f6198e92e1986d221cfa7de62cd4c9c27880dffb2f98b6eaaec40cde4f" +version = "0.4.3" +source = "git+https://github.com/Start9Labs/imbl-value.git#27f9bb38cd87290ce4732a2ef3034ea1c7340560" dependencies = [ "imbl", "serde", @@ -3011,9 +3822,9 @@ dependencies = [ [[package]] name = "indenter" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" @@ -3028,12 +3839,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "serde", ] @@ -3051,6 +3862,28 @@ dependencies = [ "web-time", ] +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.9.3", + "futures-core", + "inotify-sys", + "libc", + "tokio", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "inout" version = "0.1.4" @@ -3071,12 +3904,21 @@ dependencies = [ ] [[package]] -name = "io-uring" -version = "0.7.8" +name = "inventory" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" dependencies = [ - "bitflags 2.9.1", + "rustversion", +] + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags 2.9.3", "cfg-if", "libc", ] @@ -3087,7 +3929,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2", + "socket2 0.5.10", "widestring", "windows-sys 0.48.0", "winreg", @@ -3102,16 +3944,6 @@ dependencies = [ "serde", ] -[[package]] -name = "iprange" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37209be0ad225457e63814401415e748e2453a5297f9b637338f5fb8afa4ec00" -dependencies = [ - "ipnet", - "serde", -] - [[package]] name = "iri-string" version = "0.7.8" @@ -3249,9 +4081,9 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", @@ -3270,7 +4102,7 @@ dependencies = [ "regex", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.16", "time", ] @@ -3288,7 +4120,7 @@ dependencies = [ name = "json-patch" version = "0.2.7-alpha.0" dependencies = [ - "imbl-value 0.3.2", + "imbl-value 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "json-ptr", "serde", ] @@ -3298,9 +4130,9 @@ name = "json-ptr" version = "0.1.0" dependencies = [ "imbl", - "imbl-value 0.3.2", + "imbl-value 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde", - "thiserror 2.0.12", + "thiserror 2.0.16", ] [[package]] @@ -3308,12 +4140,22 @@ name = "jsonpath_lib" version = "0.3.0" source = "git+https://github.com/Start9Labs/jsonpath.git#1cacbd64afa2e1941a21fef06bad14317ba92f30" dependencies = [ - "imbl-value 0.3.0", + "imbl-value 0.4.3 (git+https://github.com/Start9Labs/imbl-value.git)", "log", "serde", "serde_json", ] +[[package]] +name = "k12" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dc5fdb62af2f520116927304f15d25b3c2667b4817b90efdc045194c912c54" +dependencies = [ + "digest 0.10.7", + "sha3", +] + [[package]] name = "keccak" version = "0.1.5" @@ -3323,6 +4165,35 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lalrpop" version = "0.20.2" @@ -3337,7 +4208,7 @@ dependencies = [ "petgraph", "pico-args", "regex", - "regex-syntax 0.8.5", + "regex-syntax 0.8.6", "string_cache", "term", "tiny-keccak", @@ -3351,7 +4222,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ - "regex-automata 0.4.9", + "regex-automata 0.4.10", ] [[package]] @@ -3400,9 +4271,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libloading" @@ -3411,7 +4282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -3422,21 +4293,22 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.4" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "libc", - "redox_syscall 0.5.13", + "redox_syscall 0.5.17", ] [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" dependencies = [ + "cc", "pkg-config", "vcpkg", ] @@ -3486,9 +4358,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "lock_api" @@ -3505,6 +4377,9 @@ name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +dependencies = [ + "value-bag", +] [[package]] name = "lru-cache" @@ -3592,7 +4467,7 @@ dependencies = [ "mail-builder", "md5", "rand 0.8.5", - "rustls 0.23.29", + "rustls 0.23.31", "rustls-pki-types", "smtp-proto", "tokio", @@ -3631,7 +4506,7 @@ dependencies = [ "bitvec 1.0.1", "serde", "serde-big-array", - "thiserror 2.0.12", + "thiserror 2.0.16", ] [[package]] @@ -3658,9 +4533,9 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" dependencies = [ "libc", ] @@ -3683,6 +4558,18 @@ dependencies = [ "autocfg", ] +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "mime" version = "0.3.17" @@ -3720,6 +4607,7 @@ dependencies = [ name = "models" version = "0.1.0" dependencies = [ + "arti-client", "axum 0.8.4", "base64 0.22.1", "color-eyre", @@ -3732,18 +4620,16 @@ dependencies = [ "num_enum", "openssl", "patch-db", - "rand 0.9.1", + "rand 0.9.2", "regex", "reqwest", "rpc-toolkit", - "rustls 0.23.29", + "rustls 0.23.31", "serde", "serde_json", - "sqlx", "ssh-key", - "thiserror 2.0.12", + "thiserror 2.0.16", "tokio", - "torut", "tracing", "ts-rs", "yasi", @@ -3783,6 +4669,15 @@ dependencies = [ "unicase", ] +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + [[package]] name = "nix" version = "0.24.3" @@ -3801,7 +4696,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "cfg-if", "cfg_aliases", "libc", @@ -3849,6 +4744,38 @@ dependencies = [ "memchr", ] +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.9.3", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-types" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -3985,7 +4912,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4000,6 +4927,25 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.3", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "object" version = "0.36.7" @@ -4015,7 +4961,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" dependencies = [ - "asn1-rs", + "asn1-rs 0.6.2", ] [[package]] @@ -4023,6 +4969,10 @@ name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] [[package]] name = "once_cell_polyfill" @@ -4030,6 +4980,20 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "oneshot-fused-workaround" +version = "0.2.3" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "futures", +] + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -4055,7 +5019,7 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "cfg-if", "foreign-types", "libc", @@ -4072,7 +5036,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4083,9 +5047,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.5.1+3.5.1" +version = "300.5.2+3.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "735230c832b28c000e3bc117119e6466a663ec73506bc0a9907ea4187508e42a" +checksum = "d270b79e2926f5150189d475bc7e9d2c69f9c4697b185fa917d5a32b792d21b4" dependencies = [ "cc", ] @@ -4103,6 +5067,21 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -4113,6 +5092,15 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +dependencies = [ + "memchr", +] + [[package]] name = "overload" version = "0.1.1" @@ -4187,11 +5175,17 @@ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.13", + "redox_syscall 0.5.17", "smallvec", "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "patch-db" version = "0.1.0" @@ -4200,7 +5194,7 @@ dependencies = [ "fd-lock-rs", "futures", "imbl", - "imbl-value 0.3.2", + "imbl-value 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "json-patch", "json-ptr", "lazy_static", @@ -4208,7 +5202,7 @@ dependencies = [ "patch-db-macro", "serde", "serde_cbor", - "thiserror 2.0.12", + "thiserror 2.0.16", "tokio", "tracing", "tracing-error", @@ -4264,9 +5258,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" @@ -4275,7 +5269,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror 2.0.12", + "thiserror 2.0.16", "ucd-trie", ] @@ -4299,7 +5293,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4319,7 +5313,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.10.0", + "indexmap 2.11.0", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared 0.13.1", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] @@ -4331,6 +5359,15 @@ dependencies = [ "siphasher", ] +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pico-args" version = "0.5.0" @@ -4354,7 +5391,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4408,18 +5445,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] -name = "polling" -version = "3.8.0" +name = "plotters" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "polling" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" dependencies = [ "cfg-if", "concurrent-queue 2.5.0", "hermit-abi", "pin-project-lite", - "rustix 1.0.7", - "tracing", - "windows-sys 0.59.0", + "rustix 1.0.8", + "windows-sys 0.60.2", ] [[package]] @@ -4428,6 +5492,21 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +[[package]] +name = "postage" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" +dependencies = [ + "atomic 0.5.3", + "crossbeam-queue", + "futures", + "parking_lot", + "pin-project", + "static_assertions", + "thiserror 1.0.69", +] + [[package]] name = "potential_utf" version = "0.1.2" @@ -4459,13 +5538,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] -name = "prettyplease" -version = "0.2.35" +name = "prefix-trie" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" +checksum = "85cf4c7c25f1dd66c76b451e9041a8cfce26e4ca754934fa7aed8d5a59a01d20" +dependencies = [ + "ipnet", + "num-traits", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4491,20 +5580,53 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "priority-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5676d703dda103cbb035b653a9f11448c0a7216c7926bd35fcb5865475d0c970" +dependencies = [ + "autocfg", + "equivalent", + "indexmap 2.11.0", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "toml_edit 0.22.27", + "toml_edit", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -4515,7 +5637,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "chrono", "flate2", "hex", @@ -4529,7 +5651,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "chrono", "hex", ] @@ -4542,13 +5664,13 @@ checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ "bit-set 0.8.0", "bit-vec 0.8.0", - "bitflags 2.9.1", + "bitflags 2.9.3", "lazy_static", "num-traits", - "rand 0.9.1", + "rand 0.9.2", "rand_chacha 0.9.0", "rand_xorshift", - "regex-syntax 0.8.5", + "regex-syntax 0.8.6", "rusty-fork", "tempfile", "unarray", @@ -4562,7 +5684,7 @@ checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4585,7 +5707,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4605,11 +5727,11 @@ checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" [[package]] name = "pty-process" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a480f2bcfed0fee5dd30e529d985c2ff4457dc7468b3c0dcb92b9fd2b14c6b9" +checksum = "71cec9e2670207c5ebb9e477763c74436af3b9091dd550b9fb3c1bec7f3ea266" dependencies = [ - "rustix 1.0.7", + "rustix 1.0.8", ] [[package]] @@ -4618,10 +5740,22 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" dependencies = [ - "idna 1.0.3", + "idna", "psl-types", ] +[[package]] +name = "pwd-grp" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94fdf3867b7f2889a736f0022ea9386766280d2cca4bdbe41629ada9e4f3b8f" +dependencies = [ + "derive-deftly 0.14.6", + "libc", + "paste", + "thiserror 1.0.69", +] + [[package]] name = "qrcode" version = "0.14.1" @@ -4673,6 +5807,16 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.7.3" @@ -4699,9 +5843,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -4773,6 +5917,17 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_jitter" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16df48f071248e67b8fc5e866d9448d45c08ad8b672baaaf796e2f15e606ff0" +dependencies = [ + "libc", + "rand_core 0.9.3", + "winapi", +] + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -4784,18 +5939,28 @@ dependencies = [ [[package]] name = "rand_xoshiro" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" dependencies = [ - "rand_core 0.6.4", + "rand_core 0.9.3", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", ] [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -4813,6 +5978,15 @@ dependencies = [ "yasna", ] +[[package]] +name = "rdrand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92195228612ac8eed47adbc2ed0f04e513a4ccb98175b6f2bd04d963b533655" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -4830,11 +6004,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", ] [[package]] @@ -4854,6 +6028,17 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.16", +] + [[package]] name = "ref-cast" version = "1.0.24" @@ -4871,19 +6056,19 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata 0.4.10", + "regex-syntax 0.8.6", ] [[package]] @@ -4897,13 +6082,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax 0.8.6", ] [[package]] @@ -4914,15 +6099,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64 0.22.1", "bytes", @@ -4965,9 +6150,9 @@ dependencies = [ [[package]] name = "reqwest_cookie_store" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b36498c7452f11b1833900f31fbb01fc46be20992a50269c88cf59d79f54e9" +checksum = "2314c325724fea278d44c13a525ebf60074e33c05f13b4345c076eb65b2446b3" dependencies = [ "bytes", "cookie_store", @@ -4981,6 +6166,11 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" +[[package]] +name = "retry-error" +version = "0.6.5" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" + [[package]] name = "rfc6979" version = "0.4.0" @@ -5018,8 +6208,8 @@ dependencies = [ [[package]] name = "rpc-toolkit" -version = "0.3.1" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=master#f83d9d9934036c1bc929073cad537774ecdbb5f2" +version = "0.3.2" +source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=master#23ecbda1c6b549fd2778e2847a53ea75a4103577" dependencies = [ "async-stream", "async-trait", @@ -5028,7 +6218,7 @@ dependencies = [ "futures", "http", "http-body-util", - "imbl-value 0.3.2", + "imbl-value 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.14.0", "lazy_format", "lazy_static", @@ -5037,7 +6227,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.16", "tokio", "tokio-stream", "url", @@ -5075,6 +6265,21 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rusqlite" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +dependencies = [ + "bitflags 2.9.3", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", + "time", +] + [[package]] name = "rust-argon2" version = "2.1.0" @@ -5088,9 +6293,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -5122,7 +6327,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "errno 0.3.13", "libc", "linux-raw-sys 0.4.15", @@ -5131,15 +6336,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "errno 0.3.13", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -5170,9 +6375,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.29" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "aws-lc-rs", "log", @@ -5246,9 +6451,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rusty-fork" @@ -5264,17 +6469,17 @@ dependencies = [ [[package]] name = "rustyline-async" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "055f3061e6c11d3c981f55b9c60da44d0413344ceeaf2fb479450f18dc9c2675" +checksum = "6e07ddce8399c61495b405dc94d4f30d01fc1c5e1238f10b9c09940678bc81ab" dependencies = [ + "ansi-width", "crossterm", "futures-util", "pin-project", "thingbuf", - "thiserror 2.0.12", + "thiserror 2.0.16", "unicode-segmentation", - "unicode-width 0.2.1", ] [[package]] @@ -5283,6 +6488,18 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "safelog" +version = "0.4.8" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "derive_more", + "educe", + "either", + "fluid-let", + "thiserror 2.0.16", +] + [[package]] name = "same-file" version = "1.0.6" @@ -5292,6 +6509,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "sanitize-filename" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc984f4f9ceb736a7bb755c3e3bd17dc56370af2600c9780dcc48c66453da34d" +dependencies = [ + "regex", +] + [[package]] name = "schannel" version = "0.1.27" @@ -5361,7 +6587,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "core-foundation", "core-foundation-sys", "libc", @@ -5405,6 +6631,25 @@ dependencies = [ "serde", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_bytes" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" +dependencies = [ + "serde", +] + [[package]] name = "serde_cbor" version = "0.11.1" @@ -5421,16 +6666,25 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", +] + +[[package]] +name = "serde_ignored" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b516445dac1e3535b6d658a7b528d771153dfb272ed4180ca4617a20550365ff" +dependencies = [ + "serde", ] [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.11.0", "itoa", "memchr", "ryu", @@ -5466,7 +6720,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -5478,6 +6732,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -5500,7 +6763,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.10.0", + "indexmap 2.11.0", "schemars 0.9.0", "schemars 1.0.4", "serde", @@ -5516,10 +6779,10 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ - "darling", + "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -5528,7 +6791,7 @@ version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.11.0", "itoa", "libyml", "memchr", @@ -5609,6 +6872,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "shellexpand" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" +dependencies = [ + "bstr", + "dirs", + "os_str_bytes", +] + [[package]] name = "shlex" version = "1.3.0" @@ -5638,9 +6912,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -5692,9 +6966,31 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "slotmap-careful" +version = "0.2.5" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "paste", + "serde", + "slotmap", + "thiserror 2.0.16", + "void", +] [[package]] name = "smallvec" @@ -5727,6 +7023,29 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "socks5-impl" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214a7c0af583e8f7abbd3394f235b4df7cc65db9b98ef80506cac7c7eefc0c75" +dependencies = [ + "async-trait", + "bytes", + "percent-encoding", + "thiserror 2.0.16", + "tokio", +] + [[package]] name = "solana-nohash-hasher" version = "0.2.1" @@ -5738,9 +7057,6 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] [[package]] name = "spki" @@ -5760,9 +7076,7 @@ checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" dependencies = [ "sqlx-core", "sqlx-macros", - "sqlx-mysql", "sqlx-postgres", - "sqlx-sqlite", ] [[package]] @@ -5773,28 +7087,27 @@ checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "base64 0.22.1", "bytes", - "chrono", "crc", "crossbeam-queue", "either", - "event-listener 5.4.0", + "event-listener 5.4.1", "futures-core", "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "hashlink", - "indexmap 2.10.0", + "indexmap 2.11.0", "log", "memchr", "once_cell", "percent-encoding", - "rustls 0.23.29", + "rustls 0.23.31", "serde", "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.16", "tokio", "tokio-stream", "tracing", @@ -5812,7 +7125,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -5832,57 +7145,12 @@ dependencies = [ "serde_json", "sha2 0.10.9", "sqlx-core", - "sqlx-mysql", "sqlx-postgres", - "sqlx-sqlite", - "syn 2.0.104", + "syn 2.0.106", "tokio", "url", ] -[[package]] -name = "sqlx-mysql" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" -dependencies = [ - "atoi", - "base64 0.22.1", - "bitflags 2.9.1", - "byteorder", - "bytes", - "chrono", - "crc", - "digest 0.10.7", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand 0.8.5", - "rsa", - "serde", - "sha1", - "sha2 0.10.9", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.12", - "tracing", - "whoami", -] - [[package]] name = "sqlx-postgres" version = "0.8.6" @@ -5891,9 +7159,8 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.9.1", + "bitflags 2.9.3", "byteorder", - "chrono", "crc", "dotenvy", "etcetera", @@ -5916,36 +7183,11 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.12", + "thiserror 2.0.16", "tracing", "whoami", ] -[[package]] -name = "sqlx-sqlite" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" -dependencies = [ - "atoi", - "chrono", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "thiserror 2.0.12", - "tracing", - "url", -] - [[package]] name = "sscanf" version = "0.4.1" @@ -5969,7 +7211,7 @@ dependencies = [ "quote", "regex-syntax 0.6.29", "strsim 0.10.0", - "syn 2.0.104", + "syn 2.0.106", "unicode-width 0.1.14", ] @@ -6023,9 +7265,10 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "start-os" -version = "0.4.0-alpha.9" +version = "0.4.0-alpha.10" dependencies = [ "aes 0.7.5", + "arti-client", "async-acme", "async-compression", "async-stream", @@ -6033,7 +7276,7 @@ dependencies = [ "axum 0.8.4", "backhand", "barrage", - "base32 0.5.1", + "base32", "base64 0.22.1", "base64ct", "basic-cookies", @@ -6051,6 +7294,7 @@ dependencies = [ "der", "digest 0.10.7", "divrem", + "dns-lookup", "ed25519 2.2.3", "ed25519-dalek 1.0.1", "ed25519-dalek 2.2.0", @@ -6061,6 +7305,8 @@ dependencies = [ "gpt", "helpers", "hex", + "hickory-client", + "hickory-server", "hmac", "http", "http-body-util", @@ -6068,13 +7314,13 @@ dependencies = [ "hyper-util", "id-pool", "imbl", - "imbl-value 0.3.2", + "imbl-value 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "include_dir", - "indexmap 2.10.0", + "indexmap 2.11.0", "indicatif", + "inotify", "integer-encoding", "ipnet", - "iprange", "isocountry", "itertools 0.14.0", "jaq-core", @@ -6110,16 +7356,17 @@ dependencies = [ "proptest-derive", "pty-process", "qrcode", - "rand 0.9.1", + "rand 0.9.2", "regex", "reqwest", "reqwest_cookie_store", "rpassword", "rpc-toolkit", "rust-argon2", - "rustls 0.23.29", + "rustls 0.23.31", "rustls-pki-types", "rustyline-async", + "safelog", "semver", "serde", "serde_json", @@ -6131,30 +7378,35 @@ dependencies = [ "shell-words", "signal-hook", "simple-logging", - "socket2", + "socket2 0.6.0", + "socks5-impl", "sqlx", "sscanf", "ssh-key", "tar", "termion", "textwrap", - "thiserror 2.0.12", + "thiserror 2.0.16", "tokio", "tokio-rustls 0.26.2", - "tokio-socks", "tokio-stream", "tokio-tar", "tokio-tungstenite", "tokio-util", "toml 0.8.23", - "torut", + "tor-cell", + "tor-hscrypto", + "tor-hsservice", + "tor-keymgr", + "tor-llcrypto", + "tor-proto", + "tor-rtcompat", "tower-service", "tracing", "tracing-error", "tracing-futures", "tracing-journald", "tracing-subscriber", - "trust-dns-server", "ts-rs", "typed-builder", "unix-named-pipe", @@ -6179,7 +7431,7 @@ checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared", + "phf_shared 0.11.3", "precomputed-hash", ] @@ -6206,6 +7458,27 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "subtle" version = "2.6.1" @@ -6225,9 +7498,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -6251,7 +7524,21 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", +] + +[[package]] +name = "sysinfo" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", ] [[package]] @@ -6260,7 +7547,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "core-foundation", "system-configuration-sys", ] @@ -6294,15 +7581,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.7", - "windows-sys 0.59.0", + "rustix 1.0.8", + "windows-sys 0.60.2", ] [[package]] @@ -6369,11 +7656,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.16", ] [[package]] @@ -6384,18 +7671,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -6469,10 +7756,20 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.9.0" +name = "tinytemplate" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -6485,9 +7782,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.46.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", @@ -6498,10 +7795,10 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "slab", - "socket2", + "socket2 0.6.0", "tokio-macros", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6512,7 +7809,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -6552,19 +7849,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.29", - "tokio", -] - -[[package]] -name = "tokio-socks" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" -dependencies = [ - "either", - "futures-util", - "thiserror 1.0.69", + "rustls 0.23.31", "tokio", ] @@ -6610,29 +7895,18 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", ] -[[package]] -name = "toml" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", -] - [[package]] name = "toml" version = "0.8.23" @@ -6640,9 +7914,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.27", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +dependencies = [ + "indexmap 2.11.0", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] @@ -6655,16 +7944,12 @@ dependencies = [ ] [[package]] -name = "toml_edit" -version = "0.19.15" +name = "toml_datetime" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" dependencies = [ - "indexmap 2.10.0", "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.5.40", ] [[package]] @@ -6673,12 +7958,21 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.11.0", "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_write", - "winnow 0.7.12", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow", ] [[package]] @@ -6687,6 +7981,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + [[package]] name = "tonic" version = "0.12.3" @@ -6708,7 +8008,7 @@ dependencies = [ "percent-encoding", "pin-project", "prost", - "socket2", + "socket2 0.5.10", "tokio", "tokio-stream", "tower 0.4.13", @@ -6718,22 +8018,978 @@ dependencies = [ ] [[package]] -name = "torut" -version = "0.2.1" -source = "git+https://github.com/Start9Labs/torut.git?branch=update%2Fdependencies#cc7a1425a01214465e106975e6690794d8551bdb" +name = "tor-async-utils" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" dependencies = [ - "base32 0.4.0", - "base64 0.21.7", - "derive_more 0.99.20", - "ed25519-dalek 1.0.1", + "derive-deftly 1.2.0", + "educe", + "futures", + "oneshot-fused-workaround", + "pin-project", + "postage", + "thiserror 2.0.16", + "void", +] + +[[package]] +name = "tor-basic-utils" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "derive_more", "hex", - "hmac", - "rand 0.7.3", + "itertools 0.14.0", + "libc", + "paste", + "rand 0.9.2", + "rand_chacha 0.9.0", "serde", - "serde_derive", + "slab", + "smallvec", + "thiserror 2.0.16", +] + +[[package]] +name = "tor-bytes" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "bytes", + "derive-deftly 1.2.0", + "digest 0.10.7", + "educe", + "getrandom 0.3.3", + "safelog", + "thiserror 2.0.16", + "tor-error", + "tor-llcrypto", + "zeroize", +] + +[[package]] +name = "tor-cell" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "amplify", + "bitflags 2.9.3", + "bytes", + "caret", + "derive-deftly 1.2.0", + "derive_more", + "educe", + "itertools 0.14.0", + "paste", + "rand 0.9.2", + "smallvec", + "thiserror 2.0.16", + "tor-basic-utils", + "tor-bytes", + "tor-cert", + "tor-error", + "tor-hscrypto", + "tor-linkspec", + "tor-llcrypto", + "tor-memquota", + "tor-protover", + "tor-units", + "void", +] + +[[package]] +name = "tor-cert" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "caret", + "derive_builder_fork_arti", + "derive_more", + "digest 0.10.7", + "thiserror 2.0.16", + "tor-bytes", + "tor-checkable", + "tor-llcrypto", +] + +[[package]] +name = "tor-chanmgr" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "async-trait", + "caret", + "derive_builder_fork_arti", + "derive_more", + "educe", + "futures", + "oneshot-fused-workaround", + "postage", + "rand 0.9.2", + "safelog", + "serde", + "thiserror 2.0.16", + "tor-async-utils", + "tor-basic-utils", + "tor-cell", + "tor-config", + "tor-error", + "tor-keymgr", + "tor-linkspec", + "tor-llcrypto", + "tor-memquota", + "tor-netdir", + "tor-proto", + "tor-rtcompat", + "tor-socksproto", + "tor-units", + "tracing", + "void", +] + +[[package]] +name = "tor-checkable" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "humantime", + "signature 2.2.0", + "thiserror 2.0.16", + "tor-llcrypto", +] + +[[package]] +name = "tor-circmgr" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "amplify", + "async-trait", + "bounded-vec-deque", + "cfg-if", + "derive-deftly 1.2.0", + "derive_builder_fork_arti", + "derive_more", + "downcast-rs", + "dyn-clone", + "educe", + "futures", + "humantime-serde", + "itertools 0.14.0", + "once_cell", + "oneshot-fused-workaround", + "pin-project", + "rand 0.9.2", + "retry-error", + "safelog", + "serde", + "static_assertions", + "thiserror 2.0.16", + "tor-async-utils", + "tor-basic-utils", + "tor-cell", + "tor-chanmgr", + "tor-config", + "tor-error", + "tor-guardmgr", + "tor-linkspec", + "tor-memquota", + "tor-netdir", + "tor-netdoc", + "tor-persist", + "tor-proto", + "tor-protover", + "tor-relay-selection", + "tor-rtcompat", + "tor-units", + "tracing", + "void", + "weak-table", +] + +[[package]] +name = "tor-config" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "amplify", + "cfg-if", + "derive-deftly 1.2.0", + "derive_builder_fork_arti", + "educe", + "either", + "figment", + "fs-mistrust", + "futures", + "itertools 0.14.0", + "notify", + "paste", + "postage", + "regex", + "serde", + "serde-value", + "serde_ignored", + "strum", + "thiserror 2.0.16", + "toml 0.9.5", + "tor-basic-utils", + "tor-error", + "tor-rtcompat", + "tracing", + "void", +] + +[[package]] +name = "tor-config-path" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "directories", + "serde", + "shellexpand", + "thiserror 2.0.16", + "tor-error", + "tor-general-addr", +] + +[[package]] +name = "tor-consdiff" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "digest 0.10.7", + "hex", + "thiserror 2.0.16", + "tor-llcrypto", +] + +[[package]] +name = "tor-dirclient" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "async-compression", + "base64ct", + "derive_more", + "futures", + "hex", + "http", + "httparse", + "httpdate", + "itertools 0.14.0", + "memchr", + "thiserror 2.0.16", + "tor-circmgr", + "tor-error", + "tor-hscrypto", + "tor-linkspec", + "tor-llcrypto", + "tor-netdoc", + "tor-proto", + "tor-rtcompat", + "tracing", +] + +[[package]] +name = "tor-dirmgr" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "async-trait", + "base64ct", + "derive_builder_fork_arti", + "derive_more", + "digest 0.10.7", + "educe", + "event-listener 5.4.1", + "fs-mistrust", + "fslock", + "futures", + "hex", + "humantime", + "humantime-serde", + "itertools 0.14.0", + "memmap2", + "oneshot-fused-workaround", + "paste", + "postage", + "rand 0.9.2", + "rusqlite", + "safelog", + "scopeguard", + "serde", + "serde_json", + "signature 2.2.0", + "static_assertions", + "strum", + "thiserror 2.0.16", + "time", + "tor-async-utils", + "tor-basic-utils", + "tor-checkable", + "tor-circmgr", + "tor-config", + "tor-consdiff", + "tor-dirclient", + "tor-error", + "tor-guardmgr", + "tor-llcrypto", + "tor-netdir", + "tor-netdoc", + "tor-persist", + "tor-proto", + "tor-protover", + "tor-rtcompat", + "tracing", +] + +[[package]] +name = "tor-error" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "derive_more", + "futures", + "paste", + "retry-error", + "static_assertions", + "strum", + "thiserror 2.0.16", + "tracing", + "void", +] + +[[package]] +name = "tor-general-addr" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "arbitrary", + "derive_more", + "thiserror 2.0.16", + "void", +] + +[[package]] +name = "tor-guardmgr" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "amplify", + "base64ct", + "derive-deftly 1.2.0", + "derive_builder_fork_arti", + "derive_more", + "dyn-clone", + "educe", + "futures", + "humantime", + "humantime-serde", + "itertools 0.14.0", + "num_enum", + "oneshot-fused-workaround", + "pin-project", + "postage", + "rand 0.9.2", + "safelog", + "serde", + "strum", + "thiserror 2.0.16", + "tor-async-utils", + "tor-basic-utils", + "tor-config", + "tor-error", + "tor-linkspec", + "tor-llcrypto", + "tor-netdir", + "tor-netdoc", + "tor-persist", + "tor-proto", + "tor-relay-selection", + "tor-rtcompat", + "tor-units", + "tracing", +] + +[[package]] +name = "tor-hsclient" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "async-trait", + "derive-deftly 1.2.0", + "derive_more", + "educe", + "either", + "futures", + "itertools 0.14.0", + "oneshot-fused-workaround", + "postage", + "rand 0.9.2", + "retry-error", + "safelog", + "slotmap-careful", + "strum", + "thiserror 2.0.16", + "tor-async-utils", + "tor-basic-utils", + "tor-bytes", + "tor-cell", + "tor-checkable", + "tor-circmgr", + "tor-config", + "tor-dirclient", + "tor-error", + "tor-hscrypto", + "tor-keymgr", + "tor-linkspec", + "tor-llcrypto", + "tor-memquota", + "tor-netdir", + "tor-netdoc", + "tor-persist", + "tor-proto", + "tor-protover", + "tor-rtcompat", + "tracing", +] + +[[package]] +name = "tor-hscrypto" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "cipher 0.4.4", + "data-encoding", + "derive-deftly 1.2.0", + "derive_more", + "digest 0.10.7", + "equix", + "hex", + "humantime", + "itertools 0.14.0", + "paste", + "rand 0.9.2", + "safelog", + "serde", + "signature 2.2.0", + "subtle", + "thiserror 2.0.16", + "tor-basic-utils", + "tor-bytes", + "tor-error", + "tor-key-forge", + "tor-llcrypto", + "tor-memquota", + "tor-units", + "void", + "zeroize", +] + +[[package]] +name = "tor-hsservice" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "amplify", + "async-trait", + "base64ct", + "cfg-if", + "derive-deftly 1.2.0", + "derive_builder_fork_arti", + "derive_more", + "digest 0.10.7", + "educe", + "fs-mistrust", + "futures", + "growable-bloom-filter", + "hex", + "humantime", + "itertools 0.14.0", + "k12", + "once_cell", + "oneshot-fused-workaround", + "postage", + "rand 0.9.2", + "rand_core 0.9.3", + "retry-error", + "safelog", + "serde", + "serde_with", + "strum", + "thiserror 2.0.16", + "tor-async-utils", + "tor-basic-utils", + "tor-bytes", + "tor-cell", + "tor-circmgr", + "tor-config", + "tor-config-path", + "tor-dirclient", + "tor-error", + "tor-hscrypto", + "tor-keymgr", + "tor-linkspec", + "tor-llcrypto", + "tor-log-ratelim", + "tor-netdir", + "tor-netdoc", + "tor-persist", + "tor-proto", + "tor-protover", + "tor-relay-selection", + "tor-rtcompat", + "tracing", + "void", +] + +[[package]] +name = "tor-key-forge" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "derive-deftly 1.2.0", + "derive_more", + "downcast-rs", + "paste", + "rand 0.9.2", + "signature 2.2.0", + "ssh-key", + "thiserror 2.0.16", + "tor-bytes", + "tor-cert", + "tor-checkable", + "tor-error", + "tor-llcrypto", +] + +[[package]] +name = "tor-keymgr" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "amplify", + "arrayvec 0.7.6", + "cfg-if", + "derive-deftly 1.2.0", + "derive_builder_fork_arti", + "derive_more", + "downcast-rs", + "dyn-clone", + "fs-mistrust", + "glob-match", + "humantime", + "inventory", + "itertools 0.14.0", + "rand 0.9.2", + "safelog", + "serde", + "signature 2.2.0", + "ssh-key", + "thiserror 2.0.16", + "tor-basic-utils", + "tor-bytes", + "tor-config", + "tor-config-path", + "tor-error", + "tor-hscrypto", + "tor-key-forge", + "tor-llcrypto", + "tor-persist", + "tracing", + "visibility", + "walkdir", + "zeroize", +] + +[[package]] +name = "tor-linkspec" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "base64ct", + "by_address", + "caret", + "derive-deftly 1.2.0", + "derive_builder_fork_arti", + "derive_more", + "hex", + "itertools 0.14.0", + "safelog", + "serde", + "serde_with", + "strum", + "thiserror 2.0.16", + "tor-basic-utils", + "tor-bytes", + "tor-config", + "tor-llcrypto", + "tor-memquota", + "tor-protover", +] + +[[package]] +name = "tor-llcrypto" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "aes 0.8.4", + "base64ct", + "ctr 0.9.2", + "curve25519-dalek 4.1.3", + "der-parser 10.0.0", + "derive-deftly 1.2.0", + "derive_more", + "digest 0.10.7", + "ed25519-dalek 2.2.0", + "educe", + "getrandom 0.3.3", + "hex", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_core 0.6.4", + "rand_core 0.9.3", + "rand_jitter", + "rdrand", + "rsa", + "safelog", + "serde", + "sha1", "sha2 0.10.9", "sha3", + "signature 2.2.0", + "subtle", + "thiserror 2.0.16", + "tor-memquota", + "visibility", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "tor-log-ratelim" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "futures", + "humantime", + "thiserror 2.0.16", + "tor-error", + "tor-rtcompat", + "tracing", + "weak-table", +] + +[[package]] +name = "tor-memquota" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "cfg-if", + "derive-deftly 1.2.0", + "derive_more", + "dyn-clone", + "educe", + "futures", + "itertools 0.14.0", + "paste", + "pin-project", + "serde", + "slotmap-careful", + "static_assertions", + "sysinfo", + "thiserror 2.0.16", + "tor-async-utils", + "tor-basic-utils", + "tor-config", + "tor-error", + "tor-log-ratelim", + "tor-rtcompat", + "tracing", + "void", +] + +[[package]] +name = "tor-netdir" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "async-trait", + "bitflags 2.9.3", + "derive_more", + "digest 0.10.7", + "futures", + "hex", + "humantime", + "itertools 0.14.0", + "num_enum", + "rand 0.9.2", + "serde", + "static_assertions", + "strum", + "thiserror 2.0.16", + "time", + "tor-basic-utils", + "tor-error", + "tor-hscrypto", + "tor-linkspec", + "tor-llcrypto", + "tor-netdoc", + "tor-protover", + "tor-units", + "tracing", + "typed-index-collections", +] + +[[package]] +name = "tor-netdoc" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "amplify", + "base64ct", + "bitflags 2.9.3", + "cipher 0.4.4", + "derive-deftly 1.2.0", + "derive_builder_fork_arti", + "derive_more", + "digest 0.10.7", + "educe", + "hex", + "humantime", + "itertools 0.14.0", + "memchr", + "paste", + "phf", + "rand 0.9.2", + "serde", + "serde_with", + "signature 2.2.0", + "smallvec", + "strum", + "subtle", + "thiserror 2.0.16", + "time", + "tinystr", + "tor-basic-utils", + "tor-bytes", + "tor-cell", + "tor-cert", + "tor-checkable", + "tor-error", + "tor-hscrypto", + "tor-linkspec", + "tor-llcrypto", + "tor-protover", + "tor-units", + "void", + "weak-table", + "zeroize", +] + +[[package]] +name = "tor-persist" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "amplify", + "derive-deftly 1.2.0", + "derive_more", + "filetime", + "fs-mistrust", + "fslock", + "fslock-guard", + "futures", + "itertools 0.14.0", + "oneshot-fused-workaround", + "paste", + "sanitize-filename", + "serde", + "serde_json", + "thiserror 2.0.16", + "time", + "tor-async-utils", + "tor-basic-utils", + "tor-error", + "tracing", + "void", +] + +[[package]] +name = "tor-proto" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "amplify", + "asynchronous-codec", + "bitvec 1.0.1", + "bytes", + "caret", + "cfg-if", + "cipher 0.4.4", + "coarsetime", + "criterion-cycles-per-byte", + "derive-deftly 1.2.0", + "derive_builder_fork_arti", + "derive_more", + "digest 0.10.7", + "educe", + "futures", + "futures-util", + "hkdf", + "hmac", + "itertools 0.14.0", + "oneshot-fused-workaround", + "pin-project", + "postage", + "rand 0.9.2", + "rand_core 0.9.3", + "safelog", + "slotmap-careful", + "smallvec", + "static_assertions", + "subtle", + "sync_wrapper", + "thiserror 2.0.16", "tokio", + "tokio-util", + "tor-async-utils", + "tor-basic-utils", + "tor-bytes", + "tor-cell", + "tor-cert", + "tor-checkable", + "tor-config", + "tor-error", + "tor-hscrypto", + "tor-linkspec", + "tor-llcrypto", + "tor-log-ratelim", + "tor-memquota", + "tor-protover", + "tor-rtcompat", + "tor-rtmock", + "tor-units", + "tracing", + "typenum", + "visibility", + "void", + "zeroize", +] + +[[package]] +name = "tor-protover" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "caret", + "paste", + "serde_with", + "thiserror 2.0.16", + "tor-bytes", +] + +[[package]] +name = "tor-relay-selection" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "rand 0.9.2", + "serde", + "tor-basic-utils", + "tor-linkspec", + "tor-netdir", + "tor-netdoc", +] + +[[package]] +name = "tor-rtcompat" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "arbitrary", + "async-io", + "async-native-tls", + "async-std", + "async-trait", + "async_executors", + "asynchronous-codec", + "coarsetime", + "derive_more", + "dyn-clone", + "educe", + "futures", + "futures-rustls 0.26.0", + "hex", + "libc", + "native-tls", + "paste", + "pin-project", + "rustls-pki-types", + "rustls-webpki 0.103.4", + "thiserror 2.0.16", + "tokio", + "tokio-util", + "tor-error", + "tor-general-addr", + "tracing", + "void", +] + +[[package]] +name = "tor-rtmock" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "amplify", + "assert_matches", + "async-trait", + "derive-deftly 1.2.0", + "derive_more", + "educe", + "futures", + "humantime", + "itertools 0.14.0", + "oneshot-fused-workaround", + "pin-project", + "priority-queue", + "slotmap-careful", + "strum", + "thiserror 2.0.16", + "tor-error", + "tor-general-addr", + "tor-rtcompat", + "tracing", + "tracing-test", + "void", +] + +[[package]] +name = "tor-socksproto" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "amplify", + "caret", + "derive-deftly 1.2.0", + "educe", + "safelog", + "subtle", + "thiserror 2.0.16", + "tor-bytes", + "tor-error", +] + +[[package]] +name = "tor-units" +version = "0.33.0" +source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" +dependencies = [ + "derive-deftly 1.2.0", + "derive_more", + "serde", + "thiserror 2.0.16", + "tor-memquota", ] [[package]] @@ -6778,7 +9034,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "bytes", "futures-util", "http", @@ -6822,7 +9078,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -6896,50 +9152,24 @@ dependencies = [ ] [[package]] -name = "trust-dns-proto" -version = "0.23.2" +name = "tracing-test" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374" +checksum = "557b891436fe0d5e0e363427fc7f217abf9ccd510d5136549847bdcbcd011d68" dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.4.0", - "ipnet", - "once_cell", - "rand 0.8.5", - "smallvec", - "thiserror 1.0.69", - "tinyvec", - "tokio", - "tracing", - "url", + "tracing-core", + "tracing-subscriber", + "tracing-test-macro", ] [[package]] -name = "trust-dns-server" -version = "0.23.2" +name = "tracing-test-macro" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c540f73c2b2ec2f6c54eabd0900e7aafb747a820224b742f556e8faabb461bc7" +checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ - "async-trait", - "bytes", - "cfg-if", - "drain", - "enum-as-inner", - "futures-executor", - "futures-util", - "serde", - "thiserror 1.0.69", - "time", - "tokio", - "toml 0.7.8", - "tracing", - "trust-dns-proto", + "quote", + "syn 2.0.106", ] [[package]] @@ -6950,8 +9180,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ts-rs" -version = "8.1.0" -source = "git+https://github.com/dr-bonez/ts-rs.git?branch=feature%2Ftop-level-as#7ae88ade90b5e724159048a663a0bdb04bed27f7" +version = "9.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b44017f9f875786e543595076374b9ef7d13465a518dd93d6ccdbf5b432dde8c" dependencies = [ "thiserror 1.0.69", "ts-rs-macros", @@ -6959,13 +9190,13 @@ dependencies = [ [[package]] name = "ts-rs-macros" -version = "8.1.0" -source = "git+https://github.com/dr-bonez/ts-rs.git?branch=feature%2Ftop-level-as#7ae88ade90b5e724159048a663a0bdb04bed27f7" +version = "9.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c88cc88fd23b5a04528f3a8436024f20010a16ec18eb23c164b1242f65860130" dependencies = [ - "Inflector", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "termcolor", ] @@ -6981,31 +9212,41 @@ dependencies = [ "httparse", "log", "native-tls", - "rand 0.9.1", + "rand 0.9.2", "sha1", - "thiserror 2.0.12", + "thiserror 2.0.16", "url", "utf-8", ] [[package]] name = "typed-builder" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce63bcaf7e9806c206f7d7b9c1f38e0dce8bb165a80af0898161058b19248534" +checksum = "fef81aec2ca29576f9f6ae8755108640d0a86dd3161b2e8bca6cfa554e98f77d" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d8d828da2a3d759d3519cdf29a5bac49c77d039ad36d0782edadbf9cd5415b" +checksum = "1ecb9ecf7799210407c14a8cfdfe0173365780968dc57973ed082211958e0b18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", +] + +[[package]] +name = "typed-index-collections" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd393dbd1e7b23e0cab7396570309b4068aa504e9dac2cd41d827583b4e9ab7" +dependencies = [ + "bincode 2.0.1", + "serde", ] [[package]] @@ -7037,6 +9278,15 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + [[package]] name = "unicase" version = "2.8.1" @@ -7124,12 +9374,12 @@ checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", - "idna 1.0.3", + "idna", "percent-encoding", "serde", ] @@ -7160,9 +9410,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" dependencies = [ "getrandom 0.3.3", "js-sys", @@ -7175,6 +9425,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" + [[package]] name = "vcpkg" version = "0.2.15" @@ -7193,6 +9449,23 @@ version = "0.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "wait-timeout" version = "0.2.1" @@ -7248,6 +9521,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" +[[package]] +name = "wasix" +version = "0.12.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +dependencies = [ + "wasi 0.11.1+wasi-snapshot-preview1", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -7270,7 +9552,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -7305,7 +9587,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7332,6 +9614,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "weak-table" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549" + [[package]] name = "web-sys" version = "0.3.77" @@ -7358,14 +9646,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.1", + "webpki-roots 1.0.2", ] [[package]] name = "webpki-roots" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] @@ -7384,11 +9672,11 @@ dependencies = [ [[package]] name = "whoami" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ - "redox_syscall 0.5.13", + "libredox", "wasite", ] @@ -7416,11 +9704,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -7429,6 +9717,28 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + [[package]] name = "windows-core" version = "0.61.2" @@ -7442,6 +9752,17 @@ dependencies = [ "windows-strings", ] +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.60.0" @@ -7450,7 +9771,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7461,7 +9782,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7470,6 +9791,16 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + [[package]] name = "windows-registry" version = "0.5.3" @@ -7532,7 +9863,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -7568,10 +9899,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -7582,6 +9914,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -7722,18 +10063,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.5.40" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] @@ -7754,7 +10086,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", ] [[package]] @@ -7778,15 +10110,27 @@ dependencies = [ "tap", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek 4.1.3", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "x509-parser" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" dependencies = [ - "asn1-rs", + "asn1-rs 0.6.2", "data-encoding", - "der-parser", + "der-parser 9.0.0", "lazy_static", "nom 7.1.3", "oid-registry", @@ -7811,7 +10155,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" dependencies = [ "libc", - "rustix 1.0.7", + "rustix 1.0.8", ] [[package]] @@ -7843,14 +10187,16 @@ dependencies = [ [[package]] name = "yasi" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f355ab62ebe30b758c1f4ab096a306722c4b7dbfb9d8c07d18c70d71a945588" +checksum = "91d8075d6829add5913054d2de87aec55a786599427d93ef3504a0b7f515a418" dependencies = [ "ahash 0.8.12", "hashbrown 0.13.2", "lazy_static", "serde", + "tinyvec", + "ts-rs", ] [[package]] @@ -7882,15 +10228,15 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "synstructure", ] [[package]] name = "zbus" -version = "5.8.0" +version = "5.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597f45e98bc7e6f0988276012797855613cd8269e23b5be62cc4e5d28b7e515d" +checksum = "67a073be99ace1adc48af593701c8015cd9817df372e14a1a6b0ee8f8bf043be" dependencies = [ "async-broadcast", "async-executor", @@ -7902,7 +10248,7 @@ dependencies = [ "async-trait", "blocking", "enumflags2", - "event-listener 5.4.0", + "event-listener 5.4.1", "futures-core", "futures-lite", "hex", @@ -7912,8 +10258,8 @@ dependencies = [ "serde_repr", "tracing", "uds_windows", - "windows-sys 0.59.0", - "winnow 0.7.12", + "windows-sys 0.60.2", + "winnow", "zbus_macros", "zbus_names", "zvariant", @@ -7921,14 +10267,14 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.8.0" +version = "5.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c8e4e14dcdd9d97a98b189cd1220f30e8394ad271e8c987da84f73693862c2" +checksum = "0e80cd713a45a49859dcb648053f63265f4f2851b6420d47a958e5697c68b131" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "zbus_names", "zvariant", "zvariant_utils", @@ -7942,7 +10288,7 @@ checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" dependencies = [ "serde", "static_assertions", - "winnow 0.7.12", + "winnow", "zvariant", ] @@ -7963,7 +10309,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7983,7 +10329,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "synstructure", ] @@ -8004,7 +10350,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -8020,9 +10366,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", @@ -8037,7 +10383,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -8057,12 +10403,12 @@ dependencies = [ "flate2", "getrandom 0.3.3", "hmac", - "indexmap 2.10.0", + "indexmap 2.11.0", "lzma-rs", "memchr", "pbkdf2", "sha1", - "thiserror 2.0.12", + "thiserror 2.0.16", "time", "xz2", "zeroize", @@ -8118,41 +10464,40 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.6.0" +version = "5.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91b3680bb339216abd84714172b5138a4edac677e641ef17e1d8cb1b3ca6e6f" +checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db" dependencies = [ "endi", "enumflags2", "serde", - "winnow 0.7.12", + "winnow", "zvariant_derive", "zvariant_utils", ] [[package]] name = "zvariant_derive" -version = "5.6.0" +version = "5.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8c68501be459a8dbfffbe5d792acdd23b4959940fc87785fb013b32edbc208" +checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "zvariant_utils", ] [[package]] name = "zvariant_utils" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" +checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" dependencies = [ "proc-macro2", "quote", "serde", - "static_assertions", - "syn 2.0.104", - "winnow 0.7.12", + "syn 2.0.106", + "winnow", ] diff --git a/core/build-containerbox.sh b/core/build-containerbox.sh index e81efcc97..277aaa396 100755 --- a/core/build-containerbox.sh +++ b/core/build-containerbox.sh @@ -26,11 +26,11 @@ if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then RUSTFLAGS="--cfg tokio_unstable" fi -alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl' +source ./core/builder-alias.sh echo "FEATURES=\"$FEATURES\"" echo "RUSTFLAGS=\"$RUSTFLAGS\"" -rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features container-runtime,$FEATURES --locked --bin containerbox --target=$ARCH-unknown-linux-musl" +rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli-container,$FEATURES --locked --bin containerbox --target=$ARCH-unknown-linux-musl" if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/containerbox | awk '{ print $3 }')" != "$UID" ]; then rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo" fi \ No newline at end of file diff --git a/core/build-registrybox.sh b/core/build-registrybox.sh index 3659b372a..e00200454 100755 --- a/core/build-registrybox.sh +++ b/core/build-registrybox.sh @@ -26,11 +26,11 @@ if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then RUSTFLAGS="--cfg tokio_unstable" fi -alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl' +source ./core/builder-alias.sh echo "FEATURES=\"$FEATURES\"" echo "RUSTFLAGS=\"$RUSTFLAGS\"" -rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli,registry,$FEATURES --locked --bin registrybox --target=$ARCH-unknown-linux-musl" +rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli-registry,registry,$FEATURES --locked --bin registrybox --target=$ARCH-unknown-linux-musl" if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/registrybox | awk '{ print $3 }')" != "$UID" ]; then rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo" fi diff --git a/core/build-startbox.sh b/core/build-startbox.sh index 9fad6fa3d..30721ff0e 100755 --- a/core/build-startbox.sh +++ b/core/build-startbox.sh @@ -1,5 +1,10 @@ #!/bin/bash +PROFILE=${PROFILE:-release} +if [ "${PROFILE}" = "release" ]; then + BUILD_FLAGS="--release" +fi + cd "$(dirname "${BASH_SOURCE[0]}")" set -ea @@ -26,11 +31,11 @@ if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then RUSTFLAGS="--cfg tokio_unstable" fi -alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl' +source ./core/builder-alias.sh echo "FEATURES=\"$FEATURES\"" echo "RUSTFLAGS=\"$RUSTFLAGS\"" -rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli,daemon,$FEATURES --locked --bin startbox --target=$ARCH-unknown-linux-musl" -if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/startbox | awk '{ print $3 }')" != "$UID" ]; then +rust-musl-builder sh -c "cd core && cargo build $BUILD_FLAGS --no-default-features --features cli,startd,$FEATURES --locked --bin startbox --target=$ARCH-unknown-linux-musl" +if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/${PROFILE}/startbox | awk '{ print $3 }')" != "$UID" ]; then rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo" fi \ No newline at end of file diff --git a/core/build-ts.sh b/core/build-ts.sh index c9890bfe7..106054cdf 100755 --- a/core/build-ts.sh +++ b/core/build-ts.sh @@ -26,7 +26,7 @@ if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then RUSTFLAGS="--cfg tokio_unstable" fi -alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl' +source ./core/builder-alias.sh echo "FEATURES=\"$FEATURES\"" echo "RUSTFLAGS=\"$RUSTFLAGS\"" diff --git a/core/build-tunnelbox.sh b/core/build-tunnelbox.sh new file mode 100755 index 000000000..525b84bcd --- /dev/null +++ b/core/build-tunnelbox.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +cd "$(dirname "${BASH_SOURCE[0]}")" + +set -ea +shopt -s expand_aliases + +if [ -z "$ARCH" ]; then + ARCH=$(uname -m) +fi + +if [ "$ARCH" = "arm64" ]; then + ARCH="aarch64" +fi + +USE_TTY= +if tty -s; then + USE_TTY="-it" +fi + +cd .. +FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')" +RUSTFLAGS="" + +if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then + RUSTFLAGS="--cfg tokio_unstable" +fi + +source ./core/builder-alias.sh + +echo "FEATURES=\"$FEATURES\"" +echo "RUSTFLAGS=\"$RUSTFLAGS\"" +rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli-tunnel,tunnel,$FEATURES --locked --bin tunnelbox --target=$ARCH-unknown-linux-musl" +if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/tunnelbox | awk '{ print $3 }')" != "$UID" ]; then + rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo" +fi diff --git a/core/builder-alias.sh b/core/builder-alias.sh new file mode 100644 index 000000000..fa8d545d6 --- /dev/null +++ b/core/builder-alias.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -e SCCACHE_GHA_ENABLED -e SCCACHE_GHA_VERSION -e ACTIONS_RESULTS_URL -e ACTIONS_RUNTIME_TOKEN -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$HOME/.cache/sccache":/root/.cache/sccache -v "$(pwd)":/home/rust/src -w /home/rust/src -P start9/rust-musl-cross:$ARCH-musl' diff --git a/core/install-cli.sh b/core/install-cli.sh index b278947a3..7a0d725f2 100755 --- a/core/install-cli.sh +++ b/core/install-cli.sh @@ -16,4 +16,4 @@ if [ "$PLATFORM" = "arm64" ]; then PLATFORM="aarch64" fi -cargo install --path=./startos --no-default-features --features=cli,docker,registry --bin start-cli --locked +cargo install --path=./startos --no-default-features --features=cli,docker --bin start-cli --locked diff --git a/core/models/Cargo.toml b/core/models/Cargo.toml index 03149b7fb..9d489a84e 100644 --- a/core/models/Cargo.toml +++ b/core/models/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +arti-client = { version = "0.33", default-features = false, git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" } axum = "0.8.4" base64 = "0.22.1" color-eyre = "0.6.2" @@ -29,16 +30,10 @@ rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = rustls = "0.23" serde = { version = "1.0", features = ["derive", "rc"] } serde_json = "1.0" -sqlx = { version = "0.8.6", features = [ - "chrono", - "runtime-tokio-rustls", - "postgres", -] } ssh-key = "0.6.2" -ts-rs = { git = "https://github.com/dr-bonez/ts-rs.git", branch = "feature/top-level-as" } # "8" +ts-rs = "9" thiserror = "2.0" tokio = { version = "1", features = ["full"] } -torut = { git = "https://github.com/Start9Labs/torut.git", branch = "update/dependencies" } tracing = "0.1.39" -yasi = "0.1.5" +yasi = { version = "0.1.6", features = ["serde", "ts-rs"] } zbus = "5" diff --git a/core/models/bindings/ServiceInterfaceId.ts b/core/models/bindings/ServiceInterfaceId.ts index 87edd8694..55315ab60 100644 --- a/core/models/bindings/ServiceInterfaceId.ts +++ b/core/models/bindings/ServiceInterfaceId.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type ServiceInterfaceId = string; \ No newline at end of file +export type ServiceInterfaceId = string; diff --git a/core/models/src/data_url.rs b/core/models/src/data_url.rs index 8a95c993e..f5cdcb1e4 100644 --- a/core/models/src/data_url.rs +++ b/core/models/src/data_url.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::path::Path; +use std::str::FromStr; use base64::Engine; use color_eyre::eyre::eyre; @@ -14,28 +15,26 @@ use crate::{mime, Error, ErrorKind, ResultExt}; #[derive(Clone, TS)] #[ts(type = "string")] pub struct DataUrl<'a> { - mime: InternedString, - data: Cow<'a, [u8]>, + pub mime: InternedString, + pub data: Cow<'a, [u8]>, } impl<'a> DataUrl<'a> { pub const DEFAULT_MIME: &'static str = "application/octet-stream"; pub const MAX_SIZE: u64 = 100 * 1024; - // data:{mime};base64,{data} - pub fn to_string(&self) -> String { + fn to_string(&self) -> String { use std::fmt::Write; - let mut res = String::with_capacity(self.data_url_len_without_mime() + self.mime.len()); - let _ = write!(res, "data:{};base64,", self.mime); - base64::engine::general_purpose::STANDARD.encode_string(&self.data, &mut res); + let mut res = String::with_capacity(self.len()); + write!(&mut res, "{self}").unwrap(); res } - fn data_url_len_without_mime(&self) -> usize { + fn len_without_mime(&self) -> usize { 5 + 8 + (4 * self.data.len() / 3) + 3 } - pub fn data_url_len(&self) -> usize { - self.data_url_len_without_mime() + self.mime.len() + pub fn len(&self) -> usize { + self.len_without_mime() + self.mime.len() } pub fn from_slice(mime: &str, data: &'a [u8]) -> Self { @@ -44,6 +43,10 @@ impl<'a> DataUrl<'a> { data: Cow::Borrowed(data), } } + + pub fn canonical_ext(&self) -> Option<&'static str> { + mime::unmime(&self.mime) + } } impl DataUrl<'static> { pub async fn from_reader( @@ -109,12 +112,57 @@ impl DataUrl<'static> { } } +impl<'a> std::fmt::Display for DataUrl<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "data:{};base64,{}", + self.mime, + base64::display::Base64Display::new( + &*self.data, + &base64::engine::general_purpose::STANDARD + ) + ) + } +} impl<'a> std::fmt::Debug for DataUrl<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.to_string()) + std::fmt::Display::fmt(self, f) } } +#[derive(Debug)] +pub struct DataUrlParseError; +impl std::fmt::Display for DataUrlParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "invalid base64 url") + } +} +impl std::error::Error for DataUrlParseError {} +impl From for Error { + fn from(e: DataUrlParseError) -> Self { + Error::new(e, ErrorKind::ParseUrl) + } +} + +impl FromStr for DataUrl<'static> { + type Err = DataUrlParseError; + fn from_str(s: &str) -> Result { + s.strip_prefix("data:") + .and_then(|v| v.split_once(";base64,")) + .and_then(|(mime, data)| { + Some(DataUrl { + mime: InternedString::intern(mime), + data: Cow::Owned( + base64::engine::general_purpose::STANDARD + .decode(data) + .ok()?, + ), + }) + }) + .ok_or(DataUrlParseError) + } +} impl<'de> Deserialize<'de> for DataUrl<'static> { fn deserialize(deserializer: D) -> Result where @@ -130,21 +178,9 @@ impl<'de> Deserialize<'de> for DataUrl<'static> { where E: serde::de::Error, { - v.strip_prefix("data:") - .and_then(|v| v.split_once(";base64,")) - .and_then(|(mime, data)| { - Some(DataUrl { - mime: InternedString::intern(mime), - data: Cow::Owned( - base64::engine::general_purpose::STANDARD - .decode(data) - .ok()?, - ), - }) - }) - .ok_or_else(|| { - E::invalid_value(serde::de::Unexpected::Str(v), &"a valid base64 data url") - }) + v.parse().map_err(|_| { + E::invalid_value(serde::de::Unexpected::Str(v), &"a valid base64 data url") + }) } } deserializer.deserialize_any(Visitor) @@ -168,6 +204,6 @@ fn doesnt_reallocate() { mime: InternedString::intern("png"), data: Cow::Borrowed(&random[..i]), }; - assert_eq!(icon.to_string().capacity(), icon.data_url_len()); + assert_eq!(icon.to_string().capacity(), icon.len()); } } diff --git a/core/models/src/errors.rs b/core/models/src/errors.rs index b0a709133..779e4b1a4 100644 --- a/core/models/src/errors.rs +++ b/core/models/src/errors.rs @@ -288,11 +288,6 @@ impl From for Error { Error::new(e, ErrorKind::Database) } } -impl From for Error { - fn from(e: sqlx::Error) -> Self { - Error::new(e, ErrorKind::Database) - } -} impl From for Error { fn from(e: ed25519_dalek::SignatureError) -> Self { Error::new(e, ErrorKind::InvalidSignature) @@ -303,11 +298,6 @@ impl From for Error { Error::new(e, ErrorKind::ParseNetAddress) } } -impl From for Error { - fn from(e: torut::control::ConnError) -> Self { - Error::new(e, ErrorKind::Tor) - } -} impl From for Error { fn from(e: ipnet::AddrParseError) -> Self { Error::new(e, ErrorKind::ParseNetAddress) @@ -353,8 +343,8 @@ impl From for Error { Error::new(e, kind) } } -impl From for Error { - fn from(e: torut::onion::OnionAddressParseError) -> Self { +impl From for Error { + fn from(e: arti_client::Error) -> Self { Error::new(e, ErrorKind::Tor) } } diff --git a/core/models/src/id/gateway.rs b/core/models/src/id/gateway.rs new file mode 100644 index 000000000..7721a7301 --- /dev/null +++ b/core/models/src/id/gateway.rs @@ -0,0 +1,58 @@ +use std::convert::Infallible; +use std::path::Path; +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; +use ts_rs::TS; +use yasi::InternedString; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TS)] +#[ts(type = "string")] +pub struct GatewayId(InternedString); +impl GatewayId { + pub fn as_str(&self) -> &str { + &*self.0 + } +} +impl From for GatewayId +where + T: Into, +{ + fn from(value: T) -> Self { + Self(value.into()) + } +} +impl FromStr for GatewayId { + type Err = Infallible; + fn from_str(s: &str) -> Result { + Ok(GatewayId(InternedString::intern(s))) + } +} +impl AsRef for GatewayId { + fn as_ref(&self) -> &GatewayId { + self + } +} +impl std::fmt::Display for GatewayId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &self.0) + } +} +impl AsRef for GatewayId { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} +impl AsRef for GatewayId { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} +impl<'de> Deserialize<'de> for GatewayId { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + Ok(GatewayId(serde::Deserialize::deserialize(deserializer)?)) + } +} diff --git a/core/models/src/id/host.rs b/core/models/src/id/host.rs index f147813f3..58d7eb027 100644 --- a/core/models/src/id/host.rs +++ b/core/models/src/id/host.rs @@ -60,20 +60,3 @@ impl AsRef for HostId { self.0.as_ref().as_ref() } } -impl<'q> sqlx::Encode<'q, sqlx::Postgres> for HostId { - fn encode_by_ref( - &self, - buf: &mut ::ArgumentBuffer<'q>, - ) -> Result { - <&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf) - } -} -impl sqlx::Type for HostId { - fn type_info() -> sqlx::postgres::PgTypeInfo { - <&str as sqlx::Type>::type_info() - } - - fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool { - <&str as sqlx::Type>::compatible(ty) - } -} diff --git a/core/models/src/id/mod.rs b/core/models/src/id/mod.rs index 2039c7242..f9f29513c 100644 --- a/core/models/src/id/mod.rs +++ b/core/models/src/id/mod.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use yasi::InternedString; mod action; +mod gateway; mod health_check; mod host; mod image; @@ -16,6 +17,7 @@ mod service_interface; mod volume; pub use action::ActionId; +pub use gateway::GatewayId; pub use health_check::HealthCheckId; pub use host::HostId; pub use image::ImageId; @@ -116,20 +118,3 @@ impl Serialize for Id { serializer.serialize_str(self) } } -impl<'q> sqlx::Encode<'q, sqlx::Postgres> for Id { - fn encode_by_ref( - &self, - buf: &mut ::ArgumentBuffer<'q>, - ) -> Result { - <&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf) - } -} -impl sqlx::Type for Id { - fn type_info() -> sqlx::postgres::PgTypeInfo { - <&str as sqlx::Type>::type_info() - } - - fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool { - <&str as sqlx::Type>::compatible(ty) - } -} diff --git a/core/models/src/id/package.rs b/core/models/src/id/package.rs index 886866f87..4c3b06d60 100644 --- a/core/models/src/id/package.rs +++ b/core/models/src/id/package.rs @@ -87,20 +87,3 @@ impl Serialize for PackageId { Serialize::serialize(&self.0, serializer) } } -impl<'q> sqlx::Encode<'q, sqlx::Postgres> for PackageId { - fn encode_by_ref( - &self, - buf: &mut ::ArgumentBuffer<'q>, - ) -> Result { - <&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf) - } -} -impl sqlx::Type for PackageId { - fn type_info() -> sqlx::postgres::PgTypeInfo { - <&str as sqlx::Type>::type_info() - } - - fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool { - <&str as sqlx::Type>::compatible(ty) - } -} diff --git a/core/models/src/id/service_interface.rs b/core/models/src/id/service_interface.rs index 872abd419..f398eef78 100644 --- a/core/models/src/id/service_interface.rs +++ b/core/models/src/id/service_interface.rs @@ -44,23 +44,6 @@ impl AsRef for ServiceInterfaceId { self.0.as_ref().as_ref() } } -impl<'q> sqlx::Encode<'q, sqlx::Postgres> for ServiceInterfaceId { - fn encode_by_ref( - &self, - buf: &mut ::ArgumentBuffer<'q>, - ) -> Result { - <&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf) - } -} -impl sqlx::Type for ServiceInterfaceId { - fn type_info() -> sqlx::postgres::PgTypeInfo { - <&str as sqlx::Type>::type_info() - } - - fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool { - <&str as sqlx::Type>::compatible(ty) - } -} impl FromStr for ServiceInterfaceId { type Err = ::Err; fn from_str(s: &str) -> Result { diff --git a/core/startos/Cargo.toml b/core/startos/Cargo.toml index 9acc84445..48d60b793 100644 --- a/core/startos/Cargo.toml +++ b/core/startos/Cargo.toml @@ -2,7 +2,7 @@ authors = ["Aiden McClelland "] description = "The core of StartOS" documentation = "https://docs.rs/start-os" -edition = "2021" +edition = "2024" keywords = [ "self-hosted", "raspberry-pi", @@ -14,7 +14,7 @@ keywords = [ name = "start-os" readme = "README.md" repository = "https://github.com/Start9Labs/start-os" -version = "0.4.0-alpha.9" # VERSION_BUMP +version = "0.4.0-alpha.10" # VERSION_BUMP license = "MIT" [lib] @@ -37,18 +37,36 @@ path = "src/main.rs" name = "registrybox" path = "src/main.rs" +[[bin]] +name = "tunnelbox" +path = "src/main.rs" + [features] -cli = [] -container-runtime = ["procfs", "pty-process"] -daemon = ["mail-send"] -registry = [] -default = ["cli", "daemon", "registry", "container-runtime"] +cli = ["cli-startd", "cli-registry", "cli-tunnel"] +cli-container = ["procfs", "pty-process"] +cli-registry = [] +cli-startd = [] +cli-tunnel = [] +default = ["cli", "startd", "registry", "cli-container", "tunnel"] dev = [] -unstable = ["console-subscriber", "tokio/tracing"] docker = [] +registry = [] +startd = ["mail-send"] test = [] +tunnel = [] +unstable = ["console-subscriber", "tokio/tracing"] [dependencies] +arti-client = { version = "0.33", features = [ + "compression", + "experimental-api", + "rustls", + "static", + "tokio", + "ephemeral-keystore", + "onion-service-client", + "onion-service-service", +], default-features = false, git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" } aes = { version = "0.7.5", features = ["ctr"] } async-acme = { version = "0.6.0", git = "https://github.com/dr-bonez/async-acme.git", features = [ "use_rustls", @@ -81,8 +99,9 @@ cookie_store = "0.21.0" der = { version = "0.7.9", features = ["derive", "pem"] } digest = "0.10.7" divrem = "1.0.0" +dns-lookup = "2.1.0" ed25519 = { version = "2.2.3", features = ["pkcs8", "pem", "alloc"] } -ed25519-dalek = { version = "2.1.1", features = [ +ed25519-dalek = { version = "2.2.0", features = [ "serde", "zeroize", "rand_core", @@ -99,6 +118,8 @@ futures = "0.3.28" gpt = "4.1.0" helpers = { path = "../helpers" } hex = "0.4.3" +hickory-client = "0.25.2" +hickory-server = "0.25.2" hmac = "0.12.1" http = "1.0.0" http-body-util = "0.1" @@ -116,14 +137,14 @@ id-pool = { version = "0.2.2", default-features = false, features = [ "serde", "u16", ] } -imbl = "4.0.1" -imbl-value = "0.3.2" +imbl = { version = "6", features = ["serde", "small-chunks"] } +imbl-value = { version = "0.4.3", features = ["ts-rs"] } include_dir = { version = "0.7.3", features = ["metadata"] } indexmap = { version = "2.0.2", features = ["serde"] } indicatif = { version = "0.17.7", features = ["tokio"] } +inotify = "0.11.0" integer-encoding = { version = "4.0.0", features = ["tokio_async"] } ipnet = { version = "2.8.0", features = ["serde"] } -iprange = { version = "0.6.7", features = ["serde"] } isocountry = "0.3.2" itertools = "0.14.0" jaq-core = "0.10.1" @@ -168,7 +189,7 @@ proptest = "1.3.1" proptest-derive = "0.5.0" pty-process = { version = "0.5.1", optional = true } qrcode = "0.14.1" -rand = "0.9.0" +rand = "0.9.2" regex = "1.10.2" reqwest = { version = "0.12.4", features = ["stream", "json", "socks"] } reqwest_cookie_store = "0.8.0" @@ -176,6 +197,7 @@ rpassword = "7.2.0" rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "master" } rust-argon2 = "2.0.0" rustyline-async = "0.4.1" +safelog = { version = "0.4.8", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" } semver = { version = "1.0.20", features = ["serde"] } serde = { version = "1.0", features = ["derive", "rc"] } serde_cbor = { package = "ciborium", version = "0.2.1" } @@ -189,12 +211,12 @@ sha2 = "0.10.2" shell-words = "1" signal-hook = "0.3.17" simple-logging = "2.0.2" -socket2 = "0.5.7" +socket2 = { version = "0.6.0", features = ["all"] } +socks5-impl = { version = "0.7.2", features = ["server"] } sqlx = { version = "0.8.6", features = [ - "chrono", "runtime-tokio-rustls", "postgres", -] } +], default-features = false } sscanf = "0.4.1" ssh-key = { version = "0.6.2", features = ["ed25519"] } tar = "0.4.40" @@ -203,22 +225,33 @@ thiserror = "2.0.12" textwrap = "0.16.1" tokio = { version = "1.38.1", features = ["full"] } tokio-rustls = "0.26.0" -tokio-socks = "0.5.1" tokio-stream = { version = "0.1.14", features = ["io-util", "sync", "net"] } tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" } tokio-tungstenite = { version = "0.26.2", features = ["native-tls", "url"] } tokio-util = { version = "0.7.9", features = ["io"] } -torut = { git = "https://github.com/Start9Labs/torut.git", branch = "update/dependencies", features = [ - "serialize", -] } +tor-cell = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" } +tor-hscrypto = { version = "0.33", features = [ + "full", +], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" } +tor-hsservice = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" } +tor-keymgr = { version = "0.33", features = [ + "ephemeral-keystore", +], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" } +tor-llcrypto = { version = "0.33", features = [ + "full", +], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" } +tor-proto = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" } +tor-rtcompat = { version = "0.33", features = [ + "tokio", + "rustls", +], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" } tower-service = "0.3.3" tracing = "0.1.39" tracing-error = "0.2.0" tracing-futures = "0.2.5" tracing-journald = "0.3.0" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } -trust-dns-server = "0.23.1" -ts-rs = { git = "https://github.com/dr-bonez/ts-rs.git", branch = "feature/top-level-as" } # "8.1.0" +ts-rs = "9.0.1" typed-builder = "0.21.0" unix-named-pipe = "0.2.0" url = { version = "2.4.1", features = ["serde"] } diff --git a/core/startos/src/account.rs b/core/startos/src/account.rs index 46fd2c1f9..58d4649e0 100644 --- a/core/startos/src/account.rs +++ b/core/startos/src/account.rs @@ -1,12 +1,13 @@ use std::time::SystemTime; +use imbl_value::InternedString; use openssl::pkey::{PKey, Private}; use openssl::x509::X509; -use torut::onion::TorSecretKeyV3; use crate::db::model::DatabaseModel; -use crate::hostname::{generate_hostname, generate_id, Hostname}; +use crate::hostname::{Hostname, generate_hostname, generate_id}; use crate::net::ssl::{generate_key, make_root_cert}; +use crate::net::tor::TorSecretKey; use crate::prelude::*; use crate::util::serde::Pem; @@ -19,28 +20,28 @@ fn hash_password(password: &str) -> Result { .with_kind(crate::ErrorKind::PasswordHashGeneration) } -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct AccountInfo { pub server_id: String, pub hostname: Hostname, pub password: String, - pub tor_keys: Vec, + pub tor_keys: Vec, pub root_ca_key: PKey, pub root_ca_cert: X509, pub ssh_key: ssh_key::PrivateKey, - pub compat_s9pk_key: ed25519_dalek::SigningKey, + pub developer_key: ed25519_dalek::SigningKey, } impl AccountInfo { pub fn new(password: &str, start_time: SystemTime) -> Result { let server_id = generate_id(); let hostname = generate_hostname(); - let tor_key = vec![TorSecretKeyV3::generate()]; + let tor_key = vec![TorSecretKey::generate()]; let root_ca_key = generate_key()?; let root_ca_cert = make_root_cert(&root_ca_key, &hostname, start_time)?; let ssh_key = ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::random( &mut ssh_key::rand_core::OsRng::default(), )); - let compat_s9pk_key = + let developer_key = ed25519_dalek::SigningKey::generate(&mut ssh_key::rand_core::OsRng::default()); Ok(Self { server_id, @@ -50,7 +51,7 @@ impl AccountInfo { root_ca_key, root_ca_cert, ssh_key, - compat_s9pk_key, + developer_key, }) } @@ -74,7 +75,7 @@ impl AccountInfo { let root_ca_key = cert_store.as_root_key().de()?.0; let root_ca_cert = cert_store.as_root_cert().de()?.0; let ssh_key = db.as_private().as_ssh_privkey().de()?.0; - let compat_s9pk_key = db.as_private().as_compat_s9pk_key().de()?.0; + let compat_s9pk_key = db.as_private().as_developer_key().de()?.0; Ok(Self { server_id, @@ -84,7 +85,7 @@ impl AccountInfo { root_ca_key, root_ca_cert, ssh_key, - compat_s9pk_key, + developer_key: compat_s9pk_key, }) } @@ -103,7 +104,7 @@ impl AccountInfo { &self .tor_keys .iter() - .map(|tor_key| tor_key.public().get_onion_address()) + .map(|tor_key| tor_key.onion_address()) .collect(), )?; db.as_private_mut().as_password_mut().ser(&self.password)?; @@ -111,8 +112,8 @@ impl AccountInfo { .as_ssh_privkey_mut() .ser(Pem::new_ref(&self.ssh_key))?; db.as_private_mut() - .as_compat_s9pk_key_mut() - .ser(Pem::new_ref(&self.compat_s9pk_key))?; + .as_developer_key_mut() + .ser(Pem::new_ref(&self.developer_key))?; let key_store = db.as_private_mut().as_key_store_mut(); for tor_key in &self.tor_keys { key_store.as_onion_mut().insert_key(tor_key)?; @@ -131,4 +132,17 @@ impl AccountInfo { self.password = hash_password(password)?; Ok(()) } + + pub fn hostnames(&self) -> impl IntoIterator + Send + '_ { + [ + self.hostname.no_dot_host_name(), + self.hostname.local_domain_name(), + ] + .into_iter() + .chain( + self.tor_keys + .iter() + .map(|k| InternedString::from_display(&k.onion_address())), + ) + } } diff --git a/core/startos/src/action.rs b/core/startos/src/action.rs index 4079ca7ad..4729a3fb9 100644 --- a/core/startos/src/action.rs +++ b/core/startos/src/action.rs @@ -52,6 +52,8 @@ pub fn action_api() -> ParentHandler { #[ts(export)] #[serde(rename_all = "camelCase")] pub struct ActionInput { + #[serde(default)] + pub event_id: Guid, #[ts(type = "Record")] pub spec: Value, #[ts(type = "Record | null")] @@ -270,6 +272,7 @@ pub fn display_action_result( #[serde(rename_all = "camelCase")] pub struct RunActionParams { pub package_id: PackageId, + pub event_id: Option, pub action_id: ActionId, #[ts(optional, type = "any")] pub input: Option, @@ -278,6 +281,7 @@ pub struct RunActionParams { #[derive(Parser)] struct CliRunActionParams { pub package_id: PackageId, + pub event_id: Option, pub action_id: ActionId, #[command(flatten)] pub input: StdinDeserializable>, @@ -286,12 +290,14 @@ impl From for RunActionParams { fn from( CliRunActionParams { package_id, + event_id, action_id, input, }: CliRunActionParams, ) -> Self { Self { package_id, + event_id, action_id, input: input.0, } @@ -331,6 +337,7 @@ pub async fn run_action( ctx: RpcContext, RunActionParams { package_id, + event_id, action_id, input, }: RunActionParams, @@ -340,7 +347,11 @@ pub async fn run_action( .await .as_ref() .or_not_found(lazy_format!("Manager for {}", package_id))? - .run_action(Guid::new(), action_id, input.unwrap_or_default()) + .run_action( + event_id.unwrap_or_default(), + action_id, + input.unwrap_or_default(), + ) .await .map(|res| res.map(ActionResult::upcast)) } diff --git a/core/startos/src/auth.rs b/core/startos/src/auth.rs index 1249bb984..a49c8f2ab 100644 --- a/core/startos/src/auth.rs +++ b/core/startos/src/auth.rs @@ -3,29 +3,27 @@ use std::collections::BTreeMap; use chrono::{DateTime, Utc}; use clap::Parser; use color_eyre::eyre::eyre; -use imbl_value::{json, InternedString}; +use imbl_value::{InternedString, json}; use itertools::Itertools; use josekit::jwk::Jwk; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; +use rpc_toolkit::{CallRemote, Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use tokio::io::AsyncWriteExt; use tracing::instrument; use ts_rs::TS; use crate::context::{CliContext, RpcContext}; -use crate::db::model::DatabaseModel; use crate::middleware::auth::{ - AsLogoutSessionId, HasLoggedOutSessions, HashSessionToken, LoginRes, + AsLogoutSessionId, AuthContext, HasLoggedOutSessions, HashSessionToken, LoginRes, }; use crate::prelude::*; use crate::util::crypto::EncryptedWire; use crate::util::io::create_file_mod; -use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; -use crate::{ensure_code, Error, ResultExt}; +use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable}; +use crate::{Error, ResultExt, ensure_code}; #[derive(Debug, Clone, Default, Deserialize, Serialize, TS)] -#[ts(as = "BTreeMap::")] pub struct Sessions(pub BTreeMap); impl Sessions { pub fn new() -> Self { @@ -112,31 +110,34 @@ impl std::str::FromStr for PasswordType { }) } } -pub fn auth() -> ParentHandler { +pub fn auth() -> ParentHandler +where + CliContext: CallRemote, +{ ParentHandler::new() .subcommand( "login", - from_fn_async(login_impl) + from_fn_async(login_impl::) .with_metadata("login", Value::Bool(true)) .no_cli(), ) .subcommand( "login", - from_fn_async(cli_login) + from_fn_async(cli_login::) .no_display() - .with_about("Log in to StartOS server"), + .with_about("Log in a new auth session"), ) .subcommand( "logout", - from_fn_async(logout) + from_fn_async(logout::) .with_metadata("get_session", Value::Bool(true)) .no_display() - .with_about("Log out of StartOS server") + .with_about("Log out of current auth session") .with_call_remote::(), ) .subcommand( "session", - session::().with_about("List or kill StartOS sessions"), + session::().with_about("List or kill auth sessions"), ) .subcommand( "reset-password", @@ -146,7 +147,7 @@ pub fn auth() -> ParentHandler { "reset-password", from_fn_async(cli_reset_password) .no_display() - .with_about("Reset StartOS password"), + .with_about("Reset password"), ) .subcommand( "get-pubkey", @@ -172,17 +173,20 @@ fn gen_pwd() { } #[instrument(skip_all)] -async fn cli_login( +async fn cli_login( HandlerArgs { context: ctx, parent_method, method, .. }: HandlerArgs, -) -> Result<(), RpcError> { +) -> Result<(), RpcError> +where + CliContext: CallRemote, +{ let password = rpassword::prompt_password("Password: ")?; - ctx.call_remote::( + ctx.call_remote::( &parent_method.into_iter().chain(method).join("."), json!({ "password": password, @@ -210,17 +214,11 @@ pub fn check_password(hash: &str, password: &str) -> Result<(), Error> { Ok(()) } -pub fn check_password_against_db(db: &DatabaseModel, password: &str) -> Result<(), Error> { - let pw_hash = db.as_private().as_password().de()?; - check_password(&pw_hash, password)?; - Ok(()) -} - #[derive(Deserialize, Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct LoginParams { - password: Option, + password: String, #[ts(skip)] #[serde(rename = "__auth_userAgent")] // from Auth middleware user_agent: Option, @@ -229,20 +227,18 @@ pub struct LoginParams { } #[instrument(skip_all)] -pub async fn login_impl( - ctx: RpcContext, +pub async fn login_impl( + ctx: C, LoginParams { password, user_agent, ephemeral, }: LoginParams, ) -> Result { - let password = password.unwrap_or_default().decrypt(&ctx)?; - let tok = if ephemeral { - check_password_against_db(&ctx.db.peek().await, &password)?; + C::check_password(&ctx.db().peek().await, &password)?; let hash_token = HashSessionToken::new(); - ctx.ephemeral_sessions.mutate(|s| { + ctx.ephemeral_sessions().mutate(|s| { s.0.insert( hash_token.hashed().clone(), Session { @@ -254,11 +250,11 @@ pub async fn login_impl( }); Ok(hash_token.to_login_res()) } else { - ctx.db + ctx.db() .mutate(|db| { - check_password_against_db(db, &password)?; + C::check_password(db, &password)?; let hash_token = HashSessionToken::new(); - db.as_private_mut().as_sessions_mut().insert( + C::access_sessions(db).insert( hash_token.hashed(), &Session { logged_in: Utc::now(), @@ -273,12 +269,7 @@ pub async fn login_impl( .result }?; - if tokio::fs::metadata("/media/startos/config/overlay/etc/shadow") - .await - .is_err() - { - write_shadow(&password).await?; - } + ctx.post_login_hook(&password).await?; Ok(tok) } @@ -292,8 +283,8 @@ pub struct LogoutParams { session: InternedString, } -pub async fn logout( - ctx: RpcContext, +pub async fn logout( + ctx: C, LogoutParams { session }: LogoutParams, ) -> Result, Error> { Ok(Some( @@ -321,22 +312,25 @@ pub struct SessionList { sessions: Sessions, } -pub fn session() -> ParentHandler { +pub fn session() -> ParentHandler +where + CliContext: CallRemote, +{ ParentHandler::new() .subcommand( "list", - from_fn_async(list) + from_fn_async(list::) .with_metadata("get_session", Value::Bool(true)) .with_display_serializable() .with_custom_display_fn(|handle, result| display_sessions(handle.params, result)) - .with_about("Display all server sessions") + .with_about("Display all auth sessions") .with_call_remote::(), ) .subcommand( "kill", - from_fn_async(kill) + from_fn_async(kill::) .no_display() - .with_about("Terminate existing server session(s)") + .with_about("Terminate existing auth session(s)") .with_call_remote::(), ) } @@ -385,12 +379,12 @@ pub struct ListParams { // #[command(display(display_sessions))] #[instrument(skip_all)] -pub async fn list( - ctx: RpcContext, +pub async fn list( + ctx: C, ListParams { session, .. }: ListParams, ) -> Result { - let mut sessions = ctx.db.peek().await.into_private().into_sessions().de()?; - ctx.ephemeral_sessions.peek(|s| { + let mut sessions = C::access_sessions(&mut ctx.db().peek().await).de()?; + ctx.ephemeral_sessions().peek(|s| { sessions .0 .extend(s.0.iter().map(|(k, v)| (k.clone(), v.clone()))) @@ -424,7 +418,7 @@ pub struct KillParams { } #[instrument(skip_all)] -pub async fn kill(ctx: RpcContext, KillParams { ids }: KillParams) -> Result<(), Error> { +pub async fn kill(ctx: C, KillParams { ids }: KillParams) -> Result<(), Error> { HasLoggedOutSessions::new(ids.into_iter().map(KillSessionId::new), &ctx).await?; Ok(()) } diff --git a/core/startos/src/backup/backup_bulk.rs b/core/startos/src/backup/backup_bulk.rs index 5ece0cf7d..28170eb9b 100644 --- a/core/startos/src/backup/backup_bulk.rs +++ b/core/startos/src/backup/backup_bulk.rs @@ -13,9 +13,8 @@ use tokio::io::AsyncWriteExt; use tracing::instrument; use ts_rs::TS; -use super::target::{BackupTargetId, PackageBackupInfo}; use super::PackageBackupReport; -use crate::auth::check_password_against_db; +use super::target::{BackupTargetId, PackageBackupInfo}; use crate::backup::os::OsBackup; use crate::backup::{BackupReport, ServerBackupReport}; use crate::context::RpcContext; @@ -24,7 +23,8 @@ use crate::db::model::{Database, DatabaseModel}; use crate::disk::mount::backup::BackupMountGuard; use crate::disk::mount::filesystem::ReadWrite; use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard}; -use crate::notifications::{notify, NotificationLevel}; +use crate::middleware::auth::AuthContext; +use crate::notifications::{NotificationLevel, notify}; use crate::prelude::*; use crate::util::io::dir_copy; use crate::util::serde::IoFormat; @@ -170,7 +170,7 @@ pub async fn backup_all( let ((fs, package_ids, server_id), status_guard) = ( ctx.db .mutate(|db| { - check_password_against_db(db, &password)?; + RpcContext::check_password(db, &password)?; let fs = target_id.load(db)?; let package_ids = if let Some(ids) = package_ids { ids.into_iter().collect() diff --git a/core/startos/src/backup/mod.rs b/core/startos/src/backup/mod.rs index 110a918b6..b68a2fd9d 100644 --- a/core/startos/src/backup/mod.rs +++ b/core/startos/src/backup/mod.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use chrono::{DateTime, Utc}; use models::{HostId, PackageId}; use reqwest::Url; -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use crate::context::CliContext; diff --git a/core/startos/src/backup/os.rs b/core/startos/src/backup/os.rs index 4c5798103..380772b77 100644 --- a/core/startos/src/backup/os.rs +++ b/core/startos/src/backup/os.rs @@ -4,10 +4,10 @@ use openssl::x509::X509; use patch_db::Value; use serde::{Deserialize, Serialize}; use ssh_key::private::Ed25519Keypair; -use torut::onion::TorSecretKeyV3; use crate::account::AccountInfo; -use crate::hostname::{generate_hostname, generate_id, Hostname}; +use crate::hostname::{Hostname, generate_hostname, generate_id}; +use crate::net::tor::TorSecretKey; use crate::prelude::*; use crate::util::crypto::ed25519_expand_key; use crate::util::serde::{Base32, Base64, Pem}; @@ -36,7 +36,7 @@ impl<'de> Deserialize<'de> for OsBackup { v => { return Err(serde::de::Error::custom(&format!( "Unknown backup version {v}" - ))) + ))); } }) } @@ -85,8 +85,11 @@ impl OsBackupV0 { &mut ssh_key::rand_core::OsRng::default(), ssh_key::Algorithm::Ed25519, )?, - tor_keys: vec![TorSecretKeyV3::from(self.tor_key.0)], - compat_s9pk_key: ed25519_dalek::SigningKey::generate( + tor_keys: TorSecretKey::from_bytes(self.tor_key.0) + .ok() + .into_iter() + .collect(), + developer_key: ed25519_dalek::SigningKey::generate( &mut ssh_key::rand_core::OsRng::default(), ), }, @@ -116,8 +119,11 @@ impl OsBackupV1 { root_ca_key: self.root_ca_key.0, root_ca_cert: self.root_ca_cert.0, ssh_key: ssh_key::PrivateKey::from(Ed25519Keypair::from_seed(&self.net_key.0)), - tor_keys: vec![TorSecretKeyV3::from(ed25519_expand_key(&self.net_key.0))], - compat_s9pk_key: ed25519_dalek::SigningKey::from_bytes(&self.net_key), + tor_keys: TorSecretKey::from_bytes(ed25519_expand_key(&self.net_key.0)) + .ok() + .into_iter() + .collect(), + developer_key: ed25519_dalek::SigningKey::from_bytes(&self.net_key), }, ui: self.ui, } @@ -134,7 +140,7 @@ struct OsBackupV2 { root_ca_key: Pem>, // PEM Encoded OpenSSL Key root_ca_cert: Pem, // PEM Encoded OpenSSL X509 Certificate ssh_key: Pem, // PEM Encoded OpenSSH Key - tor_keys: Vec, // Base64 Encoded Ed25519 Expanded Secret Key + tor_keys: Vec, // Base64 Encoded Ed25519 Expanded Secret Key compat_s9pk_key: Pem, // PEM Encoded ED25519 Key ui: Value, // JSON Value } @@ -149,7 +155,7 @@ impl OsBackupV2 { root_ca_cert: self.root_ca_cert.0, ssh_key: self.ssh_key.0, tor_keys: self.tor_keys, - compat_s9pk_key: self.compat_s9pk_key.0, + developer_key: self.compat_s9pk_key.0, }, ui: self.ui, } @@ -162,7 +168,7 @@ impl OsBackupV2 { root_ca_cert: Pem(backup.account.root_ca_cert.clone()), ssh_key: Pem(backup.account.ssh_key.clone()), tor_keys: backup.account.tor_keys.clone(), - compat_s9pk_key: Pem(backup.account.compat_s9pk_key.clone()), + compat_s9pk_key: Pem(backup.account.developer_key.clone()), ui: backup.ui.clone(), } } diff --git a/core/startos/src/backup/restore.rs b/core/startos/src/backup/restore.rs index 1885e198e..d8d536aea 100644 --- a/core/startos/src/backup/restore.rs +++ b/core/startos/src/backup/restore.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::sync::Arc; use clap::Parser; -use futures::{stream, StreamExt}; +use futures::{StreamExt, stream}; use models::PackageId; use patch_db::json_ptr::ROOT; use serde::{Deserialize, Serialize}; @@ -11,6 +11,7 @@ use tracing::instrument; use ts_rs::TS; use super::target::BackupTargetId; +use crate::PLATFORM; use crate::backup::os::OsBackup; use crate::context::setup::SetupResult; use crate::context::{RpcContext, SetupContext}; @@ -26,7 +27,6 @@ use crate::service::service_map::DownloadInstallFuture; use crate::setup::SetupExecuteProgress; use crate::system::sync_kiosk; use crate::util::serde::IoFormat; -use crate::PLATFORM; #[derive(Deserialize, Serialize, Parser, TS)] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/backup/target/cifs.rs b/core/startos/src/backup/target/cifs.rs index 63e18d0d8..350e53220 100644 --- a/core/startos/src/backup/target/cifs.rs +++ b/core/startos/src/backup/target/cifs.rs @@ -4,17 +4,17 @@ use std::path::{Path, PathBuf}; use clap::Parser; use color_eyre::eyre::eyre; use imbl_value::InternedString; -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; use super::{BackupTarget, BackupTargetId}; use crate::context::{CliContext, RpcContext}; use crate::db::model::DatabaseModel; -use crate::disk::mount::filesystem::cifs::Cifs; use crate::disk::mount::filesystem::ReadOnly; +use crate::disk::mount::filesystem::cifs::Cifs; use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard}; -use crate::disk::util::{recovery_info, StartOsRecoveryInfo}; +use crate::disk::util::{StartOsRecoveryInfo, recovery_info}; use crate::prelude::*; use crate::util::serde::KeyVal; diff --git a/core/startos/src/backup/target/mod.rs b/core/startos/src/backup/target/mod.rs index 71df2925f..3eb37e12e 100644 --- a/core/startos/src/backup/target/mod.rs +++ b/core/startos/src/backup/target/mod.rs @@ -2,15 +2,15 @@ use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use chrono::{DateTime, Utc}; -use clap::builder::ValueParserFactory; use clap::Parser; +use clap::builder::ValueParserFactory; use color_eyre::eyre::eyre; -use digest::generic_array::GenericArray; use digest::OutputSizeUser; +use digest::generic_array::GenericArray; use exver::Version; use imbl_value::InternedString; use models::{FromStrParser, PackageId}; -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use sha2::Sha256; use tokio::sync::Mutex; @@ -27,10 +27,10 @@ use crate::disk::mount::filesystem::{FileSystem, MountType, ReadWrite}; use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard}; use crate::disk::util::PartitionInfo; use crate::prelude::*; -use crate::util::serde::{ - deserialize_from_str, display_serializable, serialize_display, HandlerExtSerde, WithIoFormat, -}; use crate::util::VersionString; +use crate::util::serde::{ + HandlerExtSerde, WithIoFormat, deserialize_from_str, display_serializable, serialize_display, +}; pub mod cifs; diff --git a/core/startos/src/bins/mod.rs b/core/startos/src/bins/mod.rs index 6ffecfce9..2308e06d3 100644 --- a/core/startos/src/bins/mod.rs +++ b/core/startos/src/bins/mod.rs @@ -2,41 +2,64 @@ use std::collections::VecDeque; use std::ffi::OsString; use std::path::Path; -#[cfg(feature = "container-runtime")] +#[cfg(feature = "cli-container")] pub mod container_cli; pub mod deprecated; -#[cfg(feature = "registry")] +#[cfg(any(feature = "registry", feature = "cli-registry"))] pub mod registry; #[cfg(feature = "cli")] pub mod start_cli; -#[cfg(feature = "daemon")] +#[cfg(feature = "startd")] pub mod start_init; -#[cfg(feature = "daemon")] +#[cfg(feature = "startd")] pub mod startd; +#[cfg(any(feature = "tunnel", feature = "cli-tunnel"))] +pub mod tunnel; fn select_executable(name: &str) -> Option)> { match name { - #[cfg(feature = "cli")] - "start-cli" => Some(start_cli::main), - #[cfg(feature = "container-runtime")] - "start-cli" => Some(container_cli::main), - #[cfg(feature = "daemon")] + #[cfg(feature = "startd")] "startd" => Some(startd::main), - #[cfg(feature = "registry")] - "registry" => Some(registry::main), - "embassy-cli" => Some(|_| deprecated::renamed("embassy-cli", "start-cli")), - "embassy-sdk" => Some(|_| deprecated::renamed("embassy-sdk", "start-sdk")), + #[cfg(feature = "startd")] "embassyd" => Some(|_| deprecated::renamed("embassyd", "startd")), + #[cfg(feature = "startd")] "embassy-init" => Some(|_| deprecated::removed("embassy-init")), + + #[cfg(feature = "cli-startd")] + "start-cli" => Some(start_cli::main), + #[cfg(feature = "cli-startd")] + "embassy-cli" => Some(|_| deprecated::renamed("embassy-cli", "start-cli")), + #[cfg(feature = "cli-startd")] + "embassy-sdk" => Some(|_| deprecated::removed("embassy-sdk")), + + #[cfg(feature = "cli-container")] + "start-container" => Some(container_cli::main), + + #[cfg(feature = "registry")] + "start-registryd" => Some(registry::main), + #[cfg(feature = "cli-registry")] + "start-registry" => Some(registry::cli), + + #[cfg(feature = "tunnel")] + "start-tunneld" => Some(tunnel::main), + #[cfg(feature = "cli-tunnel")] + "start-tunnel" => Some(tunnel::cli), + "contents" => Some(|_| { - #[cfg(feature = "cli")] - println!("start-cli"); - #[cfg(feature = "container-runtime")] - println!("start-cli (container)"); - #[cfg(feature = "daemon")] + #[cfg(feature = "startd")] println!("startd"); + #[cfg(feature = "cli-startd")] + println!("start-cli"); + #[cfg(feature = "cli-container")] + println!("start-container"); #[cfg(feature = "registry")] - println!("registry"); + println!("start-registryd"); + #[cfg(feature = "cli-registry")] + println!("start-registry"); + #[cfg(feature = "tunnel")] + println!("start-tunneld"); + #[cfg(feature = "cli-tunnel")] + println!("start-tunnel"); }), _ => None, } diff --git a/core/startos/src/bins/registry.rs b/core/startos/src/bins/registry.rs index a71b737af..2aacebd39 100644 --- a/core/startos/src/bins/registry.rs +++ b/core/startos/src/bins/registry.rs @@ -2,9 +2,12 @@ use std::ffi::OsString; use clap::Parser; use futures::FutureExt; +use rpc_toolkit::CliApp; use tokio::signal::unix::signal; use tracing::instrument; +use crate::context::CliContext; +use crate::context::config::ClientConfig; use crate::net::web_server::{Acceptor, WebServer}; use crate::prelude::*; use crate::registry::context::{RegistryConfig, RegistryContext}; @@ -85,3 +88,30 @@ pub fn main(args: impl IntoIterator) { } } } + +pub fn cli(args: impl IntoIterator) { + LOGGER.enable(); + + if let Err(e) = CliApp::new( + |cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?), + crate::registry::registry_api(), + ) + .run(args) + { + match e.data { + Some(serde_json::Value::String(s)) => eprintln!("{}: {}", e.message, s), + Some(serde_json::Value::Object(o)) => { + if let Some(serde_json::Value::String(s)) = o.get("details") { + eprintln!("{}: {}", e.message, s); + if let Some(serde_json::Value::String(s)) = o.get("debug") { + tracing::debug!("{}", s) + } + } + } + Some(a) => eprintln!("{}: {}", e.message, a), + None => eprintln!("{}", e.message), + } + + std::process::exit(e.code); + } +} diff --git a/core/startos/src/bins/start_cli.rs b/core/startos/src/bins/start_cli.rs index bda5e00d3..b3b10cf71 100644 --- a/core/startos/src/bins/start_cli.rs +++ b/core/startos/src/bins/start_cli.rs @@ -3,8 +3,8 @@ use std::ffi::OsString; use rpc_toolkit::CliApp; use serde_json::Value; -use crate::context::config::ClientConfig; use crate::context::CliContext; +use crate::context::config::ClientConfig; use crate::util::logger::LOGGER; use crate::version::{Current, VersionT}; diff --git a/core/startos/src/bins/start_init.rs b/core/startos/src/bins/start_init.rs index fdd0e075d..70c32ed1a 100644 --- a/core/startos/src/bins/start_init.rs +++ b/core/startos/src/bins/start_init.rs @@ -1,4 +1,3 @@ -use std::path::Path; use std::sync::Arc; use tokio::process::Command; @@ -7,9 +6,9 @@ use tracing::instrument; use crate::context::config::ServerConfig; use crate::context::rpc::InitRpcContextPhases; use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext}; +use crate::disk::REPAIR_DISK_PATH; use crate::disk::fsck::RepairStrategy; use crate::disk::main::DEFAULT_PASSWORD; -use crate::disk::REPAIR_DISK_PATH; use crate::firmware::{check_for_firmware_update, update_firmware}; use crate::init::{InitPhases, STANDBY_MODE_PATH}; use crate::net::web_server::{UpgradableListener, WebServer}; @@ -48,7 +47,7 @@ async fn setup_or_init( update_phase.complete(); reboot_phase.start(); return Ok(Err(Shutdown { - export_args: None, + disk_guid: None, restart: true, })); } @@ -103,7 +102,7 @@ async fn setup_or_init( .expect("context dropped"); return Ok(Err(Shutdown { - export_args: None, + disk_guid: None, restart: true, })); } @@ -117,7 +116,9 @@ async fn setup_or_init( server.serve_setup(ctx.clone()); let mut shutdown = ctx.shutdown.subscribe(); - shutdown.recv().await.expect("context dropped"); + if let Some(shutdown) = shutdown.recv().await.expect("context dropped") { + return Ok(Err(shutdown)); + } tokio::task::yield_now().await; if let Err(e) = Command::new("killall") @@ -136,7 +137,7 @@ async fn setup_or_init( return Err(Error::new( eyre!("Setup mode exited before setup completed"), ErrorKind::Unknown, - )) + )); } })) } else { @@ -183,7 +184,7 @@ async fn setup_or_init( let mut reboot_phase = handle.add_phase("Rebooting".into(), Some(1)); reboot_phase.start(); return Ok(Err(Shutdown { - export_args: Some((disk_guid, Path::new(DATA_DIR).to_owned())), + disk_guid: Some(disk_guid), restart: true, })); } diff --git a/core/startos/src/bins/startd.rs b/core/startos/src/bins/startd.rs index 0ec28d67d..25bf5ac52 100644 --- a/core/startos/src/bins/startd.rs +++ b/core/startos/src/bins/startd.rs @@ -12,7 +12,7 @@ use tracing::instrument; use crate::context::config::ServerConfig; use crate::context::rpc::InitRpcContextPhases; use crate::context::{DiagnosticContext, InitContext, RpcContext}; -use crate::net::network_interface::SelfContainedNetworkInterfaceListener; +use crate::net::gateway::SelfContainedNetworkInterfaceListener; use crate::net::web_server::{Acceptor, UpgradableListener, WebServer}; use crate::shutdown::Shutdown; use crate::system::launch_metrics_task; @@ -144,7 +144,7 @@ pub fn main(args: impl IntoIterator) { let res = { let rt = tokio::runtime::Builder::new_multi_thread() - .worker_threads(max(4, num_cpus::get())) + .worker_threads(max(1, num_cpus::get())) .enable_all() .build() .expect("failed to initialize runtime"); diff --git a/core/startos/src/bins/tunnel.rs b/core/startos/src/bins/tunnel.rs new file mode 100644 index 000000000..d1deede3f --- /dev/null +++ b/core/startos/src/bins/tunnel.rs @@ -0,0 +1,117 @@ +use std::ffi::OsString; + +use clap::Parser; +use futures::FutureExt; +use rpc_toolkit::CliApp; +use tokio::signal::unix::signal; +use tracing::instrument; + +use crate::context::CliContext; +use crate::context::config::ClientConfig; +use crate::net::web_server::{Acceptor, WebServer}; +use crate::prelude::*; +use crate::tunnel::context::{TunnelConfig, TunnelContext}; +use crate::util::logger::LOGGER; + +#[instrument(skip_all)] +async fn inner_main(config: &TunnelConfig) -> Result<(), Error> { + let server = async { + let ctx = TunnelContext::init(config).await?; + let mut server = WebServer::new(Acceptor::bind([ctx.listen]).await?); + server.serve_tunnel(ctx.clone()); + + let mut shutdown_recv = ctx.shutdown.subscribe(); + + let sig_handler_ctx = ctx; + let sig_handler = tokio::spawn(async move { + use tokio::signal::unix::SignalKind; + futures::future::select_all( + [ + SignalKind::interrupt(), + SignalKind::quit(), + SignalKind::terminate(), + ] + .iter() + .map(|s| { + async move { + signal(*s) + .unwrap_or_else(|_| panic!("register {:?} handler", s)) + .recv() + .await + } + .boxed() + }), + ) + .await; + sig_handler_ctx + .shutdown + .send(()) + .map_err(|_| ()) + .expect("send shutdown signal"); + }); + + shutdown_recv + .recv() + .await + .with_kind(crate::ErrorKind::Unknown)?; + + sig_handler.abort(); + + Ok::<_, Error>(server) + } + .await?; + server.shutdown().await; + + Ok(()) +} + +pub fn main(args: impl IntoIterator) { + LOGGER.enable(); + + let config = TunnelConfig::parse_from(args).load().unwrap(); + + let res = { + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("failed to initialize runtime"); + rt.block_on(inner_main(&config)) + }; + + match res { + Ok(()) => (), + Err(e) => { + eprintln!("{}", e.source); + tracing::debug!("{:?}", e.source); + drop(e.source); + std::process::exit(e.kind as i32) + } + } +} + +pub fn cli(args: impl IntoIterator) { + LOGGER.enable(); + + if let Err(e) = CliApp::new( + |cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?), + crate::tunnel::api::tunnel_api(), + ) + .run(args) + { + match e.data { + Some(serde_json::Value::String(s)) => eprintln!("{}: {}", e.message, s), + Some(serde_json::Value::Object(o)) => { + if let Some(serde_json::Value::String(s)) = o.get("details") { + eprintln!("{}: {}", e.message, s); + if let Some(serde_json::Value::String(s)) = o.get("debug") { + tracing::debug!("{}", s) + } + } + } + Some(a) => eprintln!("{}: {}", e.message, a), + None => eprintln!("{}", e.message), + } + + std::process::exit(e.code); + } +} diff --git a/core/startos/src/context/cli.rs b/core/startos/src/context/cli.rs index dff20ddc1..e93963f56 100644 --- a/core/startos/src/context/cli.rs +++ b/core/startos/src/context/cli.rs @@ -1,27 +1,32 @@ use std::fs::File; use std::io::BufReader; +use std::net::SocketAddr; use std::path::{Path, PathBuf}; use std::sync::Arc; -use cookie_store::{CookieStore, RawCookie}; +use cookie::{Cookie, Expiration, SameSite}; +use cookie_store::CookieStore; +use imbl_value::InternedString; use josekit::jwk::Jwk; use once_cell::sync::OnceCell; use reqwest::Proxy; use reqwest_cookie_store::CookieStoreMutex; use rpc_toolkit::reqwest::{Client, Url}; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{call_remote_http, CallRemote, Context, Empty}; +use rpc_toolkit::{CallRemote, Context, Empty}; use tokio::net::TcpStream; use tokio::runtime::Runtime; use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; use tracing::instrument; use super::setup::CURRENT_SECRET; -use crate::context::config::{local_config_path, ClientConfig}; +use crate::context::config::{ClientConfig, local_config_path}; use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext}; -use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH; +use crate::developer::{OS_DEVELOPER_KEY_PATH, default_developer_key_path}; +use crate::middleware::auth::AuthContext; use crate::prelude::*; use crate::rpc_continuations::Guid; +use crate::tunnel::context::TunnelContext; #[derive(Debug)] pub struct CliContextSeed { @@ -29,6 +34,10 @@ pub struct CliContextSeed { pub base_url: Url, pub rpc_url: Url, pub registry_url: Option, + pub registry_hostname: Option, + pub registry_listen: Option, + pub tunnel_addr: Option, + pub tunnel_listen: Option, pub client: Client, pub cookie_store: Arc, pub cookie_path: PathBuf, @@ -55,9 +64,8 @@ impl Drop for CliContextSeed { true, ) .unwrap(); - let mut store = self.cookie_store.lock().unwrap(); - store.remove("localhost", "", "local"); - store.save_json(&mut *writer).unwrap(); + let store = self.cookie_store.lock().unwrap(); + cookie_store::serde::json::save(&store, &mut *writer).unwrap(); writer.sync_all().unwrap(); std::fs::rename(tmp, &self.cookie_path).unwrap(); } @@ -85,26 +93,14 @@ impl CliContext { .unwrap_or(Path::new("/")) .join(".cookies.json") }); - let cookie_store = Arc::new(CookieStoreMutex::new({ - let mut store = if cookie_path.exists() { - CookieStore::load_json(BufReader::new( - File::open(&cookie_path) - .with_ctx(|_| (ErrorKind::Filesystem, cookie_path.display()))?, - )) - .map_err(|e| eyre!("{}", e)) - .with_kind(crate::ErrorKind::Deserialization)? - } else { - CookieStore::default() - }; - if let Ok(local) = std::fs::read_to_string(LOCAL_AUTH_COOKIE_PATH) { - store - .insert_raw( - &RawCookie::new("local", local), - &"http://localhost".parse()?, - ) - .with_kind(crate::ErrorKind::Network)?; - } - store + let cookie_store = Arc::new(CookieStoreMutex::new(if cookie_path.exists() { + cookie_store::serde::json::load(BufReader::new( + File::open(&cookie_path) + .with_ctx(|_| (ErrorKind::Filesystem, cookie_path.display()))?, + )) + .unwrap_or_default() + } else { + CookieStore::default() })); Ok(CliContext(Arc::new(CliContextSeed { @@ -129,9 +125,17 @@ impl CliContext { Ok::<_, Error>(registry) }) .transpose()?, + registry_hostname: config.registry_hostname, + registry_listen: config.registry_listen, + tunnel_addr: config.tunnel, + tunnel_listen: config.tunnel_listen, client: { let mut builder = Client::builder().cookie_provider(cookie_store.clone()); - if let Some(proxy) = config.proxy { + if let Some(proxy) = config.proxy.or_else(|| { + config + .socks_listen + .and_then(|socks| format!("socks5h://{socks}").parse::().log_err()) + }) { builder = builder.proxy(Proxy::all(proxy).with_kind(crate::ErrorKind::ParseUrl)?) } @@ -139,14 +143,9 @@ impl CliContext { }, cookie_store, cookie_path, - developer_key_path: config.developer_key_path.unwrap_or_else(|| { - local_config_path() - .as_deref() - .unwrap_or_else(|| Path::new(super::config::CONFIG_PATH)) - .parent() - .unwrap_or(Path::new("/")) - .join("developer.key.pem") - }), + developer_key_path: config + .developer_key_path + .unwrap_or_else(default_developer_key_path), developer_key: OnceCell::new(), }))) } @@ -155,20 +154,26 @@ impl CliContext { #[instrument(skip_all)] pub fn developer_key(&self) -> Result<&ed25519_dalek::SigningKey, Error> { self.developer_key.get_or_try_init(|| { - if !self.developer_key_path.exists() { - return Err(Error::new(eyre!("Developer Key does not exist! Please run `start-cli init` before running this command."), crate::ErrorKind::Uninitialized)); - } - let pair = ::from_pkcs8_pem( - &std::fs::read_to_string(&self.developer_key_path)?, - ) - .with_kind(crate::ErrorKind::Pem)?; - let secret = ed25519_dalek::SecretKey::try_from(&pair.secret_key[..]).map_err(|_| { - Error::new( - eyre!("pkcs8 key is of incorrect length"), - ErrorKind::OpenSsl, + for path in [Path::new(OS_DEVELOPER_KEY_PATH), &self.developer_key_path] { + if !path.exists() { + continue; + } + let pair = ::from_pkcs8_pem( + &std::fs::read_to_string(&self.developer_key_path)?, ) - })?; - Ok(secret.into()) + .with_kind(crate::ErrorKind::Pem)?; + let secret = ed25519_dalek::SecretKey::try_from(&pair.secret_key[..]).map_err(|_| { + Error::new( + eyre!("pkcs8 key is of incorrect length"), + ErrorKind::OpenSsl, + ) + })?; + return Ok(secret.into()) + } + Err(Error::new( + eyre!("Developer Key does not exist! Please run `start-cli init` before running this command."), + crate::ErrorKind::Uninitialized + )) }) } @@ -185,7 +190,7 @@ impl CliContext { eyre!("Cannot parse scheme from base URL"), crate::ErrorKind::ParseUrl, ) - .into()) + .into()); } }; url.set_scheme(ws_scheme) @@ -276,27 +281,90 @@ impl Context for CliContext { } impl CallRemote for CliContext { async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { - call_remote_http(&self.client, self.rpc_url.clone(), method, params).await + if let Ok(local) = std::fs::read_to_string(RpcContext::LOCAL_AUTH_COOKIE_PATH) { + self.cookie_store + .lock() + .unwrap() + .insert_raw( + &Cookie::build(("local", local)) + .domain("localhost") + .expires(Expiration::Session) + .same_site(SameSite::Strict) + .build(), + &"http://localhost".parse()?, + ) + .with_kind(crate::ErrorKind::Network)?; + } + crate::middleware::signature::call_remote( + self, + self.rpc_url.clone(), + self.rpc_url.host_str().or_not_found("rpc url hostname")?, + method, + params, + ) + .await } } impl CallRemote for CliContext { async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { - call_remote_http(&self.client, self.rpc_url.clone(), method, params).await + if let Ok(local) = std::fs::read_to_string(TunnelContext::LOCAL_AUTH_COOKIE_PATH) { + self.cookie_store + .lock() + .unwrap() + .insert_raw( + &Cookie::build(("local", local)) + .domain("localhost") + .expires(Expiration::Session) + .same_site(SameSite::Strict) + .build(), + &"http://localhost".parse()?, + ) + .with_kind(crate::ErrorKind::Network)?; + } + crate::middleware::signature::call_remote( + self, + self.rpc_url.clone(), + self.rpc_url.host_str().or_not_found("rpc url hostname")?, + method, + params, + ) + .await } } impl CallRemote for CliContext { async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { - call_remote_http(&self.client, self.rpc_url.clone(), method, params).await + crate::middleware::signature::call_remote( + self, + self.rpc_url.clone(), + self.rpc_url.host_str().or_not_found("rpc url hostname")?, + method, + params, + ) + .await } } impl CallRemote for CliContext { async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { - call_remote_http(&self.client, self.rpc_url.clone(), method, params).await + crate::middleware::signature::call_remote( + self, + self.rpc_url.clone(), + self.rpc_url.host_str().or_not_found("rpc url hostname")?, + method, + params, + ) + .await } } impl CallRemote for CliContext { async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { - call_remote_http(&self.client, self.rpc_url.clone(), method, params).await + crate::middleware::signature::call_remote( + self, + self.rpc_url.clone(), + self.rpc_url.host_str().or_not_found("rpc url hostname")?, + method, + params, + ) + .await } } diff --git a/core/startos/src/context/config.rs b/core/startos/src/context/config.rs index b98b07f16..a0ccbb2b4 100644 --- a/core/startos/src/context/config.rs +++ b/core/startos/src/context/config.rs @@ -3,15 +3,16 @@ use std::net::SocketAddr; use std::path::{Path, PathBuf}; use clap::Parser; +use imbl_value::InternedString; use reqwest::Url; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; +use crate::MAIN_DATA; use crate::disk::OsPartitionInfo; use crate::prelude::*; use crate::util::serde::IoFormat; use crate::version::VersionT; -use crate::MAIN_DATA; pub const DEVICE_CONFIG_PATH: &str = "/media/startos/config/config.yaml"; // "/media/startos/config/config.yaml"; pub const CONFIG_PATH: &str = "/etc/startos/config.yaml"; @@ -55,7 +56,6 @@ pub trait ContextConfig: DeserializeOwned + Default { #[derive(Debug, Default, Deserialize, Serialize, Parser)] #[serde(rename_all = "kebab-case")] #[command(rename_all = "kebab-case")] -#[command(name = "start-cli")] #[command(version = crate::version::Current::default().semver().to_string())] pub struct ClientConfig { #[arg(short = 'c', long)] @@ -64,8 +64,18 @@ pub struct ClientConfig { pub host: Option, #[arg(short = 'r', long)] pub registry: Option, + #[arg(long)] + pub registry_hostname: Option, + #[arg(skip)] + pub registry_listen: Option, + #[arg(short = 't', long)] + pub tunnel: Option, + #[arg(skip)] + pub tunnel_listen: Option, #[arg(short = 'p', long)] pub proxy: Option, + #[arg(skip)] + pub socks_listen: Option, #[arg(long)] pub cookie_path: Option, #[arg(long)] @@ -78,6 +88,8 @@ impl ContextConfig for ClientConfig { fn merge_with(&mut self, other: Self) { self.host = self.host.take().or(other.host); self.registry = self.registry.take().or(other.registry); + self.registry_hostname = self.registry_hostname.take().or(other.registry_hostname); + self.tunnel = self.tunnel.take().or(other.tunnel); self.proxy = self.proxy.take().or(other.proxy); self.cookie_path = self.cookie_path.take().or(other.cookie_path); self.developer_key_path = self.developer_key_path.take().or(other.developer_key_path); @@ -104,15 +116,15 @@ pub struct ServerConfig { #[arg(skip)] pub os_partitions: Option, #[arg(long)] - pub tor_control: Option, - #[arg(long)] - pub tor_socks: Option, + pub socks_listen: Option, #[arg(long)] pub revision_cache_size: Option, #[arg(long)] pub disable_encryption: Option, #[arg(long)] pub multi_arch_s9pks: Option, + #[arg(long)] + pub developer_key_path: Option, } impl ContextConfig for ServerConfig { fn next(&mut self) -> Option { @@ -121,14 +133,14 @@ impl ContextConfig for ServerConfig { fn merge_with(&mut self, other: Self) { self.ethernet_interface = self.ethernet_interface.take().or(other.ethernet_interface); self.os_partitions = self.os_partitions.take().or(other.os_partitions); - self.tor_control = self.tor_control.take().or(other.tor_control); - self.tor_socks = self.tor_socks.take().or(other.tor_socks); + self.socks_listen = self.socks_listen.take().or(other.socks_listen); self.revision_cache_size = self .revision_cache_size .take() .or(other.revision_cache_size); self.disable_encryption = self.disable_encryption.take().or(other.disable_encryption); self.multi_arch_s9pks = self.multi_arch_s9pks.take().or(other.multi_arch_s9pks); + self.developer_key_path = self.developer_key_path.take().or(other.developer_key_path); } } diff --git a/core/startos/src/context/diagnostic.rs b/core/startos/src/context/diagnostic.rs index 6acb21e30..e1bff2466 100644 --- a/core/startos/src/context/diagnostic.rs +++ b/core/startos/src/context/diagnostic.rs @@ -1,15 +1,15 @@ use std::ops::Deref; use std::sync::Arc; -use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::Context; +use rpc_toolkit::yajrc::RpcError; use tokio::sync::broadcast::Sender; use tracing::instrument; +use crate::Error; use crate::context::config::ServerConfig; use crate::rpc_continuations::RpcContinuations; use crate::shutdown::Shutdown; -use crate::Error; pub struct DiagnosticContextSeed { pub shutdown: Sender, diff --git a/core/startos/src/context/init.rs b/core/startos/src/context/init.rs index 566457a9c..bceae8a86 100644 --- a/core/startos/src/context/init.rs +++ b/core/startos/src/context/init.rs @@ -25,10 +25,12 @@ impl InitContext { #[instrument(skip_all)] pub async fn init(cfg: &ServerConfig) -> Result { let (shutdown, _) = tokio::sync::broadcast::channel(1); + let mut progress = FullProgressTracker::new(); + progress.enable_logging(true); Ok(Self(Arc::new(InitContextSeed { config: cfg.clone(), error: watch::channel(None).0, - progress: FullProgressTracker::new(), + progress, shutdown, rpc_continuations: RpcContinuations::new(), }))) diff --git a/core/startos/src/context/install.rs b/core/startos/src/context/install.rs index c0c564b34..04717cf93 100644 --- a/core/startos/src/context/install.rs +++ b/core/startos/src/context/install.rs @@ -5,9 +5,9 @@ use rpc_toolkit::Context; use tokio::sync::broadcast::Sender; use tracing::instrument; +use crate::Error; use crate::net::utils::find_eth_iface; use crate::rpc_continuations::RpcContinuations; -use crate::Error; pub struct InstallContextSeed { pub ethernet_interface: String, diff --git a/core/startos/src/context/rpc.rs b/core/startos/src/context/rpc.rs index cfbf4375f..5830b7950 100644 --- a/core/startos/src/context/rpc.rs +++ b/core/startos/src/context/rpc.rs @@ -18,7 +18,7 @@ use models::{ActionId, PackageId}; use reqwest::{Client, Proxy}; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{CallRemote, Context, Empty}; -use tokio::sync::{broadcast, oneshot, watch, Mutex, RwLock}; +use tokio::sync::{broadcast, oneshot, watch, RwLock}; use tokio::time::Instant; use tracing::instrument; @@ -31,8 +31,9 @@ use crate::db::model::Database; use crate::disk::OsPartitionInfo; use crate::init::{check_time_is_synchronized, InitResult}; use crate::install::PKG_ARCHIVE_DIR; -use crate::lxc::{ContainerId, LxcContainer, LxcManager}; +use crate::lxc::LxcManager; use crate::net::net_controller::{NetController, NetService}; +use crate::net::socks::DEFAULT_SOCKS_LISTEN; use crate::net::utils::{find_eth_iface, find_wifi_iface}; use crate::net::web_server::{UpgradableListener, WebServerAcceptorSetter}; use crate::net::wifi::WpaCli; @@ -46,7 +47,7 @@ use crate::shutdown::Shutdown; use crate::util::io::delete_file; use crate::util::lshw::LshwDevice; use crate::util::sync::{SyncMutex, Watch}; -use crate::DATA_DIR; +use crate::{DATA_DIR, HOST_IP}; pub struct RpcContextSeed { is_closed: AtomicBool, @@ -65,7 +66,6 @@ pub struct RpcContextSeed { pub cancellable_installs: SyncMutex>>, pub metrics_cache: Watch>, pub shutdown: broadcast::Sender>, - pub tor_socks: SocketAddr, pub lxc_manager: Arc, pub open_authed_continuations: OpenAuthedContinuations>, pub rpc_continuations: RpcContinuations, @@ -75,12 +75,6 @@ pub struct RpcContextSeed { pub client: Client, pub start_time: Instant, pub crons: SyncMutex>>, - // #[cfg(feature = "dev")] - pub dev: Dev, -} - -pub struct Dev { - pub lxc: Mutex>, } pub struct Hardware { @@ -138,10 +132,7 @@ impl RpcContext { run_migrations, }: InitRpcContextPhases, ) -> Result { - let tor_proxy = config.tor_socks.unwrap_or(SocketAddr::V4(SocketAddrV4::new( - Ipv4Addr::new(127, 0, 0, 1), - 9050, - ))); + let socks_proxy = config.socks_listen.unwrap_or(DEFAULT_SOCKS_LISTEN); let (shutdown, _) = tokio::sync::broadcast::channel(1); load_db.start(); @@ -163,18 +154,9 @@ impl RpcContext { { (net_ctrl, os_net_service) } else { - let net_ctrl = Arc::new( - NetController::init( - db.clone(), - config - .tor_control - .unwrap_or(SocketAddr::from(([127, 0, 0, 1], 9051))), - tor_proxy, - &account.hostname, - ) - .await?, - ); - webserver.try_upgrade(|a| net_ctrl.net_iface.upgrade_listener(a))?; + let net_ctrl = + Arc::new(NetController::init(db.clone(), &account.hostname, socks_proxy).await?); + webserver.try_upgrade(|a| net_ctrl.net_iface.watcher.upgrade_listener(a))?; let os_net_service = net_ctrl.os_bindings().await?; (net_ctrl, os_net_service) }; @@ -183,7 +165,7 @@ impl RpcContext { let services = ServiceMap::default(); let metrics_cache = Watch::>::new(None); - let tor_proxy_url = format!("socks5h://{tor_proxy}"); + let socks_proxy_url = format!("socks5h://{socks_proxy}"); let crons = SyncMutex::new(BTreeMap::new()); @@ -251,7 +233,6 @@ impl RpcContext { cancellable_installs: SyncMutex::new(BTreeMap::new()), metrics_cache, shutdown, - tor_socks: tor_proxy, lxc_manager: Arc::new(LxcManager::new()), open_authed_continuations: OpenAuthedContinuations::new(), rpc_continuations: RpcContinuations::new(), @@ -267,21 +248,11 @@ impl RpcContext { })?, ), client: Client::builder() - .proxy(Proxy::custom(move |url| { - if url.host_str().map_or(false, |h| h.ends_with(".onion")) { - Some(tor_proxy_url.clone()) - } else { - None - } - })) + .proxy(Proxy::all(socks_proxy_url)?) .build() .with_kind(crate::ErrorKind::ParseUrl)?, start_time: Instant::now(), crons, - // #[cfg(feature = "dev")] - dev: Dev { - lxc: Mutex::new(BTreeMap::new()), - }, }); let res = Self(seed.clone()); diff --git a/core/startos/src/context/setup.rs b/core/startos/src/context/setup.rs index 2cd3b2f73..94c81a9db 100644 --- a/core/startos/src/context/setup.rs +++ b/core/startos/src/context/setup.rs @@ -25,6 +25,7 @@ use crate::prelude::*; use crate::progress::FullProgressTracker; use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations}; use crate::setup::SetupProgress; +use crate::shutdown::Shutdown; use crate::util::net::WebSocketExt; use crate::MAIN_DATA; @@ -54,7 +55,7 @@ impl TryFrom<&AccountInfo> for SetupResult { tor_addresses: value .tor_keys .iter() - .map(|tor_key| format!("https://{}", tor_key.public().get_onion_address())) + .map(|tor_key| format!("https://{}", tor_key.onion_address())) .collect(), hostname: value.hostname.clone(), lan_address: value.hostname.lan_address(), @@ -71,7 +72,8 @@ pub struct SetupContextSeed { pub progress: FullProgressTracker, pub task: OnceCell>, pub result: OnceCell>, - pub shutdown: Sender<()>, + pub disk_guid: OnceCell>, + pub shutdown: Sender>, pub rpc_continuations: RpcContinuations, } @@ -84,6 +86,8 @@ impl SetupContext { config: &ServerConfig, ) -> Result { let (shutdown, _) = tokio::sync::broadcast::channel(1); + let mut progress = FullProgressTracker::new(); + progress.enable_logging(true); Ok(Self(Arc::new(SetupContextSeed { webserver: webserver.acceptor_setter(), config: config.clone(), @@ -94,9 +98,10 @@ impl SetupContext { ) })?, disable_encryption: config.disable_encryption.unwrap_or(false), - progress: FullProgressTracker::new(), + progress, task: OnceCell::new(), result: OnceCell::new(), + disk_guid: OnceCell::new(), shutdown, rpc_continuations: RpcContinuations::new(), }))) diff --git a/core/startos/src/control.rs b/core/startos/src/control.rs index afb1768f2..4690c8a39 100644 --- a/core/startos/src/control.rs +++ b/core/startos/src/control.rs @@ -5,10 +5,10 @@ use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; +use crate::Error; use crate::context::RpcContext; use crate::prelude::*; use crate::rpc_continuations::Guid; -use crate::Error; #[derive(Deserialize, Serialize, Parser, TS)] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/db/mod.rs b/core/startos/src/db/mod.rs index 2c08a9e5d..f1a16ab2f 100644 --- a/core/startos/src/db/mod.rs +++ b/core/startos/src/db/mod.rs @@ -12,7 +12,7 @@ use itertools::Itertools; use patch_db::json_ptr::{JsonPointer, ROOT}; use patch_db::{DiffPatch, Dump, Revision}; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use tokio::sync::mpsc::{self, UnboundedReceiver}; use tokio::sync::watch; @@ -23,7 +23,7 @@ use crate::context::{CliContext, RpcContext}; use crate::prelude::*; use crate::rpc_continuations::{Guid, RpcContinuation}; use crate::util::net::WebSocketExt; -use crate::util::serde::{apply_expr, HandlerExtSerde}; +use crate::util::serde::{HandlerExtSerde, apply_expr}; lazy_static::lazy_static! { static ref PUBLIC: JsonPointer = "/public".parse().unwrap(); diff --git a/core/startos/src/db/model/mod.rs b/core/startos/src/db/model/mod.rs index 4806d8621..b153ea44b 100644 --- a/core/startos/src/db/model/mod.rs +++ b/core/startos/src/db/model/mod.rs @@ -12,6 +12,7 @@ use crate::net::forward::AvailablePorts; use crate::net::keys::KeyStore; use crate::notifications::Notifications; use crate::prelude::*; +use crate::sign::AnyVerifyingKey; use crate::ssh::SshKeys; use crate::util::serde::Pem; @@ -33,6 +34,9 @@ impl Database { private: Private { key_store: KeyStore::new(account)?, password: account.password.clone(), + auth_pubkeys: [AnyVerifyingKey::Ed25519((&account.developer_key).into())] + .into_iter() + .collect(), ssh_privkey: Pem(account.ssh_key.clone()), ssh_pubkeys: SshKeys::new(), available_ports: AvailablePorts::new(), @@ -40,7 +44,7 @@ impl Database { notifications: Notifications::new(), cifs: CifsTargets::new(), package_stores: BTreeMap::new(), - compat_s9pk_key: Pem(account.compat_s9pk_key.clone()), + developer_key: Pem(account.developer_key.clone()), }, // TODO }) } diff --git a/core/startos/src/db/model/package.rs b/core/startos/src/db/model/package.rs index 34c9c5f5d..2a2abe2b6 100644 --- a/core/startos/src/db/model/package.rs +++ b/core/startos/src/db/model/package.rs @@ -5,8 +5,8 @@ use chrono::{DateTime, Utc}; use exver::VersionRange; use imbl_value::InternedString; use models::{ActionId, DataUrl, HealthCheckId, HostId, PackageId, ReplayId, ServiceInterfaceId}; -use patch_db::json_ptr::JsonPointer; use patch_db::HasModel; +use patch_db::json_ptr::JsonPointer; use reqwest::Url; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -17,7 +17,7 @@ use crate::prelude::*; use crate::progress::FullProgress; use crate::s9pk::manifest::Manifest; use crate::status::MainStatus; -use crate::util::serde::{is_partial_of, Pem}; +use crate::util::serde::{Pem, is_partial_of}; #[derive(Debug, Default, Deserialize, Serialize, TS)] #[ts(export)] @@ -268,7 +268,7 @@ impl Model { return Err(Error::new( eyre!("could not determine package state to get manifest"), ErrorKind::Database, - )) + )); } }) } @@ -375,7 +375,6 @@ pub struct PackageDataEntry { pub last_backup: Option>, pub current_dependencies: CurrentDependencies, pub actions: BTreeMap, - #[ts(as = "BTreeMap::")] pub tasks: BTreeMap, pub service_interfaces: BTreeMap, pub hosts: Hosts, diff --git a/core/startos/src/db/model/private.rs b/core/startos/src/db/model/private.rs index 2108b32a4..47de5093a 100644 --- a/core/startos/src/db/model/private.rs +++ b/core/startos/src/db/model/private.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashSet}; use models::PackageId; use patch_db::{HasModel, Value}; @@ -10,6 +10,7 @@ use crate::net::forward::AvailablePorts; use crate::net::keys::KeyStore; use crate::notifications::Notifications; use crate::prelude::*; +use crate::sign::AnyVerifyingKey; use crate::ssh::SshKeys; use crate::util::serde::Pem; @@ -19,8 +20,9 @@ use crate::util::serde::Pem; pub struct Private { pub key_store: KeyStore, pub password: String, // argon2 hash - #[serde(default = "generate_compat_key")] - pub compat_s9pk_key: Pem, + pub auth_pubkeys: HashSet, + #[serde(default = "generate_developer_key")] + pub developer_key: Pem, pub ssh_privkey: Pem, pub ssh_pubkeys: SshKeys, pub available_ports: AvailablePorts, @@ -31,7 +33,7 @@ pub struct Private { pub package_stores: BTreeMap, } -pub fn generate_compat_key() -> Pem { +pub fn generate_developer_key() -> Pem { Pem(ed25519_dalek::SigningKey::generate( &mut ssh_key::rand_core::OsRng::default(), )) diff --git a/core/startos/src/db/model/public.rs b/core/startos/src/db/model/public.rs index 1fad92be6..5451d4795 100644 --- a/core/startos/src/db/model/public.rs +++ b/core/startos/src/db/model/public.rs @@ -1,13 +1,15 @@ use std::collections::{BTreeMap, BTreeSet}; -use std::net::{IpAddr, Ipv4Addr}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use chrono::{DateTime, Utc}; use exver::{Version, VersionRange}; +use imbl::{OrdMap, OrdSet}; use imbl_value::InternedString; use ipnet::IpNet; use isocountry::CountryCode; use itertools::Itertools; -use models::PackageId; +use lazy_static::lazy_static; +use models::{GatewayId, PackageId}; use openssl::hash::MessageDigest; use patch_db::{HasModel, Value}; use serde::{Deserialize, Serialize}; @@ -16,6 +18,7 @@ use ts_rs::TS; use crate::account::AccountInfo; use crate::db::model::package::AllPackageData; use crate::net::acme::AcmeProvider; +use crate::net::forward::START9_BRIDGE_IFACE; use crate::net::host::binding::{AddSslOptions, BindInfo, BindOptions, NetInfo}; use crate::net::host::Host; use crate::net::utils::ipv6_is_local; @@ -27,7 +30,7 @@ use crate::util::cpupower::Governor; use crate::util::lshw::LshwDevice; use crate::util::serde::MaybeUtf8String; use crate::version::{Current, VersionT}; -use crate::{ARCH, PLATFORM}; +use crate::{ARCH, HOST_IP, PLATFORM}; #[derive(Debug, Deserialize, Serialize, HasModel, TS)] #[serde(rename_all = "camelCase")] @@ -71,26 +74,25 @@ impl Public { net: NetInfo { assigned_port: None, assigned_ssl_port: Some(443), - public: false, + private_disabled: OrdSet::new(), + public_enabled: OrdSet::new(), }, }, )] .into_iter() .collect(), - onions: account - .tor_keys - .iter() - .map(|k| k.public().get_onion_address()) - .collect(), - domains: BTreeMap::new(), + onions: account.tor_keys.iter().map(|k| k.onion_address()).collect(), + public_domains: BTreeMap::new(), + private_domains: BTreeSet::new(), hostname_info: BTreeMap::new(), }, wifi: WifiInfo { enabled: true, ..Default::default() }, - network_interfaces: BTreeMap::new(), + gateways: OrdMap::new(), acme: BTreeMap::new(), + dns: Default::default(), }, status_info: ServerStatus { backup_progress: None, @@ -186,11 +188,22 @@ pub struct ServerInfo { pub struct NetworkInfo { pub wifi: WifiInfo, pub host: Host, - #[ts(as = "BTreeMap::")] + #[ts(as = "BTreeMap::")] #[serde(default)] - pub network_interfaces: BTreeMap, + pub gateways: OrdMap, #[serde(default)] pub acme: BTreeMap, + #[serde(default)] + pub dns: DnsSettings, +} + +#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +#[ts(export)] +pub struct DnsSettings { + pub dhcp_servers: Vec, + pub static_servers: Option>, } #[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)] @@ -198,13 +211,68 @@ pub struct NetworkInfo { #[model = "Model"] #[ts(export)] pub struct NetworkInterfaceInfo { - pub inbound: Option, - pub outbound: Option, + pub name: Option, + pub public: Option, + pub secure: Option, pub ip_info: Option, } impl NetworkInterfaceInfo { - pub fn inbound(&self) -> bool { - self.inbound.unwrap_or_else(|| { + pub fn loopback() -> (&'static GatewayId, &'static Self) { + lazy_static! { + static ref LO: GatewayId = GatewayId::from("lo"); + static ref LOOPBACK: NetworkInterfaceInfo = NetworkInterfaceInfo { + name: Some(InternedString::from_static("Loopback")), + public: Some(false), + secure: Some(true), + ip_info: Some(IpInfo { + name: "lo".into(), + scope_id: 1, + device_type: None, + subnets: [ + IpNet::new(Ipv4Addr::LOCALHOST.into(), 8).unwrap(), + IpNet::new(Ipv6Addr::LOCALHOST.into(), 128).unwrap(), + ] + .into_iter() + .collect(), + lan_ip: [ + IpAddr::from(Ipv4Addr::LOCALHOST), + IpAddr::from(Ipv6Addr::LOCALHOST) + ] + .into_iter() + .collect(), + wan_ip: None, + ntp_servers: Default::default(), + dns_servers: Default::default(), + }), + }; + } + (&*LO, &*LOOPBACK) + } + pub fn lxc_bridge() -> (&'static GatewayId, &'static Self) { + lazy_static! { + static ref LXCBR0: GatewayId = GatewayId::from(START9_BRIDGE_IFACE); + static ref LXC_BRIDGE: NetworkInterfaceInfo = NetworkInterfaceInfo { + name: Some(InternedString::from_static("LXC Bridge Interface")), + public: Some(false), + secure: Some(true), + ip_info: Some(IpInfo { + name: START9_BRIDGE_IFACE.into(), + scope_id: 0, + device_type: None, + subnets: [IpNet::new(HOST_IP.into(), 24).unwrap()] + .into_iter() + .collect(), + lan_ip: [IpAddr::from(HOST_IP)].into_iter().collect(), + wan_ip: None, + ntp_servers: Default::default(), + dns_servers: Default::default(), + }), + }; + } + (&*LXCBR0, &*LXC_BRIDGE) + } + pub fn public(&self) -> bool { + self.public.unwrap_or_else(|| { !self.ip_info.as_ref().map_or(true, |ip_info| { let ip4s = ip_info .subnets @@ -218,11 +286,9 @@ impl NetworkInterfaceInfo { }) .collect::>(); if !ip4s.is_empty() { - return ip4s.iter().all(|ip4| { - ip4.is_loopback() - || (ip4.is_private() && !ip4.octets().starts_with(&[10, 59])) // reserving 10.59 for public wireguard configurations - || ip4.is_link_local() - }); + return ip4s + .iter() + .all(|ip4| ip4.is_loopback() || ip4.is_private() || ip4.is_link_local()); } ip_info.subnets.iter().all(|ipnet| { if let IpAddr::V6(ip6) = ipnet.addr() { @@ -234,6 +300,14 @@ impl NetworkInterfaceInfo { }) }) } + + pub fn secure(&self) -> bool { + self.secure.unwrap_or_else(|| { + self.ip_info.as_ref().map_or(false, |ip_info| { + ip_info.device_type == Some(NetworkInterfaceType::Wireguard) + }) + }) + } } #[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize, TS, HasModel)] @@ -246,10 +320,14 @@ pub struct IpInfo { pub scope_id: u32, pub device_type: Option, #[ts(type = "string[]")] - pub subnets: BTreeSet, + pub subnets: OrdSet, + #[ts(type = "string[]")] + pub lan_ip: OrdSet, pub wan_ip: Option, #[ts(type = "string[]")] - pub ntp_servers: BTreeSet, + pub ntp_servers: OrdSet, + #[ts(type = "string[]")] + pub dns_servers: OrdSet, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, TS)] @@ -269,6 +347,14 @@ pub struct AcmeSettings { pub contact: Vec, } +#[derive(Debug, Deserialize, Serialize, HasModel, TS)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +#[ts(export)] +pub struct DomainSettings { + pub gateway: GatewayId, +} + #[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)] #[model = "Model"] #[ts(export)] diff --git a/core/startos/src/db/prelude.rs b/core/startos/src/db/prelude.rs index 30ce13b0f..bb779a3c0 100644 --- a/core/startos/src/db/prelude.rs +++ b/core/startos/src/db/prelude.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; use std::str::FromStr; use chrono::{DateTime, Utc}; +use imbl::OrdMap; pub use imbl_value::Value; use patch_db::value::InternedString; pub use patch_db::{HasModel, MutateResult, PatchDb}; @@ -199,6 +200,18 @@ where } } +impl Map for OrdMap +where + A: serde::Serialize + serde::de::DeserializeOwned + Clone + Ord + AsRef, + B: serde::Serialize + serde::de::DeserializeOwned + Clone, +{ + type Key = A; + type Value = B; + fn key_str(key: &Self::Key) -> Result, Error> { + Ok(key.as_ref()) + } +} + impl Model where T::Value: Serialize, diff --git a/core/startos/src/dependencies.rs b/core/startos/src/dependencies.rs index 3b55c8fc3..43ed3bbc3 100644 --- a/core/startos/src/dependencies.rs +++ b/core/startos/src/dependencies.rs @@ -1,4 +1,5 @@ use std::collections::BTreeMap; +use std::path::Path; use imbl_value::InternedString; use models::PackageId; @@ -24,20 +25,62 @@ impl Map for Dependencies { } } -#[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)] +#[derive(Clone, Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "camelCase")] #[model = "Model"] -#[ts(export)] pub struct DepInfo { pub description: Option, pub optional: bool, - pub s9pk: Option, + #[serde(flatten)] + pub metadata: Option, +} +impl TS for DepInfo { + type WithoutGenerics = Self; + fn decl() -> String { + format!("type {} = {}", Self::name(), Self::inline()) + } + fn decl_concrete() -> String { + Self::decl() + } + fn name() -> String { + "DepInfo".into() + } + fn inline() -> String { + "{ description: string | null, optional: boolean } & MetadataSrc".into() + } + fn inline_flattened() -> String { + Self::inline() + } + fn visit_dependencies(v: &mut impl ts_rs::TypeVisitor) + where + Self: 'static, + { + v.visit::() + } + fn output_path() -> Option<&'static std::path::Path> { + Some(Path::new("DepInfo.ts")) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub enum MetadataSrc { + Metadata(Metadata), + S9pk(Option), // backwards compatibility +} + +#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct Metadata { + pub title: InternedString, + pub icon: PathOrUrl, } #[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)] #[serde(rename_all = "camelCase")] #[model = "Model"] -#[ts(export)] pub struct DependencyMetadata { #[ts(type = "string")] pub title: InternedString, diff --git a/core/startos/src/developer/mod.rs b/core/startos/src/developer/mod.rs index de71b0fc0..102a30e22 100644 --- a/core/startos/src/developer/mod.rs +++ b/core/startos/src/developer/mod.rs @@ -1,40 +1,57 @@ -use std::fs::File; -use std::io::Write; -use std::path::Path; +use std::path::{Path, PathBuf}; -use ed25519::pkcs8::EncodePrivateKey; use ed25519::PublicKeyBytes; +use ed25519::pkcs8::EncodePrivateKey; use ed25519_dalek::{SigningKey, VerifyingKey}; +use tokio::io::AsyncWriteExt; use tracing::instrument; use crate::context::CliContext; +use crate::context::config::local_config_path; use crate::prelude::*; +use crate::util::io::create_file_mod; use crate::util::serde::Pem; +pub const OS_DEVELOPER_KEY_PATH: &str = "/run/startos/developer.key.pem"; + +pub fn default_developer_key_path() -> PathBuf { + local_config_path() + .as_deref() + .unwrap_or_else(|| Path::new(crate::context::config::CONFIG_PATH)) + .parent() + .unwrap_or(Path::new("/")) + .join("developer.key.pem") +} + +pub async fn write_developer_key( + secret: &ed25519_dalek::SigningKey, + path: impl AsRef, +) -> Result<(), Error> { + let keypair_bytes = ed25519::KeypairBytes { + secret_key: secret.to_bytes(), + public_key: Some(PublicKeyBytes(VerifyingKey::from(secret).to_bytes())), + }; + let mut file = create_file_mod(path, 0o046).await?; + file.write_all( + keypair_bytes + .to_pkcs8_pem(base64ct::LineEnding::default()) + .with_kind(crate::ErrorKind::Pem)? + .as_bytes(), + ) + .await?; + file.sync_all().await?; + Ok(()) +} + #[instrument(skip_all)] -pub fn init(ctx: CliContext) -> Result<(), Error> { - if !ctx.developer_key_path.exists() { - let parent = ctx.developer_key_path.parent().unwrap_or(Path::new("/")); - if !parent.exists() { - std::fs::create_dir_all(parent) - .with_ctx(|_| (crate::ErrorKind::Filesystem, parent.display().to_string()))?; - } +pub async fn init(ctx: CliContext) -> Result<(), Error> { + if tokio::fs::metadata(OS_DEVELOPER_KEY_PATH).await.is_ok() { + println!("Developer key already exists at {}", OS_DEVELOPER_KEY_PATH); + } else if tokio::fs::metadata(&ctx.developer_key_path).await.is_err() { tracing::info!("Generating new developer key..."); let secret = SigningKey::generate(&mut ssh_key::rand_core::OsRng::default()); tracing::info!("Writing key to {}", ctx.developer_key_path.display()); - let keypair_bytes = ed25519::KeypairBytes { - secret_key: secret.to_bytes(), - public_key: Some(PublicKeyBytes(VerifyingKey::from(&secret).to_bytes())), - }; - let mut dev_key_file = File::create(&ctx.developer_key_path) - .with_ctx(|_| (ErrorKind::Filesystem, ctx.developer_key_path.display()))?; - dev_key_file.write_all( - keypair_bytes - .to_pkcs8_pem(base64ct::LineEnding::default()) - .with_kind(crate::ErrorKind::Pem)? - .as_bytes(), - )?; - dev_key_file.sync_all()?; + write_developer_key(&secret, &ctx.developer_key_path).await?; println!( "New developer key generated at {}", ctx.developer_key_path.display() diff --git a/core/startos/src/diagnostic.rs b/core/startos/src/diagnostic.rs index 2c6040e8d..820d05512 100644 --- a/core/startos/src/diagnostic.rs +++ b/core/startos/src/diagnostic.rs @@ -1,9 +1,8 @@ -use std::path::Path; use std::sync::Arc; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{ - from_fn, from_fn_async, CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler, + CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler, from_fn, from_fn_async, }; use crate::context::{CliContext, DiagnosticContext, RpcContext}; @@ -12,7 +11,6 @@ use crate::init::SYSTEM_REBUILD_PATH; use crate::prelude::*; use crate::shutdown::Shutdown; use crate::util::io::delete_file; -use crate::DATA_DIR; pub fn diagnostic() -> ParentHandler { ParentHandler::new() @@ -70,10 +68,7 @@ pub fn error(ctx: DiagnosticContext) -> Result, Error> { pub fn restart(ctx: DiagnosticContext) -> Result<(), Error> { ctx.shutdown .send(Shutdown { - export_args: ctx - .disk_guid - .clone() - .map(|guid| (guid, Path::new(DATA_DIR).to_owned())), + disk_guid: ctx.disk_guid.clone(), restart: true, }) .map_err(|_| eyre!("receiver dropped")) diff --git a/core/startos/src/disk/fsck/btrfs.rs b/core/startos/src/disk/fsck/btrfs.rs index 2a198cd8f..3d7556825 100644 --- a/core/startos/src/disk/fsck/btrfs.rs +++ b/core/startos/src/disk/fsck/btrfs.rs @@ -4,9 +4,9 @@ use std::path::Path; use tokio::process::Command; use tracing::instrument; +use crate::Error; use crate::disk::fsck::RequiresReboot; use crate::util::Invoke; -use crate::Error; #[instrument(skip_all)] pub async fn btrfs_check_readonly(logicalname: impl AsRef) -> Result { diff --git a/core/startos/src/disk/fsck/ext4.rs b/core/startos/src/disk/fsck/ext4.rs index a068749fa..534efd12d 100644 --- a/core/startos/src/disk/fsck/ext4.rs +++ b/core/startos/src/disk/fsck/ext4.rs @@ -2,13 +2,13 @@ use std::ffi::OsStr; use std::path::Path; use color_eyre::eyre::eyre; -use futures::future::BoxFuture; use futures::FutureExt; +use futures::future::BoxFuture; use tokio::process::Command; use tracing::instrument; -use crate::disk::fsck::RequiresReboot; use crate::Error; +use crate::disk::fsck::RequiresReboot; #[instrument(skip_all)] pub async fn e2fsck_preen( diff --git a/core/startos/src/disk/fsck/mod.rs b/core/startos/src/disk/fsck/mod.rs index 6758ddd58..1c6949138 100644 --- a/core/startos/src/disk/fsck/mod.rs +++ b/core/startos/src/disk/fsck/mod.rs @@ -3,10 +3,10 @@ use std::path::Path; use color_eyre::eyre::eyre; use tokio::process::Command; +use crate::Error; use crate::disk::fsck::btrfs::{btrfs_check_readonly, btrfs_check_repair}; use crate::disk::fsck::ext4::{e2fsck_aggressive, e2fsck_preen}; use crate::util::Invoke; -use crate::Error; pub mod btrfs; pub mod ext4; @@ -45,7 +45,7 @@ impl RepairStrategy { return Err(Error::new( eyre!("Unknown filesystem {fs}"), crate::ErrorKind::DiskManagement, - )) + )); } } } diff --git a/core/startos/src/disk/mod.rs b/core/startos/src/disk/mod.rs index d064c5cd2..7857dbdca 100644 --- a/core/startos/src/disk/mod.rs +++ b/core/startos/src/disk/mod.rs @@ -2,13 +2,13 @@ use std::path::{Path, PathBuf}; use itertools::Itertools; use lazy_format::lazy_format; -use rpc_toolkit::{from_fn_async, CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler}; +use rpc_toolkit::{CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; +use crate::Error; use crate::context::{CliContext, RpcContext}; use crate::disk::util::DiskInfo; -use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; -use crate::Error; +use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable}; pub mod fsck; pub mod main; @@ -96,14 +96,13 @@ fn display_disk_info(params: WithIoFormat, args: Vec) -> Result "N/A" }, part.capacity, - if let Some(used) = part + &if let Some(used) = part .used .map(|u| format!("{:.2} GiB", u as f64 / 1024.0 / 1024.0 / 1024.0)) - .as_ref() { used } else { - "N/A" + "N/A".to_owned() }, &if part.start_os.is_empty() { "N/A".to_owned() diff --git a/core/startos/src/disk/mount/backup.rs b/core/startos/src/disk/mount/backup.rs index fe771ceba..5fadba407 100644 --- a/core/startos/src/disk/mount/backup.rs +++ b/core/startos/src/disk/mount/backup.rs @@ -10,8 +10,8 @@ use tracing::instrument; use super::guard::{GenericMountGuard, TmpMountGuard}; use crate::auth::check_password; use crate::backup::target::BackupInfo; -use crate::disk::mount::filesystem::backupfs::BackupFS; use crate::disk::mount::filesystem::ReadWrite; +use crate::disk::mount::filesystem::backupfs::BackupFS; use crate::disk::mount::guard::SubPath; use crate::disk::util::StartOsRecoveryInfo; use crate::util::crypto::{decrypt_slice, encrypt_slice}; diff --git a/core/startos/src/disk/mount/filesystem/cifs.rs b/core/startos/src/disk/mount/filesystem/cifs.rs index 87509f150..255da377b 100644 --- a/core/startos/src/disk/mount/filesystem/cifs.rs +++ b/core/startos/src/disk/mount/filesystem/cifs.rs @@ -11,9 +11,9 @@ use tracing::instrument; use ts_rs::TS; use super::{FileSystem, MountType, ReadOnly}; +use crate::Error; use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard}; use crate::util::Invoke; -use crate::Error; async fn resolve_hostname(hostname: &str) -> Result { if let Ok(addr) = hostname.parse() { diff --git a/core/startos/src/disk/mount/filesystem/httpdirfs.rs b/core/startos/src/disk/mount/filesystem/httpdirfs.rs index df9416774..1cc107794 100644 --- a/core/startos/src/disk/mount/filesystem/httpdirfs.rs +++ b/core/startos/src/disk/mount/filesystem/httpdirfs.rs @@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize}; use sha2::Sha256; use super::{FileSystem, MountType}; -use crate::util::Invoke; use crate::Error; +use crate::util::Invoke; pub async fn mount_httpdirfs(url: &Url, mountpoint: impl AsRef) -> Result<(), Error> { tokio::fs::create_dir_all(mountpoint.as_ref()).await?; diff --git a/core/startos/src/disk/mount/filesystem/mod.rs b/core/startos/src/disk/mount/filesystem/mod.rs index f4c85296a..ac37f2217 100644 --- a/core/startos/src/disk/mount/filesystem/mod.rs +++ b/core/startos/src/disk/mount/filesystem/mod.rs @@ -2,8 +2,8 @@ use std::ffi::OsStr; use std::fmt::{Display, Write}; use std::path::Path; -use digest::generic_array::GenericArray; use digest::OutputSizeUser; +use digest::generic_array::GenericArray; use futures::Future; use sha2::Sha256; use tokio::process::Command; @@ -106,6 +106,7 @@ pub trait FileSystem: Send + Sync { } fn source_hash( &self, - ) -> impl Future::OutputSize>, Error>> - + Send; + ) -> impl Future< + Output = Result::OutputSize>, Error>, + > + Send; } diff --git a/core/startos/src/disk/mount/filesystem/overlayfs.rs b/core/startos/src/disk/mount/filesystem/overlayfs.rs index 85df0e12c..09a7d953b 100644 --- a/core/startos/src/disk/mount/filesystem/overlayfs.rs +++ b/core/startos/src/disk/mount/filesystem/overlayfs.rs @@ -21,11 +21,8 @@ impl, P1: AsRef, P2: AsRef> OverlayFs { Self { lower, upper, work } } } -impl< - P0: AsRef + Send + Sync, - P1: AsRef + Send + Sync, - P2: AsRef + Send + Sync, - > FileSystem for OverlayFs +impl + Send + Sync, P1: AsRef + Send + Sync, P2: AsRef + Send + Sync> + FileSystem for OverlayFs { fn mount_type(&self) -> Option> { Some("overlay") diff --git a/core/startos/src/disk/mount/guard.rs b/core/startos/src/disk/mount/guard.rs index 6e1cdc35e..03c08f1d5 100644 --- a/core/startos/src/disk/mount/guard.rs +++ b/core/startos/src/disk/mount/guard.rs @@ -10,8 +10,8 @@ use tracing::instrument; use super::filesystem::{FileSystem, MountType, ReadOnly, ReadWrite}; use super::util::unmount; -use crate::util::{Invoke, Never}; use crate::Error; +use crate::util::{Invoke, Never}; pub const TMP_MOUNTPOINT: &'static str = "/media/startos/tmp"; diff --git a/core/startos/src/disk/mount/util.rs b/core/startos/src/disk/mount/util.rs index 61368e67a..292345a59 100644 --- a/core/startos/src/disk/mount/util.rs +++ b/core/startos/src/disk/mount/util.rs @@ -2,8 +2,8 @@ use std::path::Path; use tracing::instrument; -use crate::util::Invoke; use crate::Error; +use crate::util::Invoke; pub async fn is_mountpoint(path: impl AsRef) -> Result { let is_mountpoint = tokio::process::Command::new("mountpoint") diff --git a/core/startos/src/disk/util.rs b/core/startos/src/disk/util.rs index cb6520611..a6b5bea39 100644 --- a/core/startos/src/disk/util.rs +++ b/core/startos/src/disk/util.rs @@ -14,14 +14,14 @@ use serde::{Deserialize, Serialize}; use tokio::process::Command; use tracing::instrument; -use super::mount::filesystem::block_dev::BlockDev; use super::mount::filesystem::ReadOnly; +use super::mount::filesystem::block_dev::BlockDev; use super::mount::guard::TmpMountGuard; -use crate::disk::mount::guard::GenericMountGuard; use crate::disk::OsPartitionInfo; +use crate::disk::mount::guard::GenericMountGuard; use crate::hostname::Hostname; -use crate::util::serde::IoFormat; use crate::util::Invoke; +use crate::util::serde::IoFormat; use crate::{Error, ResultExt as _}; #[derive(Clone, Copy, Debug, Deserialize, Serialize)] diff --git a/core/startos/src/firmware.rs b/core/startos/src/firmware.rs index a70cf9e47..f053bc743 100644 --- a/core/startos/src/firmware.rs +++ b/core/startos/src/firmware.rs @@ -6,11 +6,11 @@ use serde::{Deserialize, Serialize}; use tokio::io::BufReader; use tokio::process::Command; +use crate::PLATFORM; use crate::disk::fsck::RequiresReboot; use crate::prelude::*; -use crate::util::io::open_file; use crate::util::Invoke; -use crate::PLATFORM; +use crate::util::io::open_file; /// Part of the Firmware, look there for more about #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/core/startos/src/hostname.rs b/core/startos/src/hostname.rs index 862411daf..5c88bdcec 100644 --- a/core/startos/src/hostname.rs +++ b/core/startos/src/hostname.rs @@ -1,6 +1,6 @@ use imbl_value::InternedString; use lazy_format::lazy_format; -use rand::{rng, Rng}; +use rand::{Rng, rng}; use tokio::process::Command; use tracing::instrument; @@ -35,8 +35,8 @@ impl Hostname { pub fn generate_hostname() -> Hostname { let mut rng = rng(); - let adjective = &ADJECTIVES[rng.gen_range(0..ADJECTIVES.len())]; - let noun = &NOUNS[rng.gen_range(0..NOUNS.len())]; + let adjective = &ADJECTIVES[rng.random_range(0..ADJECTIVES.len())]; + let noun = &NOUNS[rng.random_range(0..NOUNS.len())]; Hostname(InternedString::from_display(&lazy_format!( "{adjective}-{noun}" ))) diff --git a/core/startos/src/init.rs b/core/startos/src/init.rs index 55b6ef462..ecba6fde9 100644 --- a/core/startos/src/init.rs +++ b/core/startos/src/init.rs @@ -1,18 +1,13 @@ -use std::fs::Permissions; use std::io::Cursor; -use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; -use std::os::unix::fs::PermissionsExt; use std::path::Path; use std::sync::Arc; use std::time::{Duration, SystemTime}; -use axum::extract::ws::{self}; -use color_eyre::eyre::eyre; +use axum::extract::ws; use const_format::formatcp; use futures::{StreamExt, TryStreamExt}; use itertools::Itertools; use models::ResultExt; -use rand::random; use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tokio::process::Command; @@ -21,13 +16,14 @@ use ts_rs::TS; use crate::account::AccountInfo; use crate::context::config::ServerConfig; -use crate::context::{CliContext, InitContext}; +use crate::context::{CliContext, InitContext, RpcContext}; use crate::db::model::public::ServerStatus; use crate::db::model::Database; -use crate::disk::mount::util::unmount; +use crate::developer::OS_DEVELOPER_KEY_PATH; use crate::hostname::Hostname; -use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH; +use crate::middleware::auth::AuthContext; use crate::net::net_controller::{NetController, NetService}; +use crate::net::socks::DEFAULT_SOCKS_LISTEN; use crate::net::utils::find_wifi_iface; use crate::net::web_server::{UpgradableListener, WebServerAcceptorSetter}; use crate::prelude::*; @@ -38,7 +34,7 @@ use crate::rpc_continuations::{Guid, RpcContinuation}; use crate::s9pk::v2::pack::{CONTAINER_DATADIR, CONTAINER_TOOL}; use crate::ssh::SSH_DIR; use crate::system::{get_mem_info, sync_kiosk}; -use crate::util::io::{create_file, open_file, IOHook}; +use crate::util::io::{open_file, IOHook}; use crate::util::lshw::lshw; use crate::util::net::WebSocketExt; use crate::util::{cpupower, Invoke}; @@ -167,28 +163,7 @@ pub async fn init( } local_auth.start(); - tokio::fs::create_dir_all("/run/startos") - .await - .with_ctx(|_| (crate::ErrorKind::Filesystem, "mkdir -p /run/startos"))?; - if tokio::fs::metadata(LOCAL_AUTH_COOKIE_PATH).await.is_err() { - tokio::fs::write( - LOCAL_AUTH_COOKIE_PATH, - base64::encode(random::<[u8; 32]>()).as_bytes(), - ) - .await - .with_ctx(|_| { - ( - crate::ErrorKind::Filesystem, - format!("write {}", LOCAL_AUTH_COOKIE_PATH), - ) - })?; - tokio::fs::set_permissions(LOCAL_AUTH_COOKIE_PATH, Permissions::from_mode(0o046)).await?; - Command::new("chown") - .arg("root:startos") - .arg(LOCAL_AUTH_COOKIE_PATH) - .invoke(crate::ErrorKind::Filesystem) - .await?; - } + RpcContext::init_auth_cookie().await?; local_auth.complete(); load_database.start(); @@ -199,6 +174,16 @@ pub async fn init( load_database.complete(); load_ssh_keys.start(); + crate::developer::write_developer_key( + &peek.as_private().as_developer_key().de()?.0, + OS_DEVELOPER_KEY_PATH, + ) + .await?; + Command::new("chown") + .arg("root:startos") + .arg(OS_DEVELOPER_KEY_PATH) + .invoke(ErrorKind::Filesystem) + .await?; crate::ssh::sync_keys( &Hostname(peek.as_public().as_server_info().as_hostname().de()?), &peek.as_private().as_ssh_privkey().de()?, @@ -206,6 +191,13 @@ pub async fn init( SSH_DIR, ) .await?; + crate::ssh::sync_keys( + &Hostname(peek.as_public().as_server_info().as_hostname().de()?), + &peek.as_private().as_ssh_privkey().de()?, + &Default::default(), + "/root/.ssh", + ) + .await?; load_ssh_keys.complete(); let account = AccountInfo::load(&peek)?; @@ -214,17 +206,12 @@ pub async fn init( let net_ctrl = Arc::new( NetController::init( db.clone(), - cfg.tor_control - .unwrap_or(SocketAddr::from(([127, 0, 0, 1], 9051))), - cfg.tor_socks.unwrap_or(SocketAddr::V4(SocketAddrV4::new( - Ipv4Addr::new(127, 0, 0, 1), - 9050, - ))), &account.hostname, + cfg.socks_listen.unwrap_or(DEFAULT_SOCKS_LISTEN), ) .await?, ); - webserver.try_upgrade(|a| net_ctrl.net_iface.upgrade_listener(a))?; + webserver.try_upgrade(|a| net_ctrl.net_iface.watcher.upgrade_listener(a))?; let os_net_service = net_ctrl.os_bindings().await?; start_net.complete(); @@ -260,7 +247,8 @@ pub async fn init( Command::new("killall") .arg("journalctl") .invoke(crate::ErrorKind::Journald) - .await?; + .await + .log_err(); mount_logs.complete(); tokio::io::copy( &mut open_file("/run/startos/init.log").await?, @@ -508,14 +496,7 @@ pub async fn init_progress(ctx: InitContext) -> Result { } ); - if let Err(e) = ws - .close_result(res.map(|_| "complete").map_err(|e| { - tracing::error!("error in init progress websocket: {e}"); - tracing::debug!("{e:?}"); - e - })) - .await - { + if let Err(e) = ws.close_result(res.map(|_| "complete")).await { tracing::error!("error closing init progress websocket: {e}"); tracing::debug!("{e:?}"); } diff --git a/core/startos/src/install/mod.rs b/core/startos/src/install/mod.rs index 2adac3169..3c4d54ebc 100644 --- a/core/startos/src/install/mod.rs +++ b/core/startos/src/install/mod.rs @@ -4,17 +4,17 @@ use std::time::Duration; use axum::extract::ws; use clap::builder::ValueParserFactory; -use clap::{value_parser, CommandFactory, FromArgMatches, Parser}; +use clap::{CommandFactory, FromArgMatches, Parser, value_parser}; use color_eyre::eyre::eyre; use exver::VersionRange; use futures::{AsyncWriteExt, StreamExt}; -use imbl_value::{json, InternedString}; +use imbl_value::{InternedString, json}; use itertools::Itertools; use models::{FromStrParser, VersionString}; -use reqwest::header::{HeaderMap, CONTENT_LENGTH}; use reqwest::Url; -use rpc_toolkit::yajrc::RpcError; +use reqwest::header::{CONTENT_LENGTH, HeaderMap}; use rpc_toolkit::HandlerArgs; +use rpc_toolkit::yajrc::RpcError; use rustyline_async::ReadlineEvent; use serde::{Deserialize, Serialize}; use tokio::sync::oneshot; @@ -31,9 +31,9 @@ use crate::registry::package::get::GetPackageResponse; use crate::rpc_continuations::{Guid, RpcContinuation}; use crate::s9pk::manifest::PackageId; use crate::upload::upload; +use crate::util::Never; use crate::util::io::open_file; use crate::util::net::WebSocketExt; -use crate::util::Never; pub const PKG_ARCHIVE_DIR: &str = "package-data/archive"; pub const PKG_PUBLIC_DIR: &str = "package-data/public"; @@ -253,7 +253,7 @@ pub async fn sideload( .await; tokio::spawn(async move { if let Err(e) = async { - let key = ctx.db.peek().await.into_private().into_compat_s9pk_key(); + let key = ctx.db.peek().await.into_private().into_developer_key(); ctx.services .install( @@ -483,7 +483,9 @@ pub async fn cli_install( let version = if packages.best.len() == 1 { packages.best.pop_first().map(|(k, _)| k).unwrap() } else { - println!("Multiple flavors of {id} found. Please select one of the following versions to install:"); + println!( + "Multiple flavors of {id} found. Please select one of the following versions to install:" + ); let version; loop { let (mut read, mut output) = rustyline_async::Readline::new("> ".into()) diff --git a/core/startos/src/lib.rs b/core/startos/src/lib.rs index 86bba8264..bcf3d3418 100644 --- a/core/startos/src/lib.rs +++ b/core/startos/src/lib.rs @@ -60,10 +60,12 @@ pub mod s9pk; pub mod service; pub mod setup; pub mod shutdown; +pub mod sign; pub mod sound; pub mod ssh; pub mod status; pub mod system; +pub mod tunnel; pub mod update; pub mod upload; pub mod util; @@ -77,8 +79,8 @@ pub use error::{Error, ErrorKind, ResultExt}; use imbl_value::Value; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{ - from_fn, from_fn_async, from_fn_blocking, CallRemoteHandler, Context, Empty, HandlerExt, - ParentHandler, + CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler, from_fn, from_fn_async, + from_fn_blocking, }; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -89,7 +91,7 @@ use crate::context::{ use crate::disk::fsck::RequiresReboot; use crate::registry::context::{RegistryContext, RegistryUrlParams}; use crate::system::kiosk; -use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; +use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable}; #[derive(Deserialize, Serialize, Parser, TS)] #[serde(rename_all = "camelCase")] @@ -148,13 +150,12 @@ pub fn main_api() -> ParentHandler { ) .subcommand( "net", - net::net::().with_about("Network commands related to tor and dhcp"), + net::net_api::().with_about("Network commands related to tor and dhcp"), ) .subcommand( "auth", - auth::auth::().with_about( - "Commands related to Authentication i.e. login, logout, reset-password", - ), + auth::auth::() + .with_about("Commands related to Authentication i.e. login, logout"), ) .subcommand( "db", @@ -582,7 +583,7 @@ pub fn expanded_api() -> ParentHandler { main_api() .subcommand( "init", - from_fn_blocking(developer::init) + from_fn_async(developer::init) .no_display() .with_about("Create developer key if it doesn't exist"), ) diff --git a/core/startos/src/logs.rs b/core/startos/src/logs.rs index b51593fce..728c9cd71 100644 --- a/core/startos/src/logs.rs +++ b/core/startos/src/logs.rs @@ -551,8 +551,8 @@ pub async fn journalctl( let deserialized_entries = String::from_utf8(cmd.invoke(ErrorKind::Journald).await?)? .lines() .map(serde_json::from_str::) - .collect::, _>>() - .with_kind(ErrorKind::Deserialization)?; + .filter_map(|e| e.ok()) + .collect::>(); if follow { let mut follow_cmd = gen_journalctl_command(&id); @@ -573,11 +573,8 @@ pub async fn journalctl( let follow_deserialized_entries = journalctl_entries .map_err(|e| Error::new(e, crate::ErrorKind::Journald)) - .and_then(|s| { - futures::future::ready( - serde_json::from_str::(&s) - .with_kind(crate::ErrorKind::Deserialization), - ) + .try_filter_map(|s| { + futures::future::ready(Ok(serde_json::from_str::(&s).ok())) }); let entries = futures::stream::iter(deserialized_entries) diff --git a/core/startos/src/lxc/mod.rs b/core/startos/src/lxc/mod.rs index 4ad11ecae..ff9f0fc24 100644 --- a/core/startos/src/lxc/mod.rs +++ b/core/startos/src/lxc/mod.rs @@ -31,7 +31,7 @@ use crate::rpc_continuations::{Guid, RpcContinuation}; use crate::service::ServiceStats; use crate::util::io::open_file; use crate::util::rpc_client::UnixRpcClient; -use crate::util::{new_guid, Invoke}; +use crate::util::{Invoke, new_guid}; const LXC_CONTAINER_DIR: &str = "/var/lib/lxc"; const RPC_DIR: &str = "media/startos/rpc"; // must not be absolute path diff --git a/core/startos/src/middleware/auth.rs b/core/startos/src/middleware/auth.rs index a43f22529..01c3eafbc 100644 --- a/core/startos/src/middleware/auth.rs +++ b/core/startos/src/middleware/auth.rs @@ -1,29 +1,155 @@ use std::borrow::Borrow; use std::collections::BTreeSet; +use std::future::Future; use std::ops::Deref; use std::sync::Arc; use std::time::{Duration, Instant}; use axum::extract::Request; use axum::response::Response; +use base64::Engine; use basic_cookies::Cookie; use chrono::Utc; use color_eyre::eyre::eyre; use digest::Digest; use helpers::const_true; -use http::header::{COOKIE, USER_AGENT}; use http::HeaderValue; -use imbl_value::InternedString; +use http::header::{COOKIE, USER_AGENT}; +use imbl_value::{InternedString, json}; +use rand::random; use rpc_toolkit::yajrc::INTERNAL_ERROR; use rpc_toolkit::{Middleware, RpcRequest, RpcResponse}; use serde::{Deserialize, Serialize}; use sha2::Sha256; +use tokio::io::AsyncWriteExt; +use tokio::process::Command; use tokio::sync::Mutex; +use crate::auth::{Sessions, check_password, write_shadow}; use crate::context::RpcContext; +use crate::db::model::Database; +use crate::middleware::signature::{SignatureAuth, SignatureAuthContext}; use crate::prelude::*; +use crate::rpc_continuations::OpenAuthedContinuations; +use crate::sign::AnyVerifyingKey; +use crate::util::Invoke; +use crate::util::io::{create_file_mod, read_file_to_string}; +use crate::util::iter::TransposeResultIterExt; +use crate::util::serde::BASE64; +use crate::util::sync::SyncMutex; -pub const LOCAL_AUTH_COOKIE_PATH: &str = "/run/startos/rpc.authcookie"; +pub trait AuthContext: SignatureAuthContext { + const LOCAL_AUTH_COOKIE_PATH: &str; + const LOCAL_AUTH_COOKIE_OWNERSHIP: &str; + fn init_auth_cookie() -> impl Future> + Send { + async { + let mut file = create_file_mod(Self::LOCAL_AUTH_COOKIE_PATH, 0o046).await?; + file.write_all(BASE64.encode(random::<[u8; 32]>()).as_bytes()) + .await?; + file.sync_all().await?; + drop(file); + Command::new("chown") + .arg(Self::LOCAL_AUTH_COOKIE_OWNERSHIP) + .arg(Self::LOCAL_AUTH_COOKIE_PATH) + .invoke(crate::ErrorKind::Filesystem) + .await?; + Ok(()) + } + } + fn ephemeral_sessions(&self) -> &SyncMutex; + fn open_authed_continuations(&self) -> &OpenAuthedContinuations>; + fn access_sessions(db: &mut Model) -> &mut Model; + fn check_password(db: &Model, password: &str) -> Result<(), Error>; + #[allow(unused_variables)] + fn post_login_hook(&self, password: &str) -> impl Future> + Send { + async { Ok(()) } + } +} + +impl SignatureAuthContext for RpcContext { + type Database = Database; + type AdditionalMetadata = (); + type CheckPubkeyRes = (); + fn db(&self) -> &TypedPatchDb { + &self.db + } + async fn sig_context( + &self, + ) -> impl IntoIterator + Send, Error>> + Send { + let peek = self.db.peek().await; + self.account + .read() + .await + .hostnames() + .into_iter() + .map(Ok) + .chain( + peek.as_public() + .as_server_info() + .as_network() + .as_host() + .as_public_domains() + .keys() + .map(|k| k.into_iter()) + .transpose(), + ) + .chain( + peek.as_public() + .as_server_info() + .as_network() + .as_host() + .as_private_domains() + .de() + .map(|k| k.into_iter()) + .transpose(), + ) + .collect::>() + } + fn check_pubkey( + db: &Model, + pubkey: Option<&AnyVerifyingKey>, + _: Self::AdditionalMetadata, + ) -> Result { + if let Some(pubkey) = pubkey { + if db.as_private().as_auth_pubkeys().de()?.contains(pubkey) { + return Ok(()); + } + } + + Err(Error::new( + eyre!("Developer Key is not authorized"), + ErrorKind::IncorrectPassword, + )) + } + async fn post_auth_hook(&self, _: Self::CheckPubkeyRes, _: &RpcRequest) -> Result<(), Error> { + Ok(()) + } +} +impl AuthContext for RpcContext { + const LOCAL_AUTH_COOKIE_PATH: &str = "/run/startos/rpc.authcookie"; + const LOCAL_AUTH_COOKIE_OWNERSHIP: &str = "root:startos"; + fn ephemeral_sessions(&self) -> &SyncMutex { + &self.ephemeral_sessions + } + fn open_authed_continuations(&self) -> &OpenAuthedContinuations> { + &self.open_authed_continuations + } + fn access_sessions(db: &mut Model) -> &mut Model { + db.as_private_mut().as_sessions_mut() + } + fn check_password(db: &Model, password: &str) -> Result<(), Error> { + check_password(&db.as_private().as_password().de()?, password) + } + async fn post_login_hook(&self, password: &str) -> Result<(), Error> { + if tokio::fs::metadata("/media/startos/config/overlay/etc/shadow") + .await + .is_err() + { + write_shadow(&password).await?; + } + Ok(()) + } +} #[derive(Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -40,25 +166,25 @@ pub trait AsLogoutSessionId { pub struct HasLoggedOutSessions(()); impl HasLoggedOutSessions { - pub async fn new( + pub async fn new( sessions: impl IntoIterator, - ctx: &RpcContext, + ctx: &C, ) -> Result { let to_log_out: BTreeSet<_> = sessions .into_iter() .map(|s| s.as_logout_session_id()) .collect(); for sid in &to_log_out { - ctx.open_authed_continuations.kill(&Some(sid.clone())) + ctx.open_authed_continuations().kill(&Some(sid.clone())) } - ctx.ephemeral_sessions.mutate(|s| { + ctx.ephemeral_sessions().mutate(|s| { for sid in &to_log_out { s.0.remove(sid); } }); - ctx.db + ctx.db() .mutate(|db| { - let sessions = db.as_private_mut().as_sessions_mut(); + let sessions = C::access_sessions(db); for sid in &to_log_out { sessions.remove(sid)?; } @@ -82,9 +208,9 @@ enum SessionType { } impl HasValidSession { - pub async fn from_header( + pub async fn from_header( header: Option<&HeaderValue>, - ctx: &RpcContext, + ctx: &C, ) -> Result { if let Some(cookie_header) = header { let cookies = Cookie::parse( @@ -94,7 +220,7 @@ impl HasValidSession { ) .with_kind(crate::ErrorKind::Authorization)?; if let Some(cookie) = cookies.iter().find(|c| c.get_name() == "local") { - if let Ok(s) = Self::from_local(cookie).await { + if let Ok(s) = Self::from_local::(cookie).await { return Ok(s); } } @@ -111,12 +237,12 @@ impl HasValidSession { )) } - pub async fn from_session( + pub async fn from_session( session_token: HashSessionToken, - ctx: &RpcContext, + ctx: &C, ) -> Result { let session_hash = session_token.hashed(); - if !ctx.ephemeral_sessions.mutate(|s| { + if !ctx.ephemeral_sessions().mutate(|s| { if let Some(session) = s.0.get_mut(session_hash) { session.last_active = Utc::now(); true @@ -124,10 +250,9 @@ impl HasValidSession { false } }) { - ctx.db + ctx.db() .mutate(|db| { - db.as_private_mut() - .as_sessions_mut() + C::access_sessions(db) .as_idx_mut(session_hash) .ok_or_else(|| { Error::new(eyre!("UNAUTHORIZED"), crate::ErrorKind::Authorization) @@ -143,8 +268,8 @@ impl HasValidSession { Ok(Self(SessionType::Session(session_token))) } - pub async fn from_local(local: &Cookie<'_>) -> Result { - let token = tokio::fs::read_to_string(LOCAL_AUTH_COOKIE_PATH).await?; + pub async fn from_local(local: &Cookie<'_>) -> Result { + let token = read_file_to_string(C::LOCAL_AUTH_COOKIE_PATH).await?; if local.get_value() == &*token { Ok(Self(SessionType::Local)) } else { @@ -258,6 +383,8 @@ pub struct Metadata { login: bool, #[serde(default)] get_session: bool, + #[serde(default)] + get_signer: bool, } #[derive(Clone)] @@ -267,6 +394,7 @@ pub struct Auth { is_login: bool, set_cookie: Option, user_agent: Option, + signature_auth: SignatureAuth, } impl Auth { pub fn new() -> Self { @@ -276,62 +404,73 @@ impl Auth { is_login: false, set_cookie: None, user_agent: None, + signature_auth: SignatureAuth::new(), } } } -impl Middleware for Auth { +impl Middleware for Auth { type Metadata = Metadata; async fn process_http_request( &mut self, - _: &RpcContext, + context: &C, request: &mut Request, ) -> Result<(), Response> { self.cookie = request.headers_mut().remove(COOKIE); self.user_agent = request.headers_mut().remove(USER_AGENT); + self.signature_auth + .process_http_request(context, request) + .await?; Ok(()) } async fn process_rpc_request( &mut self, - context: &RpcContext, + context: &C, metadata: Self::Metadata, request: &mut RpcRequest, ) -> Result<(), RpcResponse> { - if metadata.login { - self.is_login = true; - let guard = self.rate_limiter.lock().await; - if guard.1.elapsed() < Duration::from_secs(20) && guard.0 >= 3 { - return Err(RpcResponse { - id: request.id.take(), - result: Err(Error::new( + async { + if metadata.login { + self.is_login = true; + let guard = self.rate_limiter.lock().await; + if guard.1.elapsed() < Duration::from_secs(20) && guard.0 >= 3 { + return Err(Error::new( eyre!("Please limit login attempts to 3 per 20 seconds."), crate::ErrorKind::RateLimited, - ) - .into()), - }); - } - if let Some(user_agent) = self.user_agent.as_ref().and_then(|h| h.to_str().ok()) { - request.params["__auth_userAgent"] = Value::String(Arc::new(user_agent.to_owned())) - // TODO: will this panic? - } - } else if metadata.authenticated { - match HasValidSession::from_header(self.cookie.as_ref(), &context).await { - Err(e) => { - return Err(RpcResponse { - id: request.id.take(), - result: Err(e.into()), - }) + )); } - Ok(HasValidSession(SessionType::Session(s))) if metadata.get_session => { - request.params["__auth_session"] = - Value::String(Arc::new(s.hashed().deref().to_owned())); + if let Some(user_agent) = self.user_agent.as_ref().and_then(|h| h.to_str().ok()) { + request.params["__auth_userAgent"] = + Value::String(Arc::new(user_agent.to_owned())) // TODO: will this panic? } - _ => (), + } else if metadata.authenticated { + if self + .signature_auth + .process_rpc_request( + context, + from_value(json!({ + "get_signer": metadata.get_signer + }))?, + request, + ) + .await + .is_err() + { + match HasValidSession::from_header(self.cookie.as_ref(), context).await? { + HasValidSession(SessionType::Session(s)) if metadata.get_session => { + request.params["__auth_session"] = + Value::String(Arc::new(s.hashed().deref().to_owned())); + } + _ => (), + } + } } + Ok(()) } - Ok(()) + .await + .map_err(|e| RpcResponse::from_result(Err(e))) } - async fn process_rpc_response(&mut self, _: &RpcContext, response: &mut RpcResponse) { + async fn process_rpc_response(&mut self, _: &C, response: &mut RpcResponse) { if self.is_login { let mut guard = self.rate_limiter.lock().await; if guard.1.elapsed() < Duration::from_secs(20) { @@ -349,7 +488,7 @@ impl Middleware for Auth { let login_res = from_value::(res.clone())?; self.set_cookie = Some( HeaderValue::from_str(&format!( - "session={}; Path=/; SameSite=Lax; Expires=Fri, 31 Dec 9999 23:59:59 GMT;", + "session={}; Path=/; SameSite=Strict; Expires=Fri, 31 Dec 9999 23:59:59 GMT;", login_res.session )) .with_kind(crate::ErrorKind::Network)?, @@ -361,7 +500,7 @@ impl Middleware for Auth { } } } - async fn process_http_response(&mut self, _: &RpcContext, response: &mut Response) { + async fn process_http_response(&mut self, _: &C, response: &mut Response) { if let Some(set_cookie) = self.set_cookie.take() { response.headers_mut().insert("set-cookie", set_cookie); } diff --git a/core/startos/src/middleware/db.rs b/core/startos/src/middleware/db.rs index 4e5f0e037..ec0b94821 100644 --- a/core/startos/src/middleware/db.rs +++ b/core/startos/src/middleware/db.rs @@ -1,6 +1,6 @@ use axum::response::Response; -use http::header::InvalidHeaderValue; use http::HeaderValue; +use http::header::InvalidHeaderValue; use rpc_toolkit::{Middleware, RpcRequest, RpcResponse}; use serde::Deserialize; diff --git a/core/startos/src/middleware/mod.rs b/core/startos/src/middleware/mod.rs index 3438dc3db..c1b8cb573 100644 --- a/core/startos/src/middleware/mod.rs +++ b/core/startos/src/middleware/mod.rs @@ -1,3 +1,4 @@ pub mod auth; pub mod cors; pub mod db; +pub mod signature; diff --git a/core/startos/src/registry/auth.rs b/core/startos/src/middleware/signature.rs similarity index 53% rename from core/startos/src/registry/auth.rs rename to core/startos/src/middleware/signature.rs index 4707bf809..b07d79a20 100644 --- a/core/startos/src/registry/auth.rs +++ b/core/startos/src/middleware/signature.rs @@ -1,45 +1,62 @@ use std::collections::BTreeMap; +use std::future::Future; use std::sync::Arc; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use axum::body::Body; use axum::extract::Request; -use axum::response::Response; -use chrono::Utc; use http::HeaderValue; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{Middleware, RpcRequest, RpcResponse}; -use serde::{Deserialize, Serialize}; -use tokio::io::AsyncWriteExt; +use rpc_toolkit::{Context, Middleware, RpcRequest, RpcResponse}; +use serde::Deserialize; +use serde::de::DeserializeOwned; use tokio::sync::Mutex; -use ts_rs::TS; use url::Url; +use crate::context::CliContext; use crate::prelude::*; -use crate::registry::context::RegistryContext; -use crate::registry::signer::commitment::request::RequestCommitment; -use crate::registry::signer::commitment::Commitment; -use crate::registry::signer::sign::{ - AnySignature, AnySigningKey, AnyVerifyingKey, SignatureScheme, -}; +use crate::sign::commitment::Commitment; +use crate::sign::commitment::request::RequestCommitment; +use crate::sign::{AnySignature, AnySigningKey, AnyVerifyingKey, SignatureScheme}; use crate::util::serde::Base64; -pub const AUTH_SIG_HEADER: &str = "X-StartOS-Registry-Auth-Sig"; +pub trait SignatureAuthContext: Context { + type Database: HasModel> + Send + Sync; + type AdditionalMetadata: DeserializeOwned + Send; + type CheckPubkeyRes: Send; + fn db(&self) -> &TypedPatchDb; + fn sig_context( + &self, + ) -> impl Future + Send, Error>> + Send> + + Send; + fn check_pubkey( + db: &Model, + pubkey: Option<&AnyVerifyingKey>, + metadata: Self::AdditionalMetadata, + ) -> Result; + fn post_auth_hook( + &self, + check_pubkey_res: Self::CheckPubkeyRes, + request: &RpcRequest, + ) -> impl Future> + Send; +} + +pub const AUTH_SIG_HEADER: &str = "X-StartOS-Auth-Sig"; #[derive(Deserialize)] -pub struct Metadata { - #[serde(default)] - admin: bool, +pub struct Metadata { + #[serde(flatten)] + additional: Additional, #[serde(default)] get_signer: bool, } #[derive(Clone)] -pub struct Auth { +pub struct SignatureAuth { nonce_cache: Arc>>, // for replay protection signer: Option>, } -impl Auth { +impl SignatureAuth { pub fn new() -> Self { Self { nonce_cache: Arc::new(Mutex::new(BTreeMap::new())), @@ -65,15 +82,6 @@ impl Auth { } } -#[derive(Serialize, Deserialize, TS)] -pub struct RegistryAdminLogRecord { - pub timestamp: String, - pub name: String, - #[ts(type = "{ id: string | number | null; method: string; params: any }")] - pub request: RpcRequest, - pub key: AnyVerifyingKey, -} - pub struct SignatureHeader { pub commitment: RequestCommitment, pub signer: AnyVerifyingKey, @@ -120,13 +128,13 @@ impl SignatureHeader { } } -impl Middleware for Auth { - type Metadata = Metadata; +impl Middleware for SignatureAuth { + type Metadata = Metadata; async fn process_http_request( &mut self, - ctx: &RegistryContext, + context: &C, request: &mut Request, - ) -> Result<(), Response> { + ) -> Result<(), axum::response::Response> { if request.headers().contains_key(AUTH_SIG_HEADER) { self.signer = Some( async { @@ -138,15 +146,27 @@ impl Middleware for Auth { request .headers() .get(AUTH_SIG_HEADER) - .or_not_found("missing X-StartOS-Registry-Auth-Sig") + .or_not_found(AUTH_SIG_HEADER) .with_kind(ErrorKind::InvalidRequest)?, )?; - signer.scheme().verify_commitment( - &signer, - &commitment, - &ctx.hostname, - &signature, + context.sig_context().await.into_iter().fold( + Err(Error::new( + eyre!("no valid signature context available to verify"), + ErrorKind::Authorization, + )), + |acc, x| { + if acc.is_ok() { + acc + } else { + signer.scheme().verify_commitment( + &signer, + &commitment, + x?.as_ref(), + &signature, + ) + } + }, )?; let now = SystemTime::now() @@ -175,48 +195,83 @@ impl Middleware for Auth { } async fn process_rpc_request( &mut self, - ctx: &RegistryContext, + context: &C, metadata: Self::Metadata, request: &mut RpcRequest, ) -> Result<(), RpcResponse> { - async move { + async { let signer = self.signer.take().transpose()?; if metadata.get_signer { if let Some(signer) = &signer { request.params["__auth_signer"] = to_value(signer)?; } } - if metadata.admin { - let signer = signer - .ok_or_else(|| Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization))?; - let db = ctx.db.peek().await; - let (guid, admin) = db.as_index().as_signers().get_signer_info(&signer)?; - if db.into_admins().de()?.contains(&guid) { - let mut log = tokio::fs::OpenOptions::new() - .create(true) - .append(true) - .open(ctx.datadir.join("admin.log")) - .await?; - log.write_all( - (serde_json::to_string(&RegistryAdminLogRecord { - timestamp: Utc::now().to_rfc3339(), - name: admin.name, - request: request.clone(), - key: signer, - }) - .with_kind(ErrorKind::Serialization)? - + "\n") - .as_bytes(), - ) - .await?; - } else { - return Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization)); - } - } - + let db = context.db().peek().await; + let res = C::check_pubkey(&db, signer.as_ref(), metadata.additional)?; + context.post_auth_hook(res, request).await?; Ok(()) } .await - .map_err(|e| RpcResponse::from_result(Err(e))) + .map_err(|e: Error| rpc_toolkit::RpcResponse::from_result(Err(e))) + } +} + +pub async fn call_remote( + ctx: &CliContext, + url: Url, + sig_context: &str, + method: &str, + params: Value, +) -> Result { + use reqwest::Method; + use reqwest::header::{ACCEPT, CONTENT_LENGTH, CONTENT_TYPE}; + use rpc_toolkit::RpcResponse; + use rpc_toolkit::yajrc::{GenericRpcMethod, Id, RpcRequest}; + + let rpc_req = RpcRequest { + id: Some(Id::Number(0.into())), + method: GenericRpcMethod::<_, _, Value>::new(method), + params, + }; + let body = serde_json::to_vec(&rpc_req)?; + let mut req = ctx + .client + .request(Method::POST, url) + .header(CONTENT_TYPE, "application/json") + .header(ACCEPT, "application/json") + .header(CONTENT_LENGTH, body.len()); + if let Ok(key) = ctx.developer_key() { + req = req.header( + AUTH_SIG_HEADER, + SignatureHeader::sign(&AnySigningKey::Ed25519(key.clone()), &body, sig_context)? + .to_header(), + ); + } + let res = req.body(body).send().await?; + + if !res.status().is_success() { + let status = res.status(); + let txt = res.text().await?; + let mut res = Err(Error::new( + eyre!("{}", status.canonical_reason().unwrap_or(status.as_str())), + ErrorKind::Network, + )); + if !txt.is_empty() { + res = res.with_ctx(|_| (ErrorKind::Network, txt)); + } + return res.map_err(From::from); + } + + match res + .headers() + .get(CONTENT_TYPE) + .and_then(|v| v.to_str().ok()) + { + Some("application/json") => { + serde_json::from_slice::(&*res.bytes().await?) + .with_kind(ErrorKind::Deserialization)? + .result + } + _ => Err(Error::new(eyre!("unknown content type"), ErrorKind::Network).into()), } } diff --git a/core/startos/src/net/acme.rs b/core/startos/src/net/acme.rs index 57bcfe25e..f1648f3eb 100644 --- a/core/startos/src/net/acme.rs +++ b/core/startos/src/net/acme.rs @@ -2,21 +2,21 @@ use std::collections::{BTreeMap, BTreeSet}; use std::str::FromStr; use async_acme::acme::Identifier; -use clap::builder::ValueParserFactory; use clap::Parser; +use clap::builder::ValueParserFactory; use imbl_value::InternedString; use itertools::Itertools; use models::{ErrorData, FromStrParser}; use openssl::pkey::{PKey, Private}; use openssl::x509::X509; -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; use url::Url; use crate::context::{CliContext, RpcContext}; -use crate::db::model::public::AcmeSettings; use crate::db::model::Database; +use crate::db::model::public::AcmeSettings; use crate::prelude::*; use crate::util::serde::{Pem, Pkcs8Doc}; @@ -174,7 +174,7 @@ impl<'a> async_acme::cache::AcmeCache for AcmeCertCache<'a> { } } -pub fn acme() -> ParentHandler { +pub fn acme_api() -> ParentHandler { ParentHandler::new() .subcommand( "init", @@ -257,7 +257,8 @@ pub async fn init( ctx.db .mutate(|db| { db.as_public_mut() - .as_server_info_mut().as_network_mut() + .as_server_info_mut() + .as_network_mut() .as_acme_mut() .insert(&provider, &AcmeSettings { contact }) }) @@ -279,7 +280,8 @@ pub async fn remove( ctx.db .mutate(|db| { db.as_public_mut() - .as_server_info_mut().as_network_mut() + .as_server_info_mut() + .as_network_mut() .as_acme_mut() .remove(&provider) }) diff --git a/core/startos/src/net/dns.rs b/core/startos/src/net/dns.rs index beef7c382..9538fda55 100644 --- a/core/startos/src/net/dns.rs +++ b/core/startos/src/net/dns.rs @@ -1,69 +1,332 @@ use std::borrow::Borrow; use std::collections::BTreeMap; -use std::net::Ipv4Addr; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::{Arc, Weak}; use std::time::Duration; +use clap::Parser; use color_eyre::eyre::eyre; +use futures::future::BoxFuture; +use futures::{FutureExt, StreamExt, TryStreamExt}; use helpers::NonDetachingJoinHandle; -use models::PackageId; +use hickory_client::client::Client; +use hickory_client::proto::runtime::TokioRuntimeProvider; +use hickory_client::proto::tcp::TcpClientStream; +use hickory_client::proto::udp::UdpClientStream; +use hickory_client::proto::xfer::{DnsExchangeBackground, DnsRequestOptions}; +use hickory_client::proto::DnsHandle; +use hickory_server::authority::MessageResponseBuilder; +use hickory_server::proto::op::{Header, ResponseCode}; +use hickory_server::proto::rr::{Name, Record, RecordType}; +use hickory_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo}; +use hickory_server::ServerFuture; +use imbl::OrdMap; +use imbl_value::InternedString; +use itertools::Itertools; +use models::{GatewayId, OptionExt, PackageId}; +use rpc_toolkit::{ + from_fn_async, from_fn_blocking, Context, HandlerArgs, HandlerExt, ParentHandler, +}; +use serde::{Deserialize, Serialize}; use tokio::net::{TcpListener, UdpSocket}; -use tokio::process::Command; -use tokio::sync::RwLock; use tracing::instrument; -use trust_dns_server::authority::MessageResponseBuilder; -use trust_dns_server::proto::op::{Header, ResponseCode}; -use trust_dns_server::proto::rr::{Name, Record, RecordType}; -use trust_dns_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo}; -use trust_dns_server::ServerFuture; -use crate::net::forward::START9_BRIDGE_IFACE; -use crate::util::sync::Watch; -use crate::util::Invoke; -use crate::{Error, ErrorKind, ResultExt}; +use crate::context::RpcContext; +use crate::db::model::public::NetworkInterfaceInfo; +use crate::db::model::Database; +use crate::net::gateway::NetworkInterfaceWatcher; +use crate::prelude::*; +use crate::util::io::file_string_stream; +use crate::util::serde::{display_serializable, HandlerExtSerde}; +use crate::util::sync::{SyncRwLock, Watch}; + +pub fn dns_api() -> ParentHandler { + ParentHandler::new() + .subcommand( + "query", + from_fn_blocking(query_dns::) + .with_display_serializable() + .with_custom_display_fn(|HandlerArgs { params, .. }, res| { + if let Some(format) = params.format { + return display_serializable(format, res); + } + + if let Some(ip) = res { + println!("{}", ip) + } + + Ok(()) + }) + .with_about("Test the DNS configuration for a domain"), + ) + .subcommand( + "set-static", + from_fn_async(set_static_dns) + .no_display() + .with_about("Set static DNS servers"), + ) +} + +#[derive(Deserialize, Serialize, Parser)] +pub struct QueryDnsParams { + pub fqdn: InternedString, +} + +pub fn query_dns( + _: C, + QueryDnsParams { fqdn }: QueryDnsParams, +) -> Result, Error> { + let hints = dns_lookup::AddrInfoHints { + flags: 0, + address: libc::AF_INET, + socktype: 0, + protocol: 0, + }; + dns_lookup::getaddrinfo(Some(&*fqdn), None, Some(hints)) + .map(Some) + .or_else(|e| { + if matches!( + e.kind(), + dns_lookup::LookupErrorKind::NoName | dns_lookup::LookupErrorKind::NoData + ) { + Ok(None) + } else { + Err(std::io::Error::from(e)) + } + }) + .with_kind(ErrorKind::Network)? + .into_iter() + .flatten() + .find_map(|a| match a.map(|a| a.sockaddr.ip()) { + Ok(IpAddr::V4(a)) => Some(Ok(a)), + Err(e) => Some(Err(e)), + _ => None, + }) + .transpose() + .map_err(Error::from) +} + +#[derive(Deserialize, Serialize, Parser)] +pub struct SetStaticDnsParams { + pub servers: Option>, +} + +pub async fn set_static_dns( + ctx: RpcContext, + SetStaticDnsParams { servers }: SetStaticDnsParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + db.as_public_mut() + .as_server_info_mut() + .as_network_mut() + .as_dns_mut() + .as_static_servers_mut() + .ser( + &servers + .map(|s| { + s.into_iter() + .map(|s| { + s.parse::() + .or_else(|_| s.parse::().map(|a| (a, 53).into())) + }) + .collect() + }) + .transpose()?, + ) + }) + .await + .result +} + +#[derive(Default)] +struct ResolveMap { + private_domains: BTreeMap>, + services: BTreeMap, BTreeMap>>, +} pub struct DnsController { - services: Weak, BTreeMap>>>>, + resolve: Weak>, #[allow(dead_code)] - dns_server: NonDetachingJoinHandle>, + dns_server: NonDetachingJoinHandle<()>, +} + +struct DnsClient { + client: Arc>>, + _thread: NonDetachingJoinHandle<()>, +} +impl DnsClient { + pub fn new(db: TypedPatchDb) -> Self { + let client = Arc::new(SyncRwLock::new(Vec::new())); + Self { + client: client.clone(), + _thread: tokio::spawn(async move { + loop { + if let Err::<(), Error>(e) = async { + let mut stream = file_string_stream("/run/systemd/resolve/resolv.conf") + .filter_map(|a| futures::future::ready(a.transpose())) + .boxed(); + let mut conf: String = stream + .next() + .await + .or_not_found("/run/systemd/resolve/resolv.conf")??; + let mut prev_nameservers = Vec::new(); + let mut bg = BTreeMap::>::new(); + loop { + let nameservers = conf + .lines() + .map(|l| l.trim()) + .filter_map(|l| l.strip_prefix("nameserver ")) + .skip(2) + .map(|n| { + n.parse::() + .or_else(|_| n.parse::().map(|a| (a, 53).into())) + }) + .collect::, _>>()?; + let static_nameservers = db + .mutate(|db| { + let dns = db + .as_public_mut() + .as_server_info_mut() + .as_network_mut() + .as_dns_mut(); + dns.as_dhcp_servers_mut().ser(&nameservers)?; + dns.as_static_servers().de() + }) + .await + .result?; + let nameservers = static_nameservers.unwrap_or(nameservers); + if nameservers != prev_nameservers { + let mut existing: BTreeMap<_, _> = + client.peek(|c| c.iter().cloned().collect()); + let mut new = Vec::with_capacity(nameservers.len()); + for addr in &nameservers { + if let Some(existing) = existing.remove(addr) { + new.push((*addr, existing)); + } else { + let client = if let Ok((client, bg_thread)) = + Client::connect( + UdpClientStream::builder( + *addr, + TokioRuntimeProvider::new(), + ) + .build(), + ) + .await + { + bg.insert(*addr, bg_thread.boxed()); + client + } else { + let (stream, sender) = TcpClientStream::new( + *addr, + None, + Some(Duration::from_secs(30)), + TokioRuntimeProvider::new(), + ); + let (client, bg_thread) = + Client::new(stream, sender, None) + .await + .with_kind(ErrorKind::Network)?; + bg.insert(*addr, bg_thread.boxed()); + client + }; + new.push((*addr, client)); + } + } + bg.retain(|n, _| nameservers.iter().any(|a| a == n)); + prev_nameservers = nameservers; + client.replace(new); + } + tokio::select! { + c = stream.next() => conf = c.or_not_found("/run/systemd/resolve/resolv.conf")??, + _ = futures::future::join( + futures::future::join_all(bg.values_mut()), + futures::future::pending::<()>(), + ) => (), + } + } + } + .await + { + tracing::error!("{e}"); + tracing::debug!("{e:?}"); + } + } + }) + .into(), + } + } + fn lookup( + &self, + query: hickory_client::proto::op::Query, + options: DnsRequestOptions, + ) -> Vec { + self.client.peek(|c| { + c.iter() + .map(|(_, c)| c.lookup(query.clone(), options.clone())) + .collect() + }) + } } struct Resolver { - services: Arc, BTreeMap>>>>, + client: DnsClient, + net_iface: Watch>, + resolve: Arc>, } impl Resolver { - async fn resolve(&self, name: &Name) -> Option> { - match name.iter().next_back() { - Some(b"embassy") | Some(b"startos") => { - if let Some(pkg) = name.iter().rev().skip(1).next() { - if let Some(ip) = self.services.read().await.get(&Some( - std::str::from_utf8(pkg) - .unwrap_or_default() - .parse() - .unwrap_or_default(), - )) { + fn resolve(&self, name: &Name, src: IpAddr) -> Option> { + self.resolve.peek(|r| { + if r.private_domains + .get(&*name.to_lowercase().to_ascii()) + .map_or(false, |d| d.strong_count() > 0) + { + if let Some(res) = self.net_iface.peek(|i| { + i.values() + .chain([NetworkInterfaceInfo::lxc_bridge().1]) + .flat_map(|i| i.ip_info.as_ref()) + .find(|i| i.subnets.iter().any(|s| s.contains(&src))) + .map(|ip_info| { + let mut res = ip_info.subnets.iter().collect::>(); + res.sort_by_cached_key(|a| !a.contains(&src)); + res.into_iter().map(|s| s.addr()).collect() + }) + }) { + return Some(res); + } + } + match name.iter().next_back() { + Some(b"embassy") | Some(b"startos") => { + if let Some(pkg) = name.iter().rev().skip(1).next() { + if let Some(ip) = r.services.get(&Some( + std::str::from_utf8(pkg) + .unwrap_or_default() + .parse() + .unwrap_or_default(), + )) { + Some( + ip.iter() + .filter(|(_, rc)| rc.strong_count() > 0) + .map(|(ip, _)| (*ip).into()) + .collect(), + ) + } else { + None + } + } else if let Some(ip) = r.services.get(&None) { Some( ip.iter() .filter(|(_, rc)| rc.strong_count() > 0) - .map(|(ip, _)| *ip) + .map(|(ip, _)| (*ip).into()) .collect(), ) } else { None } - } else if let Some(ip) = self.services.read().await.get(&None) { - Some( - ip.iter() - .filter(|(_, rc)| rc.strong_count() > 0) - .map(|(ip, _)| *ip) - .collect(), - ) - } else { - None } + _ => None, } - _ => None, - } + }) } } @@ -74,132 +337,215 @@ impl RequestHandler for Resolver { request: &Request, mut response_handle: R, ) -> ResponseInfo { - let query = request.request_info().query; - if let Some(ip) = self.resolve(query.name().borrow()).await { - match query.query_type() { - RecordType::A => { - response_handle - .send_response( - MessageResponseBuilder::from_message_request(&*request).build( - Header::response_from_request(request.header()), - &ip.into_iter() - .map(|ip| { - Record::from_rdata( - request.request_info().query.name().to_owned().into(), - 0, - trust_dns_server::proto::rr::RData::A(ip.into()), - ) - }) - .collect::>(), - [], - [], - [], - ), - ) - .await + match async { + let req = request.request_info()?; + let query = req.query; + if let Some(ip) = self.resolve(query.name().borrow(), req.src.ip()) { + match query.query_type() { + RecordType::A => { + let mut header = Header::response_from_request(request.header()); + header.set_recursion_available(true); + response_handle + .send_response( + MessageResponseBuilder::from_message_request(&*request).build( + header, + &ip.into_iter() + .filter_map(|a| { + if let IpAddr::V4(a) = a { + Some(a) + } else { + None + } + }) + .map(|ip| { + Record::from_rdata( + query.name().to_owned().into(), + 0, + hickory_server::proto::rr::RData::A(ip.into()), + ) + }) + .collect::>(), + [], + [], + [], + ), + ) + .await + } + RecordType::AAAA => { + let mut header = Header::response_from_request(request.header()); + header.set_recursion_available(true); + response_handle + .send_response( + MessageResponseBuilder::from_message_request(&*request).build( + header, + &ip.into_iter() + .filter_map(|a| { + if let IpAddr::V6(a) = a { + Some(a) + } else { + None + } + }) + .map(|ip| { + Record::from_rdata( + query.name().to_owned().into(), + 0, + hickory_server::proto::rr::RData::AAAA(ip.into()), + ) + }) + .collect::>(), + [], + [], + [], + ), + ) + .await + } + _ => { + let mut header = Header::response_from_request(request.header()); + header.set_recursion_available(true); + response_handle + .send_response( + MessageResponseBuilder::from_message_request(&*request).build( + header.into(), + [], + [], + [], + [], + ), + ) + .await + } } - _ => { - let res = Header::response_from_request(request.header()); - response_handle - .send_response( - MessageResponseBuilder::from_message_request(&*request).build( - res.into(), - [], - [], - [], - [], - ), - ) - .await + } else { + let query = query.original().clone(); + let mut streams = self.client.lookup(query, DnsRequestOptions::default()); + let mut err = None; + for stream in streams.iter_mut() { + match tokio::time::timeout(Duration::from_secs(5), stream.next()).await { + Ok(Some(Err(e))) => err = Some(e), + Ok(Some(Ok(msg))) => { + return response_handle + .send_response( + MessageResponseBuilder::from_message_request(&*request).build( + Header::response_from_request(request.header()), + msg.answers(), + msg.name_servers(), + &msg.soa().map(|s| s.to_owned().into_record_of_rdata()), + msg.additionals(), + ), + ) + .await; + } + _ => (), + } } + if let Some(e) = err { + tracing::error!("{e}"); + tracing::debug!("{e:?}"); + } + let mut header = Header::response_from_request(request.header()); + header.set_recursion_available(true); + header.set_response_code(ResponseCode::ServFail); + response_handle + .send_response( + MessageResponseBuilder::from_message_request(&*request).build( + header, + [], + [], + [], + [], + ), + ) + .await + } + } + .await + { + Ok(a) => a, + Err(e) => { + tracing::error!("{}", e); + tracing::debug!("{:?}", e); + let mut header = Header::response_from_request(request.header()); + header.set_recursion_available(true); + header.set_response_code(ResponseCode::ServFail); + response_handle + .send_response( + MessageResponseBuilder::from_message_request(&*request).build( + header, + [], + [], + [], + [], + ), + ) + .await + .unwrap_or(header.into()) } - } else { - let mut res = Header::response_from_request(request.header()); - res.set_response_code(ResponseCode::NXDomain); - response_handle - .send_response( - MessageResponseBuilder::from_message_request(&*request).build( - res.into(), - [], - [], - [], - [], - ), - ) - .await } - .unwrap_or_else(|e| { - tracing::error!("{}", e); - tracing::debug!("{:?}", e); - let mut res = Header::response_from_request(request.header()); - res.set_response_code(ResponseCode::ServFail); - res.into() - }) } } impl DnsController { #[instrument(skip_all)] - pub async fn init(mut lxcbr_status: Watch) -> Result { - let services = Arc::new(RwLock::new(BTreeMap::new())); + pub async fn init( + db: TypedPatchDb, + watcher: &NetworkInterfaceWatcher, + ) -> Result { + let resolve = Arc::new(SyncRwLock::new(ResolveMap::default())); let mut server = ServerFuture::new(Resolver { - services: services.clone(), + client: DnsClient::new(db), + net_iface: watcher.subscribe(), + resolve: resolve.clone(), }); - let dns_server = tokio::spawn(async move { - server.register_listener( - TcpListener::bind((Ipv4Addr::LOCALHOST, 53)) + let dns_server = tokio::spawn( + async move { + server.register_listener( + TcpListener::bind((Ipv6Addr::UNSPECIFIED, 53)) + .await + .with_kind(ErrorKind::Network)?, + Duration::from_secs(30), + ); + server.register_socket( + UdpSocket::bind((Ipv6Addr::UNSPECIFIED, 53)) + .await + .with_kind(ErrorKind::Network)?, + ); + + server + .block_until_done() .await - .with_kind(ErrorKind::Network)?, - Duration::from_secs(30), - ); - server.register_socket( - UdpSocket::bind((Ipv4Addr::LOCALHOST, 53)) - .await - .with_kind(ErrorKind::Network)?, - ); - - lxcbr_status.wait_for(|a| *a).await; - - Command::new("resolvectl") - .arg("dns") - .arg(START9_BRIDGE_IFACE) - .arg("127.0.0.1") - .invoke(ErrorKind::Network) - .await?; - Command::new("resolvectl") - .arg("domain") - .arg(START9_BRIDGE_IFACE) - .arg("embassy") - .invoke(ErrorKind::Network) - .await?; - - server - .block_until_done() - .await - .map_err(|e| Error::new(e, ErrorKind::Network)) - }) + .with_kind(ErrorKind::Network) + } + .map(|r| { + r.log_err(); + }), + ) .into(); Ok(Self { - services: Arc::downgrade(&services), + resolve: Arc::downgrade(&resolve), dns_server, }) } - pub async fn add(&self, pkg_id: Option, ip: Ipv4Addr) -> Result, Error> { - if let Some(services) = Weak::upgrade(&self.services) { - let mut writable = services.write().await; - let mut ips = writable.remove(&pkg_id).unwrap_or_default(); - let rc = if let Some(rc) = Weak::upgrade(&ips.remove(&ip).unwrap_or_default()) { - rc - } else { - Arc::new(()) - }; - ips.insert(ip, Arc::downgrade(&rc)); - writable.insert(pkg_id, ips); - Ok(rc) + pub fn add_service(&self, pkg_id: Option, ip: Ipv4Addr) -> Result, Error> { + if let Some(resolve) = Weak::upgrade(&self.resolve) { + resolve.mutate(|writable| { + let ips = writable.services.entry(pkg_id).or_default(); + let weak = ips.entry(ip).or_default(); + let rc = if let Some(rc) = Weak::upgrade(&*weak) { + rc + } else { + let new = Arc::new(()); + *weak = Arc::downgrade(&new); + new + }; + Ok(rc) + }) } else { Err(Error::new( eyre!("DNS Server Thread has exited"), @@ -208,17 +554,65 @@ impl DnsController { } } - pub async fn gc(&self, pkg_id: Option, ip: Ipv4Addr) -> Result<(), Error> { - if let Some(services) = Weak::upgrade(&self.services) { - let mut writable = services.write().await; - let mut ips = writable.remove(&pkg_id).unwrap_or_default(); - if let Some(rc) = Weak::upgrade(&ips.remove(&ip).unwrap_or_default()) { - ips.insert(ip, Arc::downgrade(&rc)); - } - if !ips.is_empty() { - writable.insert(pkg_id, ips); - } - Ok(()) + pub fn gc_service(&self, pkg_id: Option, ip: Ipv4Addr) -> Result<(), Error> { + if let Some(resolve) = Weak::upgrade(&self.resolve) { + resolve.mutate(|writable| { + let mut ips = writable.services.remove(&pkg_id).unwrap_or_default(); + if let Some(rc) = Weak::upgrade(&ips.remove(&ip).unwrap_or_default()) { + ips.insert(ip, Arc::downgrade(&rc)); + } + if !ips.is_empty() { + writable.services.insert(pkg_id, ips); + } + Ok(()) + }) + } else { + Err(Error::new( + eyre!("DNS Server Thread has exited"), + crate::ErrorKind::Network, + )) + } + } + + pub fn add_private_domain(&self, fqdn: InternedString) -> Result, Error> { + if let Some(resolve) = Weak::upgrade(&self.resolve) { + resolve.mutate(|writable| { + let weak = writable.private_domains.entry(fqdn).or_default(); + let rc = if let Some(rc) = Weak::upgrade(&*weak) { + rc + } else { + let new = Arc::new(()); + *weak = Arc::downgrade(&new); + new + }; + Ok(rc) + }) + } else { + Err(Error::new( + eyre!("DNS Server Thread has exited"), + crate::ErrorKind::Network, + )) + } + } + + pub fn gc_private_domains<'a, BK: Ord + 'a>( + &self, + domains: impl IntoIterator + 'a, + ) -> Result<(), Error> + where + InternedString: Borrow, + { + if let Some(resolve) = Weak::upgrade(&self.resolve) { + resolve.mutate(|writable| { + for domain in domains { + if let Some((k, v)) = writable.private_domains.remove_entry(domain) { + if v.strong_count() > 0 { + writable.private_domains.insert(k, v); + } + } + } + Ok(()) + }) } else { Err(Error::new( eyre!("DNS Server Thread has exited"), diff --git a/core/startos/src/net/forward.rs b/core/startos/src/net/forward.rs index 9e189f013..adc36ff62 100644 --- a/core/startos/src/net/forward.rs +++ b/core/startos/src/net/forward.rs @@ -1,19 +1,22 @@ use std::collections::{BTreeMap, BTreeSet}; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::net::{IpAddr, SocketAddr, SocketAddrV6}; use std::sync::{Arc, Weak}; use futures::channel::oneshot; use helpers::NonDetachingJoinHandle; use id_pool::IdPool; -use imbl_value::InternedString; +use imbl::OrdMap; +use models::GatewayId; use serde::{Deserialize, Serialize}; use tokio::process::Command; use tokio::sync::mpsc; use crate::db::model::public::NetworkInterfaceInfo; +use crate::net::gateway::{DynInterfaceFilter, InterfaceFilter}; +use crate::net::utils::ipv6_is_link_local; use crate::prelude::*; -use crate::util::sync::Watch; use crate::util::Invoke; +use crate::util::sync::Watch; pub const START9_BRIDGE_IFACE: &str = "lxcbr0"; pub const FIRST_DYNAMIC_PRIVATE_PORT: u16 = 49152; @@ -39,106 +42,162 @@ impl AvailablePorts { } } -#[derive(Debug)] struct ForwardRequest { - public: bool, + external: u16, target: SocketAddr, + filter: DynInterfaceFilter, rc: Weak<()>, } -#[derive(Debug, Default)] -struct ForwardState { - requested: BTreeMap, - current: BTreeMap>, +struct ForwardEntry { + external: u16, + target: SocketAddr, + prev_filter: DynInterfaceFilter, + forwards: BTreeMap, + rc: Weak<()>, } -impl ForwardState { - async fn sync( +impl ForwardEntry { + fn new(external: u16, target: SocketAddr, rc: Weak<()>) -> Self { + Self { + external, + target, + prev_filter: false.into_dyn(), + forwards: BTreeMap::new(), + rc, + } + } + + fn take(&mut self) -> Self { + Self { + external: self.external, + target: self.target, + prev_filter: std::mem::replace(&mut self.prev_filter, false.into_dyn()), + forwards: std::mem::take(&mut self.forwards), + rc: self.rc.clone(), + } + } + + async fn destroy(mut self) -> Result<(), Error> { + while let Some((source, interface)) = self.forwards.pop_first() { + unforward(interface.as_str(), source, self.target).await?; + } + Ok(()) + } + + async fn update( &mut self, - interfaces: &BTreeMap)>, + ip_info: &OrdMap, + filter: Option, ) -> Result<(), Error> { - let private_interfaces = interfaces + if self.rc.strong_count() == 0 { + return self.take().destroy().await; + } + let filter_ref = filter.as_ref().unwrap_or(&self.prev_filter); + let mut keep = BTreeSet::::new(); + for (iface, info) in ip_info .iter() - .filter(|(_, (public, _))| !*public) - .map(|(i, _)| i) - .collect::>(); - let all_interfaces = interfaces.keys().collect::>(); - self.requested.retain(|_, req| req.rc.strong_count() > 0); - for external in self - .requested - .keys() - .chain(self.current.keys()) - .copied() - .collect::>() + .chain([NetworkInterfaceInfo::loopback()]) + .filter(|(id, info)| filter_ref.filter(*id, *info)) { - match ( - self.requested.get(&external), - self.current.get_mut(&external), - ) { - (Some(req), Some(cur)) => { - let expected = if req.public { - &all_interfaces - } else { - &private_interfaces + if let Some(ip_info) = &info.ip_info { + for ipnet in &ip_info.subnets { + let addr = match ipnet.addr() { + IpAddr::V6(ip6) => SocketAddrV6::new( + ip6, + self.external, + 0, + if ipv6_is_link_local(ip6) { + ip_info.scope_id + } else { + 0 + }, + ) + .into(), + ip => SocketAddr::new(ip, self.external), }; - let actual = cur.keys().collect::>(); - let mut to_rm = actual - .difference(expected) - .copied() - .map(|i| (i.clone(), &interfaces[i].1)) - .collect::>(); - let mut to_add = expected - .difference(&actual) - .copied() - .map(|i| (i.clone(), &interfaces[i].1)) - .collect::>(); - for interface in actual.intersection(expected).copied() { - if cur[interface] != req.target { - to_rm.insert(interface.clone(), &interfaces[interface].1); - to_add.insert(interface.clone(), &interfaces[interface].1); - } - } - for (interface, ips) in to_rm { - for ip in ips { - unforward(&*interface, (*ip, external).into(), cur[&interface]).await?; - } - cur.remove(&interface); - } - for (interface, ips) in to_add { - cur.insert(interface.clone(), req.target); - for ip in ips { - forward(&*interface, (*ip, external).into(), cur[&interface]).await?; - } + keep.insert(addr); + if !self.forwards.contains_key(&addr) { + forward(iface.as_str(), addr, self.target).await?; + self.forwards.insert(addr, iface.clone()); } } - (Some(req), None) => { - let cur = self.current.entry(external).or_default(); - for interface in if req.public { - &all_interfaces - } else { - &private_interfaces - } - .into_iter() - .copied() - { - cur.insert(interface.clone(), req.target); - for ip in &interfaces[interface].1 { - forward(&**interface, (*ip, external).into(), req.target).await?; - } - } - } - (None, Some(cur)) => { - let to_rm = cur.keys().cloned().collect::>(); - for interface in to_rm { - for ip in &interfaces[&interface].1 { - unforward(&*interface, (*ip, external).into(), cur[&interface]).await?; - } - cur.remove(&interface); - } - self.current.remove(&external); - } - _ => (), } } + let rm = self + .forwards + .keys() + .copied() + .filter(|a| !keep.contains(a)) + .collect::>(); + for rm in rm { + if let Some((source, interface)) = self.forwards.remove_entry(&rm) { + unforward(interface.as_str(), source, self.target).await?; + } + } + if let Some(filter) = filter { + self.prev_filter = filter; + } + Ok(()) + } + + async fn update_request( + &mut self, + ForwardRequest { + external, + target, + filter, + rc, + }: ForwardRequest, + ip_info: &OrdMap, + ) -> Result<(), Error> { + if external != self.external || target != self.target { + self.take().destroy().await?; + *self = Self::new(external, target, rc); + self.update(ip_info, Some(filter)).await?; + } else { + if self.prev_filter != filter { + self.update(ip_info, Some(filter)).await?; + } + self.rc = rc; + } + Ok(()) + } +} +impl Drop for ForwardEntry { + fn drop(&mut self) { + if !self.forwards.is_empty() { + let take = self.take(); + tokio::spawn(async move { + take.destroy().await.log_err(); + }); + } + } +} + +#[derive(Default)] +struct ForwardState { + state: BTreeMap, +} +impl ForwardState { + async fn handle_request( + &mut self, + request: ForwardRequest, + ip_info: &OrdMap, + ) -> Result<(), Error> { + self.state + .entry(request.external) + .or_insert_with(|| ForwardEntry::new(request.external, request.target, Weak::new())) + .update_request(request, ip_info) + .await + } + async fn sync( + &mut self, + ip_info: &OrdMap, + ) -> Result<(), Error> { + for entry in self.state.values_mut() { + entry.update(ip_info, None).await?; + } + self.state.retain(|_, fwd| !fwd.forwards.is_empty()); Ok(()) } } @@ -150,87 +209,37 @@ fn err_has_exited(_: T) -> Error { ) } -pub struct LanPortForwardController { - req: mpsc::UnboundedSender<( - Option<(u16, ForwardRequest)>, - oneshot::Sender>, - )>, +pub struct PortForwardController { + req: mpsc::UnboundedSender<(Option, oneshot::Sender>)>, _thread: NonDetachingJoinHandle<()>, } -impl LanPortForwardController { - pub fn new(mut ip_info: Watch>) -> Self { - let (req_send, mut req_recv) = mpsc::unbounded_channel(); +impl PortForwardController { + pub fn new(mut ip_info: Watch>) -> Self { + let (req_send, mut req_recv) = mpsc::unbounded_channel::<( + Option, + oneshot::Sender>, + )>(); let thread = NonDetachingJoinHandle::from(tokio::spawn(async move { let mut state = ForwardState::default(); - let mut interfaces = ip_info.peek_and_mark_seen(|ip_info| { - ip_info - .iter() - .map(|(iface, info)| { - ( - iface.clone(), - ( - info.inbound(), - info.ip_info.as_ref().map_or(Vec::new(), |i| { - i.subnets - .iter() - .filter_map(|s| { - if let IpAddr::V4(ip) = s.addr() { - Some(ip) - } else { - None - } - }) - .collect() - }), - ), - ) - }) - .collect() - }); - let mut reply: Option>> = None; + let mut interfaces = ip_info.read_and_mark_seen(); loop { tokio::select! { msg = req_recv.recv() => { if let Some((msg, re)) = msg { - if let Some((external, req)) = msg { - state.requested.insert(external, req); + if let Some(req) = msg { + re.send(state.handle_request(req, &interfaces).await).ok(); + } else { + re.send(state.sync(&interfaces).await).ok(); } - reply = Some(re); } else { break; } } _ = ip_info.changed() => { - interfaces = ip_info.peek(|ip_info| { - ip_info - .iter() - .map(|(iface, info)| (iface.clone(), ( - info.inbound(), - info.ip_info.as_ref().map_or(Vec::new(), |i| { - i.subnets - .iter() - .filter_map(|s| { - if let IpAddr::V4(ip) = s.addr() { - Some(ip) - } else { - None - } - }) - .collect() - }), - ))) - .collect() - }); + interfaces = ip_info.read(); + state.sync(&interfaces).await.log_err(); } } - let res = state.sync(&interfaces).await; - if let Err(e) = &res { - tracing::error!("Error in PortForwardController: {e}"); - tracing::debug!("{e:?}"); - } - if let Some(re) = reply.take() { - let _ = re.send(res); - } } })); Self { @@ -238,19 +247,22 @@ impl LanPortForwardController { _thread: thread, } } - pub async fn add(&self, port: u16, public: bool, target: SocketAddr) -> Result, Error> { + pub async fn add( + &self, + external: u16, + filter: impl InterfaceFilter, + target: SocketAddr, + ) -> Result, Error> { let rc = Arc::new(()); let (send, recv) = oneshot::channel(); self.req .send(( - Some(( - port, - ForwardRequest { - public, - target, - rc: Arc::downgrade(&rc), - }, - )), + Some(ForwardRequest { + external, + target, + filter: filter.into_dyn(), + rc: Arc::downgrade(&rc), + }), send, )) .map_err(err_has_exited)?; diff --git a/core/startos/src/net/network_interface.rs b/core/startos/src/net/gateway.rs similarity index 53% rename from core/startos/src/net/network_interface.rs rename to core/startos/src/net/gateway.rs index f69989261..83db71356 100644 --- a/core/startos/src/net/network_interface.rs +++ b/core/startos/src/net/gateway.rs @@ -1,5 +1,7 @@ -use std::collections::{BTreeMap, BTreeSet}; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6}; +use std::any::Any; +use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::future::Future; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV6}; use std::sync::{Arc, Weak}; use std::task::Poll; use std::time::Duration; @@ -7,9 +9,11 @@ use std::time::Duration; use clap::Parser; use futures::{FutureExt, Stream, StreamExt, TryStreamExt}; use helpers::NonDetachingJoinHandle; +use imbl::{OrdMap, OrdSet}; use imbl_value::InternedString; use ipnet::IpNet; use itertools::Itertools; +use models::GatewayId; use nix::net::if_::if_nametoindex; use patch_db::json_ptr::JsonPointer; use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; @@ -17,6 +21,7 @@ use serde::{Deserialize, Serialize}; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::net::{TcpListener, TcpStream}; use tokio::process::Command; +use tokio::sync::oneshot; use ts_rs::TS; use zbus::proxy::{PropertyChanged, PropertyStream, SignalStream}; use zbus::zvariant::{ @@ -28,16 +33,18 @@ use crate::context::{CliContext, RpcContext}; use crate::db::model::public::{IpInfo, NetworkInterfaceInfo, NetworkInterfaceType}; use crate::db::model::Database; use crate::net::forward::START9_BRIDGE_IFACE; -use crate::net::utils::{ipv6_is_link_local, ipv6_is_local}; +use crate::net::gateway::device::DeviceProxy; +use crate::net::utils::ipv6_is_link_local; use crate::net::web_server::Accept; use crate::prelude::*; +use crate::util::collections::OrdMapIterMut; use crate::util::future::Until; use crate::util::io::open_file; use crate::util::serde::{display_serializable, HandlerExtSerde}; use crate::util::sync::{SyncMutex, Watch}; use crate::util::Invoke; -pub fn network_interface_api() -> ParentHandler { +pub fn gateway_api() -> ParentHandler { ParentHandler::new() .subcommand( "list", @@ -58,7 +65,7 @@ pub fn network_interface_api() -> ParentHandler { info.ip_info.as_ref() .and_then(|ip_info| ip_info.device_type) .map_or_else(|| "UNKNOWN".to_owned(), |ty| format!("{ty:?}")), - info.inbound(), + info.public(), info.ip_info.as_ref().map_or_else( || "".to_owned(), |ip_info| ip_info.subnets @@ -82,85 +89,102 @@ pub fn network_interface_api() -> ParentHandler { Ok(()) }) - .with_about("Show network interfaces StartOS can listen on") + .with_about("Show gateways StartOS can listen on") .with_call_remote::(), ) .subcommand( - "set-inbound", - from_fn_async(set_inbound) + "set-public", + from_fn_async(set_public) .with_metadata("sync_db", Value::Bool(true)) .no_display() - .with_about("Indicate whether this interface has inbound access from the WAN") + .with_about("Indicate whether this gateway has inbound access from the WAN") .with_call_remote::(), ).subcommand( - "unset-inbound", - from_fn_async(unset_inbound) + "unset-public", + from_fn_async(unset_public) .with_metadata("sync_db", Value::Bool(true)) .no_display() - .with_about("Allow this interface to infer whether it has inbound access from the WAN based on its IPv4 address") + .with_about("Allow this gateway to infer whether it has inbound access from the WAN based on its IPv4 address") .with_call_remote::(), ).subcommand("forget", from_fn_async(forget_iface) .with_metadata("sync_db", Value::Bool(true)) .no_display() - .with_about("Forget a disconnected interface") + .with_about("Forget a disconnected gateway") .with_call_remote::() - ) + ).subcommand("set-name", + from_fn_async(set_name) + .with_metadata("sync_db", Value::Bool(true)) + .no_display() + .with_about("Rename a gateway") + .with_call_remote::() + ) } async fn list_interfaces( ctx: RpcContext, -) -> Result, Error> { - Ok(ctx.net_controller.net_iface.ip_info.read()) +) -> Result, Error> { + Ok(ctx.net_controller.net_iface.watcher.ip_info()) } #[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] #[ts(export)] -struct NetworkInterfaceSetInboundParams { - #[ts(type = "string")] - interface: InternedString, - inbound: Option, +struct NetworkInterfaceSetPublicParams { + gateway: GatewayId, + public: Option, } -async fn set_inbound( +async fn set_public( ctx: RpcContext, - NetworkInterfaceSetInboundParams { interface, inbound }: NetworkInterfaceSetInboundParams, + NetworkInterfaceSetPublicParams { gateway, public }: NetworkInterfaceSetPublicParams, ) -> Result<(), Error> { ctx.net_controller .net_iface - .set_inbound(&interface, Some(inbound.unwrap_or(true))) + .set_public(&gateway, Some(public.unwrap_or(true))) .await } #[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] #[ts(export)] -struct UnsetInboundParams { - #[ts(type = "string")] - interface: InternedString, +struct UnsetPublicParams { + gateway: GatewayId, } -async fn unset_inbound( +async fn unset_public( ctx: RpcContext, - UnsetInboundParams { interface }: UnsetInboundParams, + UnsetPublicParams { gateway }: UnsetPublicParams, ) -> Result<(), Error> { ctx.net_controller .net_iface - .set_inbound(&interface, None) + .set_public(&gateway, None) .await } #[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] #[ts(export)] -struct ForgetInterfaceParams { - #[ts(type = "string")] - interface: InternedString, +struct ForgetGatewayParams { + gateway: GatewayId, } async fn forget_iface( ctx: RpcContext, - ForgetInterfaceParams { interface }: ForgetInterfaceParams, + ForgetGatewayParams { gateway }: ForgetGatewayParams, ) -> Result<(), Error> { - ctx.net_controller.net_iface.forget(&interface).await + ctx.net_controller.net_iface.forget(&gateway).await +} + +#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] +#[ts(export)] +struct RenameGatewayParams { + id: GatewayId, + name: InternedString, +} + +async fn set_name( + ctx: RpcContext, + RenameGatewayParams { id, name }: RenameGatewayParams, +) -> Result<(), Error> { + ctx.net_controller.net_iface.set_name(&id, name).await } #[proxy( @@ -169,6 +193,8 @@ async fn forget_iface( default_path = "/org/freedesktop/NetworkManager" )] trait NetworkManager { + fn get_device_by_ip_iface(&self, iface: &str) -> Result; + #[zbus(property)] fn all_devices(&self) -> Result, Error>; @@ -193,6 +219,9 @@ mod active_connection { default_service = "org.freedesktop.NetworkManager" )] pub trait ActiveConnection { + #[zbus(property)] + fn connection(&self) -> Result; + #[zbus(property)] fn id(&self) -> Result; @@ -210,6 +239,24 @@ mod active_connection { } } +#[proxy( + interface = "org.freedesktop.NetworkManager.Settings.Connection", + default_service = "org.freedesktop.NetworkManager" +)] +trait ConnectionSettings { + fn get_settings(&self) -> Result>, Error>; + + fn update2( + &self, + settings: HashMap>, + flags: u32, + args: HashMap>, + ) -> Result, Error>; + + #[zbus(signal)] + fn updated(&self) -> Result<(), Error>; +} + #[proxy( interface = "org.freedesktop.NetworkManager.IP4Config", default_service = "org.freedesktop.NetworkManager" @@ -217,6 +264,12 @@ mod active_connection { trait Ip4Config { #[zbus(property)] fn address_data(&self) -> Result, Error>; + + #[zbus(property)] + fn gateway(&self) -> Result; + + #[zbus(property)] + fn nameserver_data(&self) -> Result, Error>; } #[proxy( @@ -226,6 +279,12 @@ trait Ip4Config { trait Ip6Config { #[zbus(property)] fn address_data(&self) -> Result, Error>; + + #[zbus(property)] + fn gateway(&self) -> Result; + + #[zbus(property)] + fn nameserver_data(&self) -> Result, Error>; } #[derive(Clone, Debug, DeserializeDict, ZValue, ZType)] @@ -241,6 +300,12 @@ impl TryFrom for IpNet { } } +#[derive(Clone, Debug, DeserializeDict, ZValue, ZType)] +#[zvariant(signature = "dict")] +struct NameserverData { + address: String, +} + #[proxy( interface = "org.freedesktop.NetworkManager.DHCP4Config", default_service = "org.freedesktop.NetworkManager" @@ -254,6 +319,7 @@ trait Dhcp4Config { #[zvariant(signature = "dict")] struct Dhcp4Options { ntp_servers: Option, + domain_name_servers: Option, } impl TryFrom for Dhcp4Options { type Error = zbus::Error; @@ -261,6 +327,8 @@ impl TryFrom for Dhcp4Options { let dict = value.downcast_ref::()?; Ok(Self { ntp_servers: dict.get::<_, String>(&zbus::zvariant::Str::from_static("ntp_servers"))?, + domain_name_servers: dict + .get::<_, String>(&zbus::zvariant::Str::from_static("domain_name_servers"))?, }) } } @@ -276,6 +344,8 @@ mod device { default_service = "org.freedesktop.NetworkManager" )] pub trait Device { + fn delete(&self) -> Result<(), Error>; + #[zbus(property)] fn ip_interface(&self) -> Result; @@ -325,8 +395,8 @@ impl<'a> StubStream<'a> for SignalStream<'a> { #[instrument(skip_all)] async fn watcher( - write_to: Watch>, - lxcbr_status: Watch, + watch_ip_info: Watch>, + watch_activation: Watch>, ) { loop { let res: Result<(), Error> = async { @@ -372,11 +442,14 @@ async fn watcher( let iface = InternedString::intern(device_proxy.ip_interface().await?); if iface.is_empty() { continue; - } else if &*iface == START9_BRIDGE_IFACE { + } + let iface: GatewayId = iface.into(); + if watch_activation.peek(|a| a.contains_key(&iface)) { jobs.push(Either::Left(watch_activated( &connection, device_proxy.clone(), - &lxcbr_status, + iface.clone(), + &watch_activation, ))); } @@ -384,14 +457,14 @@ async fn watcher( &connection, device_proxy.clone(), iface.clone(), - &write_to, + &watch_ip_info, ))); ifaces.insert(iface); } - write_to.send_if_modified(|m| { + watch_ip_info.send_if_modified(|m| { let mut changed = false; - for (iface, info) in m { + for (iface, info) in OrdMapIterMut::from(m) { if !ifaces.contains(iface) { info.ip_info = None; changed = true; @@ -440,8 +513,8 @@ async fn get_wan_ipv4(iface: &str) -> Result, Error> { async fn watch_ip( connection: &Connection, device_proxy: device::DeviceProxy<'_>, - iface: InternedString, - write_to: &Watch>, + iface: GatewayId, + write_to: &Watch>, ) -> Result<(), Error> { let mut until = Until::new() .with_stream( @@ -485,6 +558,12 @@ async fn watch_ip( let active_connection_proxy = active_connection::ActiveConnectionProxy::new(&connection, dac).await?; + let settings_proxy = ConnectionSettingsProxy::new( + &connection, + active_connection_proxy.connection().await?, + ) + .await?; + let mut until = Until::new() .with_stream( active_connection_proxy @@ -498,7 +577,8 @@ async fn watch_ip( .receive_dhcp4_config_changed() .await .stub(), - ); + ) + .with_stream(settings_proxy.receive_updated().await?.into_inner().stub()); loop { until @@ -524,7 +604,15 @@ async fn watch_ip( Ip6ConfigProxy::new(&connection, ip6_config.clone()).await?; let mut until = Until::new() .with_stream(ip4_proxy.receive_address_data_changed().await.stub()) - .with_stream(ip6_proxy.receive_address_data_changed().await.stub()); + .with_stream(ip4_proxy.receive_gateway_changed().await.stub()) + .with_stream( + ip4_proxy.receive_nameserver_data_changed().await.stub(), + ) + .with_stream(ip6_proxy.receive_address_data_changed().await.stub()) + .with_stream(ip6_proxy.receive_gateway_changed().await.stub()) + .with_stream( + ip6_proxy.receive_nameserver_data_changed().await.stub(), + ); let dhcp4_proxy = if &*dhcp4_config != "/" { let dhcp4_proxy = @@ -546,7 +634,19 @@ async fn watch_ip( .into_iter() .chain(ip6_proxy.address_data().await?) .collect_vec(); - let mut ntp_servers = BTreeSet::new(); + let lan_ip = [ + Some(ip4_proxy.gateway().await?) + .filter(|g| !g.is_empty()) + .and_then(|g| g.parse::().log_err()), + Some(ip6_proxy.gateway().await?) + .filter(|g| !g.is_empty()) + .and_then(|g| g.parse::().log_err()), + ] + .into_iter() + .filter_map(|a| a) + .collect(); + let mut ntp_servers = OrdSet::new(); + let mut dns_servers = OrdSet::new(); if let Some(dhcp4_proxy) = &dhcp4_proxy { let dhcp = dhcp4_proxy.options().await?; if let Some(ntp) = dhcp.ntp_servers { @@ -555,15 +655,24 @@ async fn watch_ip( .map(InternedString::intern), ); } + if let Some(dns) = dhcp.domain_name_servers { + dns_servers.extend( + dns.split_ascii_whitespace() + .filter_map(|s| { + s.parse::().log_err() + }) + .collect::>(), + ); + } } - let scope_id = if_nametoindex(&*iface) + let scope_id = if_nametoindex(iface.as_str()) .with_kind(ErrorKind::Network)?; - let subnets: BTreeSet = addresses + let subnets: OrdSet = addresses .into_iter() - .map(TryInto::try_into) + .map(IpNet::try_from) .try_collect()?; - let ip_info = if !subnets.is_empty() { - let wan_ip = match get_wan_ipv4(&*iface).await { + let wan_ip = if !subnets.is_empty() { + match get_wan_ipv4(iface.as_str()).await { Ok(a) => a, Err(e) => { tracing::error!( @@ -572,34 +681,49 @@ async fn watch_ip( tracing::debug!("{e:?}"); None } - }; - Some(IpInfo { - name: name.clone(), - scope_id, - device_type, - subnets, - wan_ip, - ntp_servers, - }) + } } else { None }; + let mut ip_info = IpInfo { + name: name.clone(), + scope_id, + device_type, + subnets, + lan_ip, + wan_ip, + ntp_servers, + dns_servers, + }; - write_to.send_if_modified(|m| { - let (inbound, outbound) = m - .get(&iface) - .map_or((None, None), |i| (i.inbound, i.outbound)); - m.insert( - iface.clone(), - NetworkInterfaceInfo { - inbound, - outbound, - ip_info: ip_info.clone(), - }, - ) - .filter(|old| &old.ip_info == &ip_info) - .is_none() - }); + write_to.send_if_modified( + |m: &mut OrdMap| { + let (name, public, secure, prev_wan_ip) = m + .get(&iface) + .map_or((None, None, None, None), |i| { + ( + i.name.clone(), + i.public, + i.secure, + i.ip_info + .as_ref() + .and_then(|i| i.wan_ip), + ) + }); + ip_info.wan_ip = ip_info.wan_ip.or(prev_wan_ip); + m.insert( + iface.clone(), + NetworkInterfaceInfo { + name, + public, + secure, + ip_info: Some(ip_info.clone()), + }, + ) + .filter(|old| &old.ip_info == &Some(ip_info)) + .is_none() + }, + ); Ok::<_, Error>(()) }) @@ -613,11 +737,12 @@ async fn watch_ip( } } -#[instrument(skip(_connection, device_proxy, write_to))] +#[instrument(skip(_connection, device_proxy, watch_activation))] async fn watch_activated( _connection: &Connection, device_proxy: device::DeviceProxy<'_>, - write_to: &Watch, + iface: GatewayId, + watch_activation: &Watch>, ) -> Result<(), Error> { let mut until = Until::new() .with_stream( @@ -637,36 +762,132 @@ async fn watch_activated( loop { until .run(async { - write_to.send(device_proxy._state().await? == 100); + let activated = device_proxy._state().await? == 100; + watch_activation.send_if_modified(|a| { + a.get_mut(&iface) + .map_or(false, |a| std::mem::replace(a, activated) != activated) + }); Ok(()) }) .await?; } } -pub struct NetworkInterfaceController { - db: TypedPatchDb, - lxcbr_status: Watch, - ip_info: Watch>, +pub struct NetworkInterfaceWatcher { + activated: Watch>, + ip_info: Watch>, _watcher: NonDetachingJoinHandle<()>, listeners: SyncMutex>>, } -impl NetworkInterfaceController { - pub fn lxcbr_status(&self) -> Watch { - self.lxcbr_status.clone_unseen() +impl NetworkInterfaceWatcher { + pub fn new( + seed: impl Future> + Send + Sync + 'static, + watch_activated: impl IntoIterator, + ) -> Self { + let ip_info = Watch::new(OrdMap::new()); + let activated = Watch::new( + watch_activated + .into_iter() + .chain([NetworkInterfaceInfo::lxc_bridge().0.clone()]) + .map(|k| (k, false)) + .collect(), + ); + Self { + activated: activated.clone(), + ip_info: ip_info.clone(), + _watcher: tokio::spawn(async move { + let seed = seed.await; + if !seed.is_empty() { + ip_info.send_replace(seed); + } + watcher(ip_info, activated).await + }) + .into(), + listeners: SyncMutex::new(BTreeMap::new()), + } } - pub fn subscribe(&self) -> Watch> { + pub fn activated(&self) -> Watch> { + self.activated.clone_unseen() + } + + pub fn wait_for_activated( + &self, + interface: GatewayId, + ) -> impl Future + Send + Sync + 'static { + let mut activated = self.activated(); + async move { + activated + .wait_for(|a| a.get(&interface).copied().unwrap_or(false)) + .await; + } + } + + pub fn subscribe(&self) -> Watch> { self.ip_info.clone_unseen() } - pub fn ip_info(&self) -> BTreeMap { + pub fn ip_info(&self) -> OrdMap { self.ip_info.read() } + pub fn bind(&self, port: u16) -> Result { + let arc = Arc::new(()); + self.listeners.mutate(|l| { + if l.get(&port).filter(|w| w.strong_count() > 0).is_some() { + return Err(Error::new( + std::io::Error::from_raw_os_error(libc::EADDRINUSE), + ErrorKind::Network, + )); + } + l.insert(port, Arc::downgrade(&arc)); + Ok(()) + })?; + let ip_info = self.ip_info.clone_unseen(); + let activated = self.activated.clone_unseen(); + Ok(NetworkInterfaceListener { + _arc: arc, + ip_info, + activated, + listeners: ListenerMap::new(port), + }) + } + + pub fn upgrade_listener( + &self, + SelfContainedNetworkInterfaceListener { + mut listener, + .. + }: SelfContainedNetworkInterfaceListener, + ) -> Result { + let port = listener.listeners.port; + let arc = &listener._arc; + self.listeners.mutate(|l| { + if l.get(&port).filter(|w| w.strong_count() > 0).is_some() { + return Err(Error::new( + std::io::Error::from_raw_os_error(libc::EADDRINUSE), + ErrorKind::Network, + )); + } + l.insert(port, Arc::downgrade(arc)); + Ok(()) + })?; + let ip_info = self.ip_info.clone_unseen(); + ip_info.mark_changed(); + listener.change_ip_info_source(ip_info); + Ok(listener) + } +} + +pub struct NetworkInterfaceController { + db: TypedPatchDb, + pub watcher: NetworkInterfaceWatcher, + _sync: NonDetachingJoinHandle<()>, +} +impl NetworkInterfaceController { async fn sync( db: &TypedPatchDb, - info: &BTreeMap, + info: &OrdMap, ) -> Result<(), Error> { tracing::debug!("syncronizing {info:?} to db"); @@ -674,7 +895,7 @@ impl NetworkInterfaceController { db.as_public_mut() .as_server_info_mut() .as_network_mut() - .as_network_interfaces_mut() + .as_gateways_mut() .ser(info) }) .await @@ -731,123 +952,89 @@ impl NetworkInterfaceController { Ok(()) } pub fn new(db: TypedPatchDb) -> Self { - let mut ip_info = Watch::new(BTreeMap::new()); - let lxcbr_status = Watch::new(false); + let (seeded_send, seeded) = oneshot::channel(); + let watcher = NetworkInterfaceWatcher::new( + { + let db = db.clone(); + async move { + let info = match db + .peek() + .await + .as_public() + .as_server_info() + .as_network() + .as_gateways() + .de() + { + Ok(mut info) => { + for (_, info) in OrdMapIterMut::from(&mut info) { + info.ip_info = None; + } + info + } + Err(e) => { + tracing::error!("Error loading network interface info: {e}"); + tracing::debug!("{e:?}"); + OrdMap::new() + } + }; + let _ = seeded_send.send(info.clone()); + info + } + }, + [START9_BRIDGE_IFACE.into()], + ); + let mut ip_info_watch = watcher.subscribe(); + ip_info_watch.mark_seen(); Self { db: db.clone(), - lxcbr_status: lxcbr_status.clone(), - ip_info: ip_info.clone(), - _watcher: tokio::spawn(async move { - match db - .peek() - .await - .as_public() - .as_server_info() - .as_network() - .as_network_interfaces() - .de() - { - Ok(mut info) => { - for info in info.values_mut() { - info.ip_info = None; - } - ip_info.send_replace(info); - } - Err(e) => { - tracing::error!("Error loading network interface info: {e}"); - tracing::debug!("{e:?}"); - } - }; - tokio::join!(watcher(ip_info.clone(), lxcbr_status), async { - let res: Result<(), Error> = async { - loop { - if let Err(e) = async { - let ip_info = ip_info.read(); + watcher, + _sync: tokio::spawn(async move { + let res: Result<(), Error> = async { + let mut ip_info = seeded.await.ok(); + loop { + if let Err(e) = async { + if let Some(ip_info) = ip_info { Self::sync(&db, &ip_info).boxed().await?; - - Ok::<_, Error>(()) - } - .await - { - tracing::error!("Error syncing ip info to db: {e}"); - tracing::debug!("{e:?}"); } - let _ = ip_info.changed().await; + Ok::<_, Error>(()) } + .await + { + tracing::error!("Error syncing ip info to db: {e}"); + tracing::debug!("{e:?}"); + } + + let _ = ip_info_watch.changed().await; + ip_info = Some(ip_info_watch.read()); } - .await; - if let Err(e) = res { - tracing::error!("Error syncing ip info to db: {e}"); - tracing::debug!("{e:?}"); - } - }); + } + .await; + if let Err(e) = res { + tracing::error!("Error syncing ip info to db: {e}"); + tracing::debug!("{e:?}"); + } }) .into(), - listeners: SyncMutex::new(BTreeMap::new()), } } - pub fn bind(&self, port: u16) -> Result { - let arc = Arc::new(()); - self.listeners.mutate(|l| { - if l.get(&port).filter(|w| w.strong_count() > 0).is_some() { - return Err(Error::new( - std::io::Error::from_raw_os_error(libc::EADDRINUSE), - ErrorKind::Network, - )); - } - l.insert(port, Arc::downgrade(&arc)); - Ok(()) - })?; - let ip_info = self.ip_info.clone_unseen(); - Ok(NetworkInterfaceListener { - _arc: arc, - ip_info, - listeners: ListenerMap::new(port), - }) - } - - pub fn upgrade_listener( + pub async fn set_public( &self, - SelfContainedNetworkInterfaceListener { - mut listener, - .. - }: SelfContainedNetworkInterfaceListener, - ) -> Result { - let port = listener.listeners.port; - let arc = &listener._arc; - self.listeners.mutate(|l| { - if l.get(&port).filter(|w| w.strong_count() > 0).is_some() { - return Err(Error::new( - std::io::Error::from_raw_os_error(libc::EADDRINUSE), - ErrorKind::Network, - )); - } - l.insert(port, Arc::downgrade(arc)); - Ok(()) - })?; - let ip_info = self.ip_info.clone_unseen(); - ip_info.mark_changed(); - listener.change_ip_info_source(ip_info); - Ok(listener) - } - - pub async fn set_inbound( - &self, - interface: &InternedString, + interface: &GatewayId, public: Option, ) -> Result<(), Error> { let mut sub = self .db .subscribe( - "/public/serverInfo/network/networkInterfaces" + "/public/serverInfo/network/gateways" .parse::>() .with_kind(ErrorKind::Database)?, ) .await; let mut err = None; - let changed = self.ip_info.send_if_modified(|ip_info| { + let changed = self.watcher.ip_info.send_if_modified(|ip_info| { let prev = std::mem::replace( &mut match ip_info.get_mut(interface).or_not_found(interface) { Ok(a) => a, @@ -856,7 +1043,7 @@ impl NetworkInterfaceController { return false; } } - .inbound, + .public, public, ); prev != public @@ -870,17 +1057,17 @@ impl NetworkInterfaceController { Ok(()) } - pub async fn forget(&self, interface: &InternedString) -> Result<(), Error> { + pub async fn forget(&self, interface: &GatewayId) -> Result<(), Error> { let mut sub = self .db .subscribe( - "/public/serverInfo/network/networkInterfaces" + "/public/serverInfo/network/gateways" .parse::>() .with_kind(ErrorKind::Database)?, ) .await; let mut err = None; - let changed = self.ip_info.send_if_modified(|ip_info| { + let changed = self.watcher.ip_info.send_if_modified(|ip_info| { if ip_info .get(interface) .map_or(false, |i| i.ip_info.is_some()) @@ -901,18 +1088,85 @@ impl NetworkInterfaceController { } Ok(()) } + + pub async fn delete_iface(&self, interface: &GatewayId) -> Result<(), Error> { + let Some(has_ip_info) = self + .watcher + .ip_info + .peek(|ifaces| ifaces.get(interface).map(|i| i.ip_info.is_some())) + else { + return Ok(()); + }; + + if has_ip_info { + let mut ip_info = self.watcher.ip_info.clone_unseen(); + + let connection = Connection::system().await?; + + let netman_proxy = NetworkManagerProxy::new(&connection).await?; + + let device = Some( + netman_proxy + .get_device_by_ip_iface(interface.as_str()) + .await?, + ) + .filter(|o| &**o != "/") + .or_not_found(lazy_format!("{interface} in NetworkManager"))?; + + let device_proxy = DeviceProxy::new(&connection, device).await?; + + device_proxy.delete().await?; + + ip_info + .wait_for(|ifaces| ifaces.get(interface).map_or(true, |i| i.ip_info.is_none())) + .await; + } + + self.forget(interface).await?; + + Ok(()) + } + + pub async fn set_name(&self, interface: &GatewayId, name: InternedString) -> Result<(), Error> { + let mut sub = self + .db + .subscribe( + "/public/serverInfo/network/gateways" + .parse::>() + .with_kind(ErrorKind::Database)? + .join_end(interface.as_str()) + .join_end("name"), + ) + .await; + let changed = self.watcher.ip_info.send_if_modified(|i| { + i.get_mut(interface) + .map(|i| { + if i.name.as_ref() != Some(&name) { + i.name = Some(name); + true + } else { + false + } + }) + .unwrap_or(false) + }); + if changed { + sub.recv().await; + } + + Ok(()) + } } struct ListenerMap { - prev_public: bool, + prev_filter: DynInterfaceFilter, port: u16, - listeners: BTreeMap)>, + listeners: BTreeMap)>, } impl ListenerMap { fn from_listener(listener: impl IntoIterator) -> Result { - let mut prev_public = false; let mut port = 0; - let mut listeners = BTreeMap::)>::new(); + let mut listeners = BTreeMap::)>::new(); for listener in listener { let mut local = listener.local_addr().with_kind(ErrorKind::Network)?; if let SocketAddr::V6(l) = &mut local { @@ -926,17 +1180,8 @@ impl ListenerMap { ErrorKind::InvalidRequest, )); } - let public = match local.ip() { - IpAddr::V4(ip4) => { - !ip4.is_loopback() - && (!ip4.is_private() || ip4.octets().starts_with(&[10, 59])) // reserving 10.59 for public wireguard configurations - && !ip4.is_link_local() - } - IpAddr::V6(ip6) => !ipv6_is_local(ip6), - }; - prev_public |= public; port = local.port(); - listeners.insert(local, (listener, public, None)); + listeners.insert(local, (listener, None)); } if port == 0 { return Err(Error::new( @@ -945,16 +1190,172 @@ impl ListenerMap { )); } Ok(Self { - prev_public, + prev_filter: false.into_dyn(), port, listeners, }) } } + +pub trait InterfaceFilter: Any + Clone + std::fmt::Debug + Eq + Ord + Send + Sync { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool; + fn eq(&self, other: &dyn Any) -> bool { + Some(self) == other.downcast_ref::() + } + fn cmp(&self, other: &dyn Any) -> std::cmp::Ordering { + match (self as &dyn Any).type_id().cmp(&other.type_id()) { + std::cmp::Ordering::Equal => { + std::cmp::Ord::cmp(self, other.downcast_ref::().unwrap()) + } + ord => ord, + } + } + fn as_any(&self) -> &dyn Any { + self + } + fn into_dyn(self) -> DynInterfaceFilter { + DynInterfaceFilter::new(self) + } +} + +impl InterfaceFilter for bool { + fn filter(&self, _: &GatewayId, _: &NetworkInterfaceInfo) -> bool { + *self + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct IdFilter(pub GatewayId); +impl InterfaceFilter for IdFilter { + fn filter(&self, id: &GatewayId, _: &NetworkInterfaceInfo) -> bool { + id == &self.0 + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct PublicFilter { + pub public: bool, +} +impl InterfaceFilter for PublicFilter { + fn filter(&self, _: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + self.public == info.public() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct SecureFilter { + pub secure: bool, +} +impl InterfaceFilter for SecureFilter { + fn filter(&self, _: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + self.secure || info.secure() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct AndFilter(pub A, pub B); +impl InterfaceFilter for AndFilter { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + self.0.filter(id, info) && self.1.filter(id, info) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct OrFilter(pub A, pub B); +impl InterfaceFilter for OrFilter { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + self.0.filter(id, info) || self.1.filter(id, info) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct AnyFilter(pub BTreeSet); +impl InterfaceFilter for AnyFilter { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + self.0.iter().any(|f| InterfaceFilter::filter(f, id, info)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct AllFilter(pub BTreeSet); +impl InterfaceFilter for AllFilter { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + self.0.iter().all(|f| InterfaceFilter::filter(f, id, info)) + } +} + +pub trait DynInterfaceFilterT: std::fmt::Debug + Any + Send + Sync { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool; + fn eq(&self, other: &dyn Any) -> bool; + fn cmp(&self, other: &dyn Any) -> std::cmp::Ordering; + fn as_any(&self) -> &dyn Any; +} +impl DynInterfaceFilterT for T { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + InterfaceFilter::filter(self, id, info) + } + fn eq(&self, other: &dyn Any) -> bool { + InterfaceFilter::eq(self, other) + } + fn cmp(&self, other: &dyn Any) -> std::cmp::Ordering { + InterfaceFilter::cmp(self, other) + } + fn as_any(&self) -> &dyn Any { + InterfaceFilter::as_any(self) + } +} + +#[test] +fn test_interface_filter_eq() { + let dyn_t = true.into_dyn(); + assert!(DynInterfaceFilterT::eq( + &dyn_t, + DynInterfaceFilterT::as_any(&true), + )) +} + +#[derive(Clone, Debug)] +pub struct DynInterfaceFilter(Arc); +impl InterfaceFilter for DynInterfaceFilter { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + self.0.filter(id, info) + } + fn eq(&self, other: &dyn Any) -> bool { + self.0.eq(other) + } + fn cmp(&self, other: &dyn Any) -> std::cmp::Ordering { + self.0.cmp(other) + } + fn as_any(&self) -> &dyn Any { + self.0.as_any() + } +} +impl DynInterfaceFilter { + fn new(value: T) -> Self { + Self(Arc::new(value)) + } +} +impl PartialEq for DynInterfaceFilter { + fn eq(&self, other: &Self) -> bool { + DynInterfaceFilterT::eq(&*self.0, DynInterfaceFilterT::as_any(&*other.0)) + } +} +impl Eq for DynInterfaceFilter {} +impl PartialOrd for DynInterfaceFilter { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.0.cmp(other.0.as_any())) + } +} +impl Ord for DynInterfaceFilter { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(other.0.as_any()) + } +} + impl ListenerMap { fn new(port: u16) -> Self { Self { - prev_public: false, + prev_filter: false.into_dyn(), port, listeners: BTreeMap::new(), } @@ -963,79 +1364,62 @@ impl ListenerMap { #[instrument(skip(self))] fn update( &mut self, - ip_info: &BTreeMap, - public: bool, + ip_info: &OrdMap, + lxc_bridge: bool, + filter: &impl InterfaceFilter, ) -> Result<(), Error> { let mut keep = BTreeSet::::new(); - for info in ip_info.values().chain([&NetworkInterfaceInfo { - inbound: Some(false), - outbound: Some(false), - ip_info: Some(IpInfo { - name: "lo".into(), - scope_id: 1, - device_type: None, - subnets: [ - IpNet::new(Ipv4Addr::LOCALHOST.into(), 8).unwrap(), - IpNet::new(Ipv6Addr::LOCALHOST.into(), 128).unwrap(), - ] - .into_iter() - .collect(), - wan_ip: None, - ntp_servers: Default::default(), - }), - }]) { - if public || !info.inbound() { - if let Some(ip_info) = &info.ip_info { - for ipnet in &ip_info.subnets { - let addr = match ipnet.addr() { - IpAddr::V6(ip6) => SocketAddrV6::new( - ip6, - self.port, - 0, - if ipv6_is_link_local(ip6) { - ip_info.scope_id - } else { - 0 - }, - ) - .into(), - ip => SocketAddr::new(ip, self.port), - }; - keep.insert(addr); - if let Some((_, is_public, wan_ip)) = self.listeners.get_mut(&addr) { - *is_public = info.inbound(); - *wan_ip = info.ip_info.as_ref().and_then(|i| i.wan_ip); - continue; - } - self.listeners.insert( - addr, - ( - TcpListener::from_std( - mio::net::TcpListener::bind(addr) - .with_ctx(|_| { - ( - ErrorKind::Network, - lazy_format!("binding to {addr:?}"), - ) - })? - .into(), - ) - .with_kind(ErrorKind::Network)?, - info.inbound(), - info.ip_info.as_ref().and_then(|i| i.wan_ip), - ), - ); + for (_, info) in ip_info + .iter() + .chain([NetworkInterfaceInfo::loopback()]) + .chain(Some(NetworkInterfaceInfo::lxc_bridge()).filter(|_| lxc_bridge)) + .filter(|(id, info)| filter.filter(*id, *info)) + { + if let Some(ip_info) = &info.ip_info { + for ipnet in &ip_info.subnets { + let addr = match ipnet.addr() { + IpAddr::V6(ip6) => SocketAddrV6::new( + ip6, + self.port, + 0, + if ipv6_is_link_local(ip6) { + ip_info.scope_id + } else { + 0 + }, + ) + .into(), + ip => SocketAddr::new(ip, self.port), + }; + keep.insert(addr); + if let Some((_, wan_ip)) = self.listeners.get_mut(&addr) { + *wan_ip = info.ip_info.as_ref().and_then(|i| i.wan_ip); + continue; } + self.listeners.insert( + addr, + ( + TcpListener::from_std( + mio::net::TcpListener::bind(addr) + .with_ctx(|_| { + (ErrorKind::Network, lazy_format!("binding to {addr:?}")) + })? + .into(), + ) + .with_kind(ErrorKind::Network)?, + info.ip_info.as_ref().and_then(|i| i.wan_ip), + ), + ); } } } self.listeners.retain(|key, _| keep.contains(key)); - self.prev_public = public; + self.prev_filter = filter.clone().into_dyn(); Ok(()) } fn poll_accept(&self, cx: &mut std::task::Context<'_>) -> Poll> { - for (bind_addr, listener) in self.listeners.iter() { - if let Poll::Ready((stream, addr)) = listener.0.poll_accept(cx)? { + for (bind_addr, (listener, wan_ip)) in self.listeners.iter() { + if let Poll::Ready((stream, addr)) = listener.poll_accept(cx)? { if let Err(e) = socket2::SockRef::from(&stream).set_tcp_keepalive( &socket2::TcpKeepalive::new() .with_time(Duration::from_secs(900)) @@ -1048,8 +1432,7 @@ impl ListenerMap { return Poll::Ready(Ok(Accepted { stream, peer: addr, - is_public: listener.1, - wan_ip: listener.2, + wan_ip: *wan_ip, bind: *bind_addr, })); } @@ -1058,8 +1441,26 @@ impl ListenerMap { } } +pub fn lookup_info_by_addr( + ip_info: &OrdMap, + addr: SocketAddr, +) -> Option<(&GatewayId, &NetworkInterfaceInfo)> { + ip_info + .iter() + .chain([ + NetworkInterfaceInfo::loopback(), + NetworkInterfaceInfo::lxc_bridge(), + ]) + .find(|(_, i)| { + i.ip_info + .as_ref() + .map_or(false, |i| i.subnets.iter().any(|i| i.addr() == addr.ip())) + }) +} + pub struct NetworkInterfaceListener { - ip_info: Watch>, + pub ip_info: Watch>, + activated: Watch>, listeners: ListenerMap, _arc: Arc<()>, } @@ -1068,25 +1469,36 @@ impl NetworkInterfaceListener { self.listeners.port } + #[cfg_attr(feature = "unstable", inline(never))] pub fn poll_accept( &mut self, cx: &mut std::task::Context<'_>, - public: bool, + filter: &impl InterfaceFilter, ) -> Poll> { - while self.ip_info.poll_changed(cx).is_ready() || public != self.listeners.prev_public { + while self.ip_info.poll_changed(cx).is_ready() + || self.activated.poll_changed(cx).is_ready() + || !DynInterfaceFilterT::eq(&self.listeners.prev_filter, filter.as_any()) + { + let lxc_bridge = self.activated.peek(|a| { + a.get(NetworkInterfaceInfo::lxc_bridge().0) + .copied() + .unwrap_or_default() + }); self.ip_info - .peek(|ip_info| self.listeners.update(ip_info, public))?; + .peek_and_mark_seen(|ip_info| self.listeners.update(ip_info, lxc_bridge, filter))?; } self.listeners.poll_accept(cx) } pub(super) fn new( - mut ip_info: Watch>, + mut ip_info: Watch>, + activated: Watch>, port: u16, ) -> Self { ip_info.mark_unseen(); Self { ip_info, + activated, listeners: ListenerMap::new(port), _arc: Arc::new(()), } @@ -1094,21 +1506,31 @@ impl NetworkInterfaceListener { pub fn change_ip_info_source( &mut self, - mut ip_info: Watch>, + mut ip_info: Watch>, ) { ip_info.mark_unseen(); self.ip_info = ip_info; } - pub async fn accept(&mut self, public: bool) -> Result { - futures::future::poll_fn(|cx| self.poll_accept(cx, public)).await + pub async fn accept(&mut self, filter: &impl InterfaceFilter) -> Result { + futures::future::poll_fn(|cx| self.poll_accept(cx, filter)).await + } + + pub fn check_filter(&self) -> impl FnOnce(SocketAddr, &DynInterfaceFilter) -> bool + 'static { + let ip_info = self.ip_info.clone(); + move |addr, filter| { + ip_info.peek(|i| { + lookup_info_by_addr(i, addr).map_or(false, |(id, info)| { + InterfaceFilter::filter(filter, id, info) + }) + }) + } } } pub struct Accepted { pub stream: TcpStream, pub peer: SocketAddr, - pub is_public: bool, pub wan_ip: Option, pub bind: SocketAddr, } @@ -1119,11 +1541,16 @@ pub struct SelfContainedNetworkInterfaceListener { } impl SelfContainedNetworkInterfaceListener { pub fn bind(port: u16) -> Self { - let ip_info = Watch::new(BTreeMap::new()); - let _watch_thread = tokio::spawn(watcher(ip_info.clone(), Watch::new(false))).into(); + let ip_info = Watch::new(OrdMap::new()); + let activated = Watch::new( + [(NetworkInterfaceInfo::lxc_bridge().0.clone(), false)] + .into_iter() + .collect(), + ); + let _watch_thread = tokio::spawn(watcher(ip_info.clone(), activated.clone())).into(); Self { _watch_thread, - listener: NetworkInterfaceListener::new(ip_info, port), + listener: NetworkInterfaceListener::new(ip_info, activated, port), } } } diff --git a/core/startos/src/net/host/address.rs b/core/startos/src/net/host/address.rs index 973f104bd..393936724 100644 --- a/core/startos/src/net/host/address.rs +++ b/core/startos/src/net/host/address.rs @@ -1,47 +1,46 @@ use std::collections::BTreeSet; +use std::net::Ipv4Addr; use clap::Parser; use imbl_value::InternedString; +use models::GatewayId; use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; -use torut::onion::OnionAddressV3; use ts_rs::TS; use crate::context::{CliContext, RpcContext}; use crate::db::model::DatabaseModel; use crate::net::acme::AcmeProvider; use crate::net::host::{all_hosts, HostApiKind}; +use crate::net::tor::OnionAddress; use crate::prelude::*; use crate::util::serde::{display_serializable, HandlerExtSerde}; -#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] #[serde(rename_all_fields = "camelCase")] #[serde(tag = "kind")] -#[ts(export)] pub enum HostAddress { Onion { - #[ts(type = "string")] - address: OnionAddressV3, + address: OnionAddress, }, Domain { - #[ts(type = "string")] address: InternedString, - public: bool, - acme: Option, + public: Option, + private: bool, }, } -#[derive(Debug, Deserialize, Serialize, TS)] -pub struct DomainConfig { - pub public: bool, +#[derive(Debug, Clone, Deserialize, Serialize, TS)] +pub struct PublicDomainConfig { + pub gateway: GatewayId, pub acme: Option, } -fn check_duplicates(db: &DatabaseModel) -> Result<(), Error> { - let mut onions = BTreeSet::::new(); +fn handle_duplicates(db: &mut DatabaseModel) -> Result<(), Error> { + let mut onions = BTreeSet::::new(); let mut domains = BTreeSet::::new(); - let mut check_onion = |onion: OnionAddressV3| { + let check_onion = |onions: &mut BTreeSet, onion: OnionAddress| { if onions.contains(&onion) { return Err(Error::new( eyre!("onion address {onion} is already in use"), @@ -51,7 +50,7 @@ fn check_duplicates(db: &DatabaseModel) -> Result<(), Error> { onions.insert(onion); Ok(()) }; - let mut check_domain = |domain: InternedString| { + let check_domain = |domains: &mut BTreeSet, domain: InternedString| { if domains.contains(&domain) { return Err(Error::new( eyre!("domain {domain} is already in use"), @@ -61,13 +60,46 @@ fn check_duplicates(db: &DatabaseModel) -> Result<(), Error> { domains.insert(domain); Ok(()) }; + let mut not_in_use = Vec::new(); for host in all_hosts(db) { let host = host?; - for onion in host.as_onions().de()? { - check_onion(onion)?; + let in_use = host.as_bindings().de()?.values().any(|v| v.enabled); + if !in_use { + not_in_use.push(host); + continue; } - for domain in host.as_domains().keys()? { - check_domain(domain)?; + for onion in host.as_onions().de()? { + check_onion(&mut onions, onion)?; + } + let public = host.as_public_domains().keys()?; + for domain in &public { + check_domain(&mut domains, domain.clone())?; + } + for domain in host.as_private_domains().de()? { + if !public.contains(&domain) { + check_domain(&mut domains, domain)?; + } + } + } + for host in not_in_use { + host.as_onions_mut() + .mutate(|o| Ok(o.retain(|o| !onions.contains(o))))?; + host.as_public_domains_mut() + .mutate(|d| Ok(d.retain(|d, _| !domains.contains(d))))?; + host.as_private_domains_mut() + .mutate(|d| Ok(d.retain(|d| !domains.contains(d))))?; + + for onion in host.as_onions().de()? { + check_onion(&mut onions, onion)?; + } + let public = host.as_public_domains().keys()?; + for domain in &public { + check_domain(&mut domains, domain.clone())?; + } + for domain in host.as_private_domains().de()? { + if !public.contains(&domain) { + check_domain(&mut domains, domain)?; + } } } Ok(()) @@ -80,22 +112,50 @@ pub fn address_api( "domain", ParentHandler::::new() .subcommand( - "add", - from_fn_async(add_domain::) - .with_metadata("sync_db", Value::Bool(true)) - .with_inherited(|_, a| a) - .no_display() - .with_about("Add an address to this host") - .with_call_remote::(), + "public", + ParentHandler::::new() + .subcommand( + "add", + from_fn_async(add_public_domain::) + .with_metadata("sync_db", Value::Bool(true)) + .with_inherited(|_, a| a) + .no_display() + .with_about("Add a public domain to this host") + .with_call_remote::(), + ) + .subcommand( + "remove", + from_fn_async(remove_public_domain::) + .with_metadata("sync_db", Value::Bool(true)) + .with_inherited(|_, a| a) + .no_display() + .with_about("Remove a public domain from this host") + .with_call_remote::(), + ) + .with_inherited(|_, a| a), ) .subcommand( - "remove", - from_fn_async(remove_domain::) - .with_metadata("sync_db", Value::Bool(true)) - .with_inherited(|_, a| a) - .no_display() - .with_about("Remove an address from this host") - .with_call_remote::(), + "private", + ParentHandler::::new() + .subcommand( + "add", + from_fn_async(add_private_domain::) + .with_metadata("sync_db", Value::Bool(true)) + .with_inherited(|_, a| a) + .no_display() + .with_about("Add a private domain to this host") + .with_call_remote::(), + ) + .subcommand( + "remove", + from_fn_async(remove_private_domain::) + .with_metadata("sync_db", Value::Bool(true)) + .with_inherited(|_, a| a) + .no_display() + .with_about("Remove a private domain from this host") + .with_call_remote::(), + ) + .with_inherited(|_, a| a), ) .with_inherited(Kind::inheritance), ) @@ -131,7 +191,7 @@ pub fn address_api( use prettytable::*; if let Some(format) = params.format { - display_serializable(format, res); + display_serializable(format, res)?; return Ok(()); } @@ -144,15 +204,25 @@ pub fn address_api( } HostAddress::Domain { address, - public, - acme, + public: Some(PublicDomainConfig { gateway, acme }), + private, } => { table.add_row(row![ address, - *public, + &format!( + "{} ({gateway})", + if *private { "YES" } else { "ONLY" } + ), acme.as_ref().map(|a| a.0.as_str()).unwrap_or("NONE") ]); } + HostAddress::Domain { + address, + public: None, + .. + } => { + table.add_row(row![address, &format!("NO"), "N/A"]); + } } } @@ -166,63 +236,109 @@ pub fn address_api( } #[derive(Deserialize, Serialize, Parser)] -pub struct AddDomainParams { - pub domain: InternedString, - #[arg(long)] - pub private: bool, +pub struct AddPublicDomainParams { + pub fqdn: InternedString, #[arg(long)] pub acme: Option, + pub gateway: GatewayId, } -pub async fn add_domain( +pub async fn add_public_domain( ctx: RpcContext, - AddDomainParams { - domain, - private, + AddPublicDomainParams { + fqdn, acme, - }: AddDomainParams, + gateway, + }: AddPublicDomainParams, inheritance: Kind::Inheritance, -) -> Result<(), Error> { +) -> Result, Error> { ctx.db .mutate(|db| { if let Some(acme) = &acme { - if !db.as_public().as_server_info().as_network().as_acme().contains_key(&acme)? { + if !db + .as_public() + .as_server_info() + .as_network() + .as_acme() + .contains_key(&acme)? + { return Err(Error::new(eyre!("unknown acme provider {}, please run acme.init for this provider first", acme.0), ErrorKind::InvalidRequest)); } } Kind::host_for(&inheritance, db)? - .as_domains_mut() - .insert( - &domain, - &DomainConfig { - public: !private, - acme, - }, - )?; - check_duplicates(db) + .as_public_domains_mut() + .insert(&fqdn, &PublicDomainConfig { acme, gateway })?; + handle_duplicates(db) }) - .await.result?; + .await + .result?; + Kind::sync_host(&ctx, inheritance).await?; + + tokio::task::spawn_blocking(|| { + crate::net::dns::query_dns(ctx, crate::net::dns::QueryDnsParams { fqdn }) + }) + .await + .with_kind(ErrorKind::Unknown)? +} + +#[derive(Deserialize, Serialize, Parser)] +pub struct RemoveDomainParams { + pub fqdn: InternedString, +} + +pub async fn remove_public_domain( + ctx: RpcContext, + RemoveDomainParams { fqdn }: RemoveDomainParams, + inheritance: Kind::Inheritance, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + Kind::host_for(&inheritance, db)? + .as_public_domains_mut() + .remove(&fqdn) + }) + .await + .result?; Kind::sync_host(&ctx, inheritance).await?; Ok(()) } #[derive(Deserialize, Serialize, Parser)] -pub struct RemoveDomainParams { - pub domain: InternedString, +pub struct AddPrivateDomainParams { + pub fqdn: InternedString, } -pub async fn remove_domain( +pub async fn add_private_domain( ctx: RpcContext, - RemoveDomainParams { domain }: RemoveDomainParams, + AddPrivateDomainParams { fqdn }: AddPrivateDomainParams, inheritance: Kind::Inheritance, ) -> Result<(), Error> { ctx.db .mutate(|db| { Kind::host_for(&inheritance, db)? - .as_domains_mut() - .remove(&domain) + .as_private_domains_mut() + .mutate(|d| Ok(d.insert(fqdn)))?; + handle_duplicates(db) + }) + .await + .result?; + Kind::sync_host(&ctx, inheritance).await?; + + Ok(()) +} + +pub async fn remove_private_domain( + ctx: RpcContext, + RemoveDomainParams { fqdn: domain }: RemoveDomainParams, + inheritance: Kind::Inheritance, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + Kind::host_for(&inheritance, db)? + .as_private_domains_mut() + .mutate(|d| Ok(d.remove(&domain))) }) .await .result?; @@ -249,7 +365,7 @@ pub async fn add_onion( ErrorKind::InvalidOnionAddress, ) })? - .parse::()?; + .parse::()?; ctx.db .mutate(|db| { db.as_private().as_key_store().as_onion().get_key(&onion)?; @@ -257,7 +373,7 @@ pub async fn add_onion( Kind::host_for(&inheritance, db)? .as_onions_mut() .mutate(|a| Ok(a.insert(onion)))?; - check_duplicates(db) + handle_duplicates(db) }) .await .result?; @@ -280,7 +396,7 @@ pub async fn remove_onion( ErrorKind::InvalidOnionAddress, ) })? - .parse::()?; + .parse::()?; ctx.db .mutate(|db| { Kind::host_for(&inheritance, db)? diff --git a/core/startos/src/net/host/binding.rs b/core/startos/src/net/host/binding.rs index 96423b9f9..d37a74dad 100644 --- a/core/startos/src/net/host/binding.rs +++ b/core/startos/src/net/host/binding.rs @@ -1,15 +1,18 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::str::FromStr; use clap::builder::ValueParserFactory; use clap::Parser; -use models::{FromStrParser, HostId}; +use imbl::OrdSet; +use models::{FromStrParser, GatewayId, HostId}; use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use ts_rs::TS; use crate::context::{CliContext, RpcContext}; +use crate::db::model::public::NetworkInterfaceInfo; use crate::net::forward::AvailablePorts; +use crate::net::gateway::InterfaceFilter; use crate::net::host::HostApiKind; use crate::net::vhost::AlpnInfo; use crate::prelude::*; @@ -50,11 +53,16 @@ pub struct BindInfo { pub net: NetInfo, } -#[derive(Clone, Copy, Debug, Deserialize, Serialize, TS, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Debug, Deserialize, Serialize, TS, PartialEq, Eq, PartialOrd, Ord)] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct NetInfo { - pub public: bool, + #[ts(as = "BTreeSet::")] + #[serde(default)] + pub private_disabled: OrdSet, + #[ts(as = "BTreeSet::")] + #[serde(default)] + pub public_enabled: OrdSet, pub assigned_port: Option, pub assigned_ssl_port: Option, } @@ -65,16 +73,19 @@ impl BindInfo { if options.add_ssl.is_some() { assigned_ssl_port = Some(available_ports.alloc()?); } - if let Some(secure) = options.secure { - if !secure.ssl || !options.add_ssl.is_some() { - assigned_port = Some(available_ports.alloc()?); - } + if options + .secure + .map_or(true, |s| !(s.ssl && options.add_ssl.is_some())) + { + assigned_port = Some(available_ports.alloc()?); } + Ok(Self { enabled: true, options, net: NetInfo { - public: false, + private_disabled: OrdSet::new(), + public_enabled: OrdSet::new(), assigned_port, assigned_ssl_port, }, @@ -88,7 +99,7 @@ impl BindInfo { let Self { net: mut lan, .. } = self; if options .secure - .map_or(false, |s| !(s.ssl && options.add_ssl.is_some())) + .map_or(true, |s| !(s.ssl && options.add_ssl.is_some())) // doesn't make sense to have 2 listening ports, both with ssl { lan.assigned_port = if let Some(port) = lan.assigned_port.take() { @@ -122,6 +133,15 @@ impl BindInfo { self.enabled = false; } } +impl InterfaceFilter for NetInfo { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + if info.public() { + self.public_enabled.contains(id) + } else { + !self.private_disabled.contains(id) + } + } +} #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, TS)] #[ts(export)] @@ -165,12 +185,11 @@ pub fn binding( } let mut table = Table::new(); - table.add_row(row![bc => "INTERNAL PORT", "ENABLED", "PUBLIC", "EXTERNAL PORT", "EXTERNAL SSL PORT"]); + table.add_row(row![bc => "INTERNAL PORT", "ENABLED", "EXTERNAL PORT", "EXTERNAL SSL PORT"]); for (internal, info) in res { table.add_row(row![ internal, info.enabled, - info.net.public, if let Some(port) = info.net.assigned_port { port.to_string() } else { @@ -192,12 +211,12 @@ pub fn binding( .with_call_remote::(), ) .subcommand( - "set-public", - from_fn_async(set_public::) + "set-gateway-enabled", + from_fn_async(set_gateway_enabled::) .with_metadata("sync_db", Value::Bool(true)) .with_inherited(Kind::inheritance) .no_display() - .with_about("Add an binding to this host") + .with_about("Set whether this gateway should be enabled for this binding") .with_call_remote::(), ) } @@ -215,29 +234,50 @@ pub async fn list_bindings( #[derive(Deserialize, Serialize, Parser, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] -pub struct BindingSetPublicParams { +pub struct BindingGatewaySetEnabledParams { internal_port: u16, + gateway: GatewayId, #[arg(long)] - public: Option, + enabled: Option, } -pub async fn set_public( +pub async fn set_gateway_enabled( ctx: RpcContext, - BindingSetPublicParams { + BindingGatewaySetEnabledParams { internal_port, - public, - }: BindingSetPublicParams, + gateway, + enabled, + }: BindingGatewaySetEnabledParams, inheritance: Kind::Inheritance, ) -> Result<(), Error> { + let enabled = enabled.unwrap_or(true); + let gateway_public = ctx + .net_controller + .net_iface + .watcher + .ip_info() + .get(&gateway) + .or_not_found(&gateway)? + .public(); ctx.db .mutate(|db| { Kind::host_for(&inheritance, db)? .as_bindings_mut() .mutate(|b| { - b.get_mut(&internal_port) - .or_not_found(internal_port)? - .net - .public = public.unwrap_or(true); + let net = &mut b.get_mut(&internal_port).or_not_found(internal_port)?.net; + if gateway_public { + if enabled { + net.public_enabled.insert(gateway); + } else { + net.public_enabled.remove(&gateway); + } + } else { + if enabled { + net.private_disabled.remove(&gateway); + } else { + net.private_disabled.insert(gateway); + } + } Ok(()) }) }) diff --git a/core/startos/src/net/host/mod.rs b/core/startos/src/net/host/mod.rs index 37765a257..e2278a858 100644 --- a/core/startos/src/net/host/mod.rs +++ b/core/startos/src/net/host/mod.rs @@ -8,15 +8,15 @@ use itertools::Itertools; use models::{HostId, PackageId}; use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, OrEmpty, ParentHandler}; use serde::{Deserialize, Serialize}; -use torut::onion::OnionAddressV3; use ts_rs::TS; use crate::context::RpcContext; use crate::db::model::DatabaseModel; use crate::net::forward::AvailablePorts; -use crate::net::host::address::{address_api, DomainConfig, HostAddress}; +use crate::net::host::address::{address_api, HostAddress, PublicDomainConfig}; use crate::net::host::binding::{binding, BindInfo, BindOptions}; use crate::net::service_interface::HostnameInfo; +use crate::net::tor::OnionAddress; use crate::prelude::*; pub mod address; @@ -29,12 +29,13 @@ pub mod binding; pub struct Host { pub bindings: BTreeMap, #[ts(type = "string[]")] - pub onions: BTreeSet, - #[ts(as = "BTreeMap::")] - pub domains: BTreeMap, + pub onions: BTreeSet, + pub public_domains: BTreeMap, + pub private_domains: BTreeSet, /// COMPUTED: NetService::update pub hostname_info: BTreeMap>, // internal port -> Hostnames } + impl AsRef for Host { fn as_ref(&self) -> &Host { self @@ -50,15 +51,23 @@ impl Host { .cloned() .map(|address| HostAddress::Onion { address }) .chain( - self.domains + self.public_domains .iter() - .map( - |(address, DomainConfig { public, acme })| HostAddress::Domain { - address: address.clone(), - public: *public, - acme: acme.clone(), - }, - ), + .map(|(address, config)| HostAddress::Domain { + address: address.clone(), + public: Some(config.clone()), + private: self.private_domains.contains(address), + }), + ) + .chain( + self.private_domains + .iter() + .filter(|a| !self.public_domains.contains_key(*a)) + .map(|address| HostAddress::Domain { + address: address.clone(), + public: None, + private: true, + }), ) } } @@ -115,24 +124,22 @@ pub fn host_for<'a>( }; host_info(db, package_id)?.upsert(host_id, || { let mut h = Host::new(); - h.onions.insert( - tor_key - .or_not_found("generated tor key")? - .public() - .get_onion_address(), - ); + h.onions + .insert(tor_key.or_not_found("generated tor key")?.onion_address()); Ok(h) }) } -pub fn all_hosts(db: &DatabaseModel) -> impl Iterator, Error>> { - [Ok(db.as_public().as_server_info().as_network().as_host())] +pub fn all_hosts(db: &mut DatabaseModel) -> impl Iterator, Error>> { + use patch_db::DestructureMut; + let destructured = db.as_public_mut().destructure_mut(); + [Ok(destructured.server_info.as_network_mut().as_host_mut())] .into_iter() .chain( - [db.as_public().as_package_data().as_entries()] + [destructured.package_data.as_entries_mut()] .into_iter() .flatten_ok() - .map(|entry| entry.and_then(|(_, v)| v.as_hosts().as_entries())) + .map(|entry| entry.and_then(|(_, v)| v.as_hosts_mut().as_entries_mut())) .flatten_ok() .map_ok(|(_, v)| v), ) diff --git a/core/startos/src/net/mod.rs b/core/startos/src/net/mod.rs index 49d3560ef..9d1f139a0 100644 --- a/core/startos/src/net/mod.rs +++ b/core/startos/src/net/mod.rs @@ -3,34 +3,43 @@ use rpc_toolkit::{Context, HandlerExt, ParentHandler}; pub mod acme; pub mod dns; pub mod forward; +pub mod gateway; pub mod host; pub mod keys; pub mod mdns; pub mod net_controller; -pub mod network_interface; pub mod service_interface; +pub mod socks; pub mod ssl; pub mod static_server; pub mod tor; +pub mod tunnel; pub mod utils; pub mod vhost; pub mod web_server; pub mod wifi; -pub fn net() -> ParentHandler { +pub fn net_api() -> ParentHandler { ParentHandler::new() .subcommand( "tor", - tor::tor::().with_about("Tor commands such as list-services, logs, and reset"), + tor::tor_api::().with_about("Tor commands such as list-services, logs, and reset"), ) .subcommand( "acme", - acme::acme::().with_about("Setup automatic clearnet certificate acquisition"), + acme::acme_api::().with_about("Setup automatic clearnet certificate acquisition"), ) .subcommand( - "network-interface", - network_interface::network_interface_api::() - .with_about("View and edit network interface configurations"), + "dns", + dns::dns_api::().with_about("Manage and query DNS"), + ) + .subcommand( + "gateway", + gateway::gateway_api::().with_about("View and edit gateway configurations"), + ) + .subcommand( + "tunnel", + tunnel::tunnel_api::().with_about("Manage tunnels"), ) .subcommand( "vhost", diff --git a/core/startos/src/net/net_controller.rs b/core/startos/src/net/net_controller.rs index 2a6dc535c..11cd36d0f 100644 --- a/core/startos/src/net/net_controller.rs +++ b/core/startos/src/net/net_controller.rs @@ -9,20 +9,24 @@ use ipnet::IpNet; use models::{HostId, OptionExt, PackageId}; use tokio::sync::Mutex; use tokio::task::JoinHandle; -use torut::onion::{OnionAddressV3, TorSecretKeyV3}; use tracing::instrument; +use crate::db::model::public::{NetworkInterfaceInfo, NetworkInterfaceType}; use crate::db::model::Database; use crate::error::ErrorCollection; use crate::hostname::Hostname; use crate::net::dns::DnsController; -use crate::net::forward::LanPortForwardController; +use crate::net::forward::PortForwardController; +use crate::net::gateway::{ + AndFilter, DynInterfaceFilter, IdFilter, InterfaceFilter, NetworkInterfaceController, OrFilter, + PublicFilter, SecureFilter, +}; use crate::net::host::address::HostAddress; use crate::net::host::binding::{AddSslOptions, BindId, BindOptions}; use crate::net::host::{host_for, Host, Hosts}; -use crate::net::network_interface::NetworkInterfaceController; use crate::net::service_interface::{HostnameInfo, IpHostname, OnionHostname}; -use crate::net::tor::TorController; +use crate::net::socks::SocksController; +use crate::net::tor::{OnionAddress, TorController, TorSecretKey}; use crate::net::utils::ipv6_is_local; use crate::net::vhost::{AlpnInfo, TargetInfo, VHostController}; use crate::prelude::*; @@ -36,7 +40,8 @@ pub struct NetController { pub(super) vhost: VHostController, pub(crate) net_iface: Arc, pub(super) dns: DnsController, - pub(super) forward: LanPortForwardController, + pub(super) forward: PortForwardController, + pub(super) socks: SocksController, pub(super) server_hostnames: Vec>, pub(crate) callbacks: Arc, } @@ -44,18 +49,20 @@ pub struct NetController { impl NetController { pub async fn init( db: TypedPatchDb, - tor_control: SocketAddr, - tor_socks: SocketAddr, hostname: &Hostname, + socks_listen: SocketAddr, ) -> Result { let net_iface = Arc::new(NetworkInterfaceController::new(db.clone())); + let tor = TorController::new()?; + let socks = SocksController::new(socks_listen, tor.clone())?; Ok(Self { db: db.clone(), - tor: TorController::new(tor_control, tor_socks), - vhost: VHostController::new(db, net_iface.clone()), - dns: DnsController::init(net_iface.lxcbr_status()).await?, - forward: LanPortForwardController::new(net_iface.subscribe()), + tor, + vhost: VHostController::new(db.clone(), net_iface.clone()), + dns: DnsController::init(db, &net_iface.watcher).await?, + forward: PortForwardController::new(net_iface.watcher.subscribe()), net_iface, + socks, server_hostnames: vec![ // LAN IP None, @@ -78,7 +85,7 @@ impl NetController { package: PackageId, ip: Ipv4Addr, ) -> Result { - let dns = self.dns.add(Some(package.clone()), ip).await?; + let dns = self.dns.add_service(Some(package.clone()), ip)?; let res = NetService::new(NetServiceData { id: Some(package), @@ -92,7 +99,7 @@ impl NetController { } pub async fn os_bindings(self: &Arc) -> Result { - let dns = self.dns.add(None, HOST_IP.into()).await?; + let dns = self.dns.add_service(None, HOST_IP.into())?; let service = NetService::new(NetServiceData { id: None, @@ -126,9 +133,10 @@ impl NetController { #[derive(Default, Debug)] struct HostBinds { - forwards: BTreeMap)>, + forwards: BTreeMap)>, vhosts: BTreeMap<(Option, u16), (TargetInfo, Arc<()>)>, - tor: BTreeMap, Vec>)>, + private_dns: BTreeMap>, + tor: BTreeMap, Vec>)>, } pub struct NetServiceData { @@ -217,9 +225,10 @@ impl NetServiceData { } async fn update(&mut self, ctrl: &NetController, id: HostId, host: Host) -> Result<(), Error> { - let mut forwards: BTreeMap = BTreeMap::new(); + let mut forwards: BTreeMap = BTreeMap::new(); let mut vhosts: BTreeMap<(Option, u16), TargetInfo> = BTreeMap::new(); - let mut tor: BTreeMap)> = + let mut private_dns: BTreeSet = BTreeSet::new(); + let mut tor: BTreeMap)> = BTreeMap::new(); let mut hostname_info: BTreeMap> = BTreeMap::new(); let binds = self.binds.entry(id.clone()).or_default(); @@ -228,7 +237,7 @@ impl NetServiceData { // LAN let server_info = peek.as_public().as_server_info(); - let net_ifaces = ctrl.net_iface.ip_info(); + let net_ifaces = ctrl.net_iface.watcher.ip_info(); let hostname = server_info.as_hostname().de()?; for (port, bind) in &host.bindings { if !bind.enabled { @@ -255,7 +264,7 @@ impl NetServiceData { vhosts.insert( (hostname, external), TargetInfo { - public: bind.net.public, + filter: bind.net.clone().into_dyn(), acme: None, addr, connect_ssl: connect_ssl.clone(), @@ -270,87 +279,177 @@ impl NetServiceData { vhosts.insert( (Some(hostname), external), TargetInfo { - public: false, + filter: OrFilter( + IdFilter( + NetworkInterfaceInfo::loopback().0.clone(), + ), + IdFilter( + NetworkInterfaceInfo::lxc_bridge().0.clone(), + ), + ) + .into_dyn(), acme: None, addr, connect_ssl: connect_ssl.clone(), }, - ); + ); // TODO: wrap onion ssl stream directly in tor ctrl } } HostAddress::Domain { address, public, - acme, + private, } => { if hostnames.insert(address.clone()) { let address = Some(address.clone()); if ssl.preferred_external_port == 443 { - if public && bind.net.public { + if let Some(public) = &public { vhosts.insert( (address.clone(), 5443), TargetInfo { - public: false, - acme: acme.clone(), + filter: AndFilter( + bind.net.clone(), + AndFilter( + IdFilter(public.gateway.clone()), + PublicFilter { public: false }, + ), + ) + .into_dyn(), + acme: public.acme.clone(), + addr, + connect_ssl: connect_ssl.clone(), + }, + ); + vhosts.insert( + (address.clone(), 443), + TargetInfo { + filter: AndFilter( + bind.net.clone(), + if private { + OrFilter( + IdFilter(public.gateway.clone()), + PublicFilter { public: false }, + ) + .into_dyn() + } else { + AndFilter( + IdFilter(public.gateway.clone()), + PublicFilter { public: true }, + ) + .into_dyn() + }, + ) + .into_dyn(), + acme: public.acme.clone(), + addr, + connect_ssl: connect_ssl.clone(), + }, + ); + } else { + vhosts.insert( + (address.clone(), 443), + TargetInfo { + filter: AndFilter( + bind.net.clone(), + PublicFilter { public: false }, + ) + .into_dyn(), + acme: None, addr, connect_ssl: connect_ssl.clone(), }, ); } - vhosts.insert( - (address.clone(), 443), - TargetInfo { - public: public && bind.net.public, - acme, - addr, - connect_ssl: connect_ssl.clone(), - }, - ); } else { - vhosts.insert( - (address.clone(), external), - TargetInfo { - public: public && bind.net.public, - acme, - addr, - connect_ssl: connect_ssl.clone(), - }, - ); + if let Some(public) = public { + vhosts.insert( + (address.clone(), external), + TargetInfo { + filter: AndFilter( + bind.net.clone(), + if private { + OrFilter( + IdFilter(public.gateway.clone()), + PublicFilter { public: false }, + ) + .into_dyn() + } else { + IdFilter(public.gateway.clone()) + .into_dyn() + }, + ) + .into_dyn(), + acme: public.acme.clone(), + addr, + connect_ssl: connect_ssl.clone(), + }, + ); + } else { + vhosts.insert( + (address.clone(), external), + TargetInfo { + filter: AndFilter( + bind.net.clone(), + PublicFilter { public: false }, + ) + .into_dyn(), + acme: None, + addr, + connect_ssl: connect_ssl.clone(), + }, + ); + } } } } } } } - if let Some(security) = bind.options.secure { - if bind.options.add_ssl.is_some() && security.ssl { - // doesn't make sense to have 2 listening ports, both with ssl - } else { - let external = bind.net.assigned_port.or_not_found("assigned lan port")?; - forwards.insert(external, ((self.ip, *port).into(), bind.net.public)); - } + if bind + .options + .secure + .map_or(true, |s| !(s.ssl && bind.options.add_ssl.is_some())) + { + let external = bind.net.assigned_port.or_not_found("assigned lan port")?; + forwards.insert( + external, + ( + (self.ip, *port).into(), + AndFilter( + SecureFilter { + secure: bind.options.secure.is_some(), + }, + bind.net.clone(), + ) + .into_dyn(), + ), + ); } let mut bind_hostname_info: Vec = hostname_info.remove(port).unwrap_or_default(); - for (interface, public, ip_info) in - net_ifaces.iter().filter_map(|(interface, info)| { - if let Some(ip_info) = &info.ip_info { - Some((interface, info.inbound(), ip_info)) - } else { - None - } - }) + for (interface, info) in net_ifaces + .iter() + .filter(|(id, info)| bind.net.filter(id, info)) { - if !public { + let port = bind.net.assigned_port.filter(|_| { + bind.options.secure.map_or(false, |s| { + !(s.ssl && bind.options.add_ssl.is_some()) || info.secure() + }) + }); + if !info.public() + && info.ip_info.as_ref().map_or(false, |i| { + i.device_type != Some(NetworkInterfaceType::Wireguard) + }) + { bind_hostname_info.push(HostnameInfo::Ip { - network_interface_id: interface.clone(), + gateway_id: interface.clone(), public: false, hostname: IpHostname::Local { value: InternedString::from_display(&{ let hostname = &hostname; lazy_format!("{hostname}.local") }), - port: bind.net.assigned_port, + port, ssl_port: bind.net.assigned_ssl_port, }, }); @@ -358,11 +457,13 @@ impl NetServiceData { for address in host.addresses() { if let HostAddress::Domain { address, - public: domain_public, - .. + public, + private, } = address { - if !public || (domain_public && bind.net.public) { + let private = private && !info.public(); + let public = public.as_ref().map_or(false, |p| &p.gateway == interface); + if public || private { if bind .options .add_ssl @@ -370,23 +471,21 @@ impl NetServiceData { .map_or(false, |ssl| ssl.preferred_external_port == 443) { bind_hostname_info.push(HostnameInfo::Ip { - network_interface_id: interface.clone(), - public: public && domain_public && bind.net.public, // TODO: check if port forward is active + gateway_id: interface.clone(), + public, hostname: IpHostname::Domain { - domain: address.clone(), - subdomain: None, + value: address.clone(), port: None, ssl_port: Some(443), }, }); } else { bind_hostname_info.push(HostnameInfo::Ip { - network_interface_id: interface.clone(), + gateway_id: interface.clone(), public, hostname: IpHostname::Domain { - domain: address.clone(), - subdomain: None, - port: bind.net.assigned_port, + value: address.clone(), + port, ssl_port: bind.net.assigned_ssl_port, }, }); @@ -394,14 +493,15 @@ impl NetServiceData { } } } - if !public || bind.net.public { - if let Some(wan_ip) = ip_info.wan_ip.filter(|_| public) { + if let Some(ip_info) = &info.ip_info { + let public = info.public(); + if let Some(wan_ip) = ip_info.wan_ip { bind_hostname_info.push(HostnameInfo::Ip { - network_interface_id: interface.clone(), - public, + gateway_id: interface.clone(), + public: true, hostname: IpHostname::Ipv4 { value: wan_ip, - port: bind.net.assigned_port, + port, ssl_port: bind.net.assigned_ssl_port, }, }); @@ -411,11 +511,11 @@ impl NetServiceData { IpNet::V4(net) => { if !public { bind_hostname_info.push(HostnameInfo::Ip { - network_interface_id: interface.clone(), + gateway_id: interface.clone(), public, hostname: IpHostname::Ipv4 { value: net.addr(), - port: bind.net.assigned_port, + port, ssl_port: bind.net.assigned_ssl_port, }, }); @@ -423,12 +523,12 @@ impl NetServiceData { } IpNet::V6(net) => { bind_hostname_info.push(HostnameInfo::Ip { - network_interface_id: interface.clone(), + gateway_id: interface.clone(), public: public && !ipv6_is_local(net.addr()), hostname: IpHostname::Ipv6 { value: net.addr(), scope_id: ip_info.scope_id, - port: bind.net.assigned_port, + port, ssl_port: bind.net.assigned_ssl_port, }, }); @@ -438,6 +538,7 @@ impl NetServiceData { } } hostname_info.insert(*port, bind_hostname_info); + private_dns.append(&mut hostnames); } } @@ -487,7 +588,7 @@ impl NetServiceData { .as_key_store() .as_onion() .get_key(tor_addr)?; - tor.insert(key.public().get_onion_address(), (key, tor_binds.clone())); + tor.insert(key.onion_address(), (key, tor_binds.clone())); for (internal, ports) in &tor_hostname_ports { let mut bind_hostname_info = hostname_info.remove(internal).unwrap_or_default(); bind_hostname_info.push(HostnameInfo::Onion { @@ -509,8 +610,8 @@ impl NetServiceData { .collect::>(); for external in all { let mut prev = binds.forwards.remove(&external); - if let Some((internal, public)) = forwards.remove(&external) { - prev = prev.filter(|(i, p, _)| i == &internal && *p == public); + if let Some((internal, filter)) = forwards.remove(&external) { + prev = prev.filter(|(i, f, _)| i == &internal && *f == filter); binds.forwards.insert( external, if let Some(prev) = prev { @@ -518,8 +619,8 @@ impl NetServiceData { } else { ( internal, - public, - ctrl.forward.add(external, public, internal).await?, + filter.clone(), + ctrl.forward.add(external, filter, internal).await?, ) }, ); @@ -553,6 +654,22 @@ impl NetServiceData { } } + let mut rm = BTreeSet::new(); + binds.private_dns.retain(|fqdn, _| { + if private_dns.remove(fqdn) { + true + } else { + rm.insert(fqdn.clone()); + false + } + }); + for fqdn in private_dns { + binds + .private_dns + .insert(fqdn.clone(), ctrl.dns.add_private_domain(fqdn)?); + } + ctrl.dns.gc_private_domains(&rm)?; + let all = binds .tor .keys() @@ -568,17 +685,15 @@ impl NetServiceData { if let Some(prev) = prev { prev } else { - let rcs = ctrl - .tor - .add(key, tor_binds.iter().map(|(k, v)| (*k, *v)).collect()) - .await?; + let service = ctrl.tor.service(key)?; + let rcs = service.proxy_all(tor_binds.iter().map(|(k, v)| (*k, *v))); (tor_binds, rcs) }, ); } else { if let Some((_, rc)) = prev { drop(rc); - ctrl.tor.gc(Some(onion), None).await?; + ctrl.tor.gc(Some(onion)).await?; } } } @@ -662,7 +777,7 @@ impl NetService { } fn new(data: NetServiceData) -> Result { - let mut ip_info = data.net_controller()?.net_iface.subscribe(); + let mut ip_info = data.net_controller()?.net_iface.watcher.subscribe(); let data = Arc::new(Mutex::new(data)); let thread_data = data.clone(); let sync_task = tokio::spawn(async move { diff --git a/core/startos/src/net/service_interface.rs b/core/startos/src/net/service_interface.rs index c75b4601c..7ffab22ed 100644 --- a/core/startos/src/net/service_interface.rs +++ b/core/startos/src/net/service_interface.rs @@ -1,8 +1,7 @@ use std::net::{Ipv4Addr, Ipv6Addr}; use imbl_value::InternedString; -use lazy_format::lazy_format; -use models::{HostId, ServiceInterfaceId}; +use models::{GatewayId, HostId, ServiceInterfaceId}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -14,7 +13,7 @@ use ts_rs::TS; pub enum HostnameInfo { Ip { #[ts(type = "string")] - network_interface_id: InternedString, + gateway_id: GatewayId, public: bool, hostname: IpHostname, }, @@ -72,9 +71,7 @@ pub enum IpHostname { }, Domain { #[ts(type = "string")] - domain: InternedString, - #[ts(type = "string | null")] - subdomain: Option, + value: InternedString, port: Option, ssl_port: Option, }, @@ -85,15 +82,7 @@ impl IpHostname { Self::Ipv4 { value, .. } => InternedString::from_display(value), Self::Ipv6 { value, .. } => InternedString::from_display(value), Self::Local { value, .. } => value.clone(), - Self::Domain { - domain, subdomain, .. - } => { - if let Some(subdomain) = subdomain { - InternedString::from_display(&lazy_format!("{subdomain}.{domain}")) - } else { - domain.clone() - } - } + Self::Domain { value, .. } => value.clone(), } } } diff --git a/core/startos/src/net/socks.rs b/core/startos/src/net/socks.rs new file mode 100644 index 000000000..b6ac3030c --- /dev/null +++ b/core/startos/src/net/socks.rs @@ -0,0 +1,176 @@ +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use std::sync::Arc; +use std::time::Duration; + +use helpers::NonDetachingJoinHandle; +use socks5_impl::protocol::{Address, Reply}; +use socks5_impl::server::auth::NoAuth; +use socks5_impl::server::{AuthAdaptor, ClientConnection, Server}; +use tokio::net::{TcpListener, TcpStream}; + +use crate::net::tor::TorController; +use crate::prelude::*; +use crate::util::actor::background::BackgroundJobQueue; +use crate::HOST_IP; + +pub const DEFAULT_SOCKS_LISTEN: SocketAddr = SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::new(HOST_IP[0], HOST_IP[1], HOST_IP[2], HOST_IP[3]), + 9050, +)); + +pub struct SocksController { + _thread: NonDetachingJoinHandle<()>, +} +impl SocksController { + pub fn new(listen: SocketAddr, tor: TorController) -> Result { + Ok(Self { + _thread: tokio::spawn(async move { + let auth: AuthAdaptor<()> = Arc::new(NoAuth); + let listener; + loop { + if let Some(l) = TcpListener::bind(listen) + .await + .with_kind(ErrorKind::Network) + .log_err() + { + listener = l; + break; + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + let (bg, mut runner) = BackgroundJobQueue::new(); + runner + .run_while(async { + let server = Server::new(listener, auth); + loop { + match server.accept().await { + Ok((stream, _)) => { + let tor = tor.clone(); + bg.add_job(async move { + if let Err(e) = async { + match stream + .authenticate() + .await + .with_kind(ErrorKind::Network)? + .0 + .wait_request() + .await + .with_kind(ErrorKind::Network)? + { + ClientConnection::Connect( + reply, + Address::DomainAddress(domain, port), + ) if domain.ends_with(".onion") => { + if let Ok(mut target) = tor + .connect_onion(&domain.parse()?, port) + .await + { + let mut sock = reply + .reply( + Reply::Succeeded, + Address::unspecified(), + ) + .await + .with_kind(ErrorKind::Network)?; + tokio::io::copy_bidirectional( + &mut sock, + &mut target, + ) + .await + .with_kind(ErrorKind::Network)?; + } else { + let mut sock = reply + .reply( + Reply::HostUnreachable, + Address::unspecified(), + ) + .await + .with_kind(ErrorKind::Network)?; + sock.shutdown() + .await + .with_kind(ErrorKind::Network)?; + } + } + ClientConnection::Connect(reply, addr) => { + if let Ok(mut target) = match addr { + Address::DomainAddress(domain, port) => { + TcpStream::connect((domain, port)).await + } + Address::SocketAddress(addr) => { + TcpStream::connect(addr).await + } + } { + let mut sock = reply + .reply( + Reply::Succeeded, + Address::unspecified(), + ) + .await + .with_kind(ErrorKind::Network)?; + tokio::io::copy_bidirectional( + &mut sock, + &mut target, + ) + .await + .with_kind(ErrorKind::Network)?; + } else { + let mut sock = reply + .reply( + Reply::HostUnreachable, + Address::unspecified(), + ) + .await + .with_kind(ErrorKind::Network)?; + sock.shutdown() + .await + .with_kind(ErrorKind::Network)?; + } + } + ClientConnection::Bind(bind, _) => { + let mut sock = bind + .reply( + Reply::CommandNotSupported, + Address::unspecified(), + ) + .await + .with_kind(ErrorKind::Network)?; + sock.shutdown() + .await + .with_kind(ErrorKind::Network)?; + } + ClientConnection::UdpAssociate(associate, _) => { + let mut sock = associate + .reply( + Reply::CommandNotSupported, + Address::unspecified(), + ) + .await + .with_kind(ErrorKind::Network)?; + sock.shutdown() + .await + .with_kind(ErrorKind::Network)?; + } + } + + Ok::<_, Error>(()) + } + .await + { + tracing::trace!("SOCKS5 Stream Error: {e}"); + tracing::trace!("{e:?}"); + } + }); + } + Err(e) => { + tracing::error!("SOCKS5 TCP Accept Error: {e}"); + tracing::debug!("{e:?}"); + } + } + } + }) + .await; + }) + .into(), + }) + } +} diff --git a/core/startos/src/net/ssl.rs b/core/startos/src/net/ssl.rs index a89853591..9b0c51f3c 100644 --- a/core/startos/src/net/ssl.rs +++ b/core/startos/src/net/ssl.rs @@ -1,4 +1,4 @@ -use std::cmp::{min, Ordering}; +use std::cmp::{Ordering, min}; use std::collections::{BTreeMap, BTreeSet}; use std::net::IpAddr; use std::path::Path; @@ -13,18 +13,18 @@ use openssl::ec::{EcGroup, EcKey}; use openssl::hash::MessageDigest; use openssl::nid::Nid; use openssl::pkey::{PKey, Private}; -use openssl::x509::{X509Builder, X509Extension, X509NameBuilder, X509}; +use openssl::x509::{X509, X509Builder, X509Extension, X509NameBuilder}; use openssl::*; use patch_db::HasModel; use serde::{Deserialize, Serialize}; use tracing::instrument; +use crate::SOURCE_DATE; use crate::account::AccountInfo; use crate::hostname::Hostname; use crate::init::check_time_is_synchronized; use crate::prelude::*; use crate::util::serde::Pem; -use crate::SOURCE_DATE; #[derive(Debug, Deserialize, Serialize, HasModel)] #[model = "Model"] diff --git a/core/startos/src/net/static_server.rs b/core/startos/src/net/static_server.rs index ab38a0d71..b055dec72 100644 --- a/core/startos/src/net/static_server.rs +++ b/core/startos/src/net/static_server.rs @@ -6,11 +6,11 @@ use std::sync::Arc; use std::time::UNIX_EPOCH; use async_compression::tokio::bufread::GzipEncoder; +use axum::Router; use axum::body::Body; use axum::extract::{self as x, Request}; use axum::response::{Redirect, Response}; use axum::routing::{any, get}; -use axum::Router; use base64::display::Base64Display; use digest::Digest; use futures::future::ready; @@ -37,12 +37,12 @@ use crate::middleware::auth::{Auth, HasValidSession}; use crate::middleware::cors::Cors; use crate::middleware::db::SyncDb; use crate::prelude::*; -use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment; use crate::rpc_continuations::{Guid, RpcContinuations}; +use crate::s9pk::S9pk; +use crate::s9pk::merkle_archive::source::FileSource; use crate::s9pk::merkle_archive::source::http::HttpSource; use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; -use crate::s9pk::merkle_archive::source::FileSource; -use crate::s9pk::S9pk; +use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment; use crate::util::io::open_file; use crate::util::net::SyncBody; use crate::util::serde::BASE64; @@ -55,10 +55,10 @@ const INTERNAL_SERVER_ERROR: &[u8] = b"Internal Server Error"; const PROXY_STRIP_HEADERS: &[&str] = &["cookie", "host", "origin", "referer", "user-agent"]; -#[cfg(all(feature = "daemon", not(feature = "test")))] +#[cfg(all(feature = "startd", not(feature = "test")))] const EMBEDDED_UIS: Dir<'_> = include_dir::include_dir!("$CARGO_MANIFEST_DIR/../../web/dist/static"); -#[cfg(not(all(feature = "daemon", not(feature = "test"))))] +#[cfg(not(all(feature = "startd", not(feature = "test"))))] const EMBEDDED_UIS: Dir<'_> = Dir::new("", &[]); #[derive(Clone)] diff --git a/core/startos/src/net/tor.rs b/core/startos/src/net/tor.rs index 6977d148b..8a82cd5e4 100644 --- a/core/startos/src/net/tor.rs +++ b/core/startos/src/net/tor.rs @@ -1,90 +1,226 @@ +use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet}; use std::net::SocketAddr; -use std::sync::atomic::AtomicBool; +use std::str::FromStr; use std::sync::{Arc, Weak}; -use std::time::Duration; +use std::time::{Duration, Instant}; +use arti_client::config::onion_service::OnionServiceConfigBuilder; +use arti_client::{DataStream, TorClient, TorClientConfig}; +use base64::Engine; use clap::Parser; use color_eyre::eyre::eyre; -use futures::future::BoxFuture; -use futures::{FutureExt, TryStreamExt}; +use futures::{FutureExt, StreamExt}; use helpers::NonDetachingJoinHandle; +use imbl_value::InternedString; use itertools::Itertools; -use lazy_static::lazy_static; -use regex::Regex; use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; -use tokio::process::Command; -use tokio::sync::{mpsc, oneshot}; -use tokio::time::Instant; -use torut::control::{AsyncEvent, AuthenticatedConn, ConnError}; -use torut::onion::{OnionAddressV3, TorSecretKeyV3}; -use tracing::instrument; +use tokio::sync::Notify; +use tor_cell::relaycell::msg::Connected; +use tor_hscrypto::pk::{HsId, HsIdKeypair}; +use tor_hsservice::status::State as ArtiOnionServiceState; +use tor_hsservice::{HsNickname, RunningOnionService}; +use tor_keymgr::config::ArtiKeystoreKind; +use tor_proto::client::stream::IncomingStreamRequest; +use tor_rtcompat::tokio::TokioRustlsRuntime; use ts_rs::TS; use crate::context::{CliContext, RpcContext}; -use crate::logs::{journalctl, LogSource, LogsParams}; use crate::prelude::*; -use crate::util::serde::{display_serializable, Base64, HandlerExtSerde, WithIoFormat}; -use crate::util::Invoke; +use crate::util::actor::background::BackgroundJobQueue; +use crate::util::future::Until; +use crate::util::io::ReadWriter; +use crate::util::serde::{ + deserialize_from_str, display_serializable, serialize_display, Base64, HandlerExtSerde, + WithIoFormat, BASE64, +}; +use crate::util::sync::{SyncMutex, SyncRwLock, Watch}; -pub const SYSTEMD_UNIT: &str = "tor@default"; -const STARTING_HEALTH_TIMEOUT: u64 = 120; // 2min +const BOOTSTRAP_PROGRESS_TIMEOUT: Duration = Duration::from_secs(300); +const HS_BOOTSTRAP_TIMEOUT: Duration = Duration::from_secs(300); +const RETRY_COOLDOWN: Duration = Duration::from_secs(15); +const HEALTH_CHECK_FAILURE_ALLOWANCE: usize = 5; +const HEALTH_CHECK_COOLDOWN: Duration = Duration::from_secs(120); -#[derive(Debug, Default, Deserialize, Serialize)] -pub struct OnionStore(BTreeMap); +#[derive(Debug, Clone, Copy)] +pub struct OnionAddress(pub HsId); +impl std::fmt::Display for OnionAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + safelog::DisplayRedacted::fmt_unredacted(&self.0, f) + } +} +impl FromStr for OnionAddress { + type Err = Error; + fn from_str(s: &str) -> Result { + Ok(Self( + if s.ends_with(".onion") { + Cow::Borrowed(s) + } else { + Cow::Owned(format!("{s}.onion")) + } + .parse::() + .with_kind(ErrorKind::Tor)?, + )) + } +} +impl Serialize for OnionAddress { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serialize_display(self, serializer) + } +} +impl<'de> Deserialize<'de> for OnionAddress { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserialize_from_str(deserializer) + } +} +impl PartialEq for OnionAddress { + fn eq(&self, other: &Self) -> bool { + self.0.as_ref() == other.0.as_ref() + } +} +impl Eq for OnionAddress {} +impl PartialOrd for OnionAddress { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.as_ref().partial_cmp(other.0.as_ref()) + } +} +impl Ord for OnionAddress { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.as_ref().cmp(other.0.as_ref()) + } +} + +pub struct TorSecretKey(pub HsIdKeypair); +impl TorSecretKey { + pub fn onion_address(&self) -> OnionAddress { + OnionAddress(HsId::from(self.0.as_ref().public().to_bytes())) + } + pub fn from_bytes(bytes: [u8; 64]) -> Result { + Ok(Self( + tor_llcrypto::pk::ed25519::ExpandedKeypair::from_secret_key_bytes(bytes) + .ok_or_else(|| { + Error::new(eyre!("invalid ed25519 expanded secret key"), ErrorKind::Tor) + })? + .into(), + )) + } + pub fn generate() -> Self { + Self( + tor_llcrypto::pk::ed25519::ExpandedKeypair::from( + &tor_llcrypto::pk::ed25519::Keypair::generate(&mut rand::rng()), + ) + .into(), + ) + } +} +impl Clone for TorSecretKey { + fn clone(&self) -> Self { + Self(HsIdKeypair::from( + tor_llcrypto::pk::ed25519::ExpandedKeypair::from_secret_key_bytes( + self.0.as_ref().to_secret_key_bytes(), + ) + .unwrap(), + )) + } +} +impl std::fmt::Display for TorSecretKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + BASE64.encode(self.0.as_ref().to_secret_key_bytes()) + ) + } +} +impl FromStr for TorSecretKey { + type Err = Error; + fn from_str(s: &str) -> Result { + Self::from_bytes(Base64::<[u8; 64]>::from_str(s)?.0) + } +} +impl Serialize for TorSecretKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serialize_display(self, serializer) + } +} +impl<'de> Deserialize<'de> for TorSecretKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserialize_from_str(deserializer) + } +} + +#[derive(Default, Deserialize, Serialize)] +pub struct OnionStore(BTreeMap); impl Map for OnionStore { - type Key = OnionAddressV3; - type Value = TorSecretKeyV3; + type Key = OnionAddress; + type Value = TorSecretKey; fn key_str(key: &Self::Key) -> Result, Error> { - Ok(key.get_address_without_dot_onion()) + Self::key_string(key) + } + fn key_string(key: &Self::Key) -> Result { + Ok(InternedString::from_display(key)) } } impl OnionStore { pub fn new() -> Self { Self::default() } - pub fn insert(&mut self, key: TorSecretKeyV3) { - self.0.insert(key.public().get_onion_address(), key); + pub fn insert(&mut self, key: TorSecretKey) { + self.0.insert(key.onion_address(), key); } } impl Model { - pub fn new_key(&mut self) -> Result { - let key = TorSecretKeyV3::generate(); - self.insert(&key.public().get_onion_address(), &key)?; + pub fn new_key(&mut self) -> Result { + let key = TorSecretKey::generate(); + self.insert(&key.onion_address(), &key)?; Ok(key) } - pub fn insert_key(&mut self, key: &TorSecretKeyV3) -> Result<(), Error> { - self.insert(&key.public().get_onion_address(), &key) + pub fn insert_key(&mut self, key: &TorSecretKey) -> Result<(), Error> { + self.insert(&key.onion_address(), &key) } - pub fn get_key(&self, address: &OnionAddressV3) -> Result { + pub fn get_key(&self, address: &OnionAddress) -> Result { self.as_idx(address) .or_not_found(lazy_format!("private key for {address}"))? .de() } } - -enum ErrorLogSeverity { - Fatal { wipe_state: bool }, - Unknown { wipe_state: bool }, +impl std::fmt::Debug for OnionStore { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + struct OnionStoreMap<'a>(&'a BTreeMap); + impl<'a> std::fmt::Debug for OnionStoreMap<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[derive(Debug)] + struct KeyFor(#[allow(unused)] OnionAddress); + let mut map = f.debug_map(); + for (k, v) in self.0 { + map.key(k); + map.value(&KeyFor(v.onion_address())); + } + map.finish() + } + } + f.debug_tuple("OnionStore") + .field(&OnionStoreMap(&self.0)) + .finish() + } } -lazy_static! { - static ref LOG_REGEXES: Vec<(Regex, ErrorLogSeverity)> = vec![( - Regex::new("This could indicate a route manipulation attack, network overload, bad local network connectivity, or a bug\\.").unwrap(), - ErrorLogSeverity::Unknown { wipe_state: true } - ),( - Regex::new("died due to an invalid selected path").unwrap(), - ErrorLogSeverity::Fatal { wipe_state: false } - ),( - Regex::new("Tor has not observed any network activity for the past").unwrap(), - ErrorLogSeverity::Unknown { wipe_state: false } - )]; - static ref PROGRESS_REGEX: Regex = Regex::new("PROGRESS=([0-9]+)").unwrap(); -} - -pub fn tor() -> ParentHandler { +pub fn tor_api() -> ParentHandler { ParentHandler::new() .subcommand( "list-services", @@ -94,13 +230,6 @@ pub fn tor() -> ParentHandler { .with_about("Display Tor V3 Onion Addresses") .with_call_remote::(), ) - .subcommand("logs", logs().with_about("Display Tor logs")) - .subcommand( - "logs", - from_fn_async(crate::logs::cli_logs::) - .no_display() - .with_about("Display Tor logs"), - ) .subcommand( "reset", from_fn_async(reset) @@ -142,7 +271,7 @@ pub fn key() -> ParentHandler { ) } -pub async fn generate_key(ctx: RpcContext) -> Result { +pub async fn generate_key(ctx: RpcContext) -> Result { ctx.db .mutate(|db| { Ok(db @@ -150,8 +279,7 @@ pub async fn generate_key(ctx: RpcContext) -> Result { .as_key_store_mut() .as_onion_mut() .new_key()? - .public() - .get_onion_address()) + .onion_address()) }) .await .result @@ -165,8 +293,8 @@ pub struct AddKeyParams { pub async fn add_key( ctx: RpcContext, AddKeyParams { key }: AddKeyParams, -) -> Result { - let key = TorSecretKeyV3::from(key.0); +) -> Result { + let key = TorSecretKey::from_bytes(key.0)?; ctx.db .mutate(|db| { db.as_private_mut() @@ -176,10 +304,10 @@ pub async fn add_key( }) .await .result?; - Ok(key.public().get_onion_address()) + Ok(key.onion_address()) } -pub async fn list_keys(ctx: RpcContext) -> Result, Error> { +pub async fn list_keys(ctx: RpcContext) -> Result, Error> { ctx.db .peek() .await @@ -195,22 +323,15 @@ pub async fn list_keys(ctx: RpcContext) -> Result, Erro pub struct ResetParams { #[arg(name = "wipe-state", short = 'w', long = "wipe-state")] wipe_state: bool, - reason: String, } -pub async fn reset( - ctx: RpcContext, - ResetParams { reason, wipe_state }: ResetParams, -) -> Result<(), Error> { - ctx.net_controller - .tor - .reset(wipe_state, Error::new(eyre!("{reason}"), ErrorKind::Tor)) - .await +pub async fn reset(ctx: RpcContext, ResetParams { wipe_state }: ResetParams) -> Result<(), Error> { + ctx.net_controller.tor.reset(wipe_state).await } pub fn display_services( params: WithIoFormat, - services: Vec, + services: BTreeMap, ) -> Result<(), Error> { use prettytable::*; @@ -219,621 +340,583 @@ pub fn display_services( } let mut table = Table::new(); - for service in services { - let row = row![&service.to_string()]; + table.add_row(row![bc => "ADDRESS", "STATE", "BINDINGS"]); + for (service, info) in services { + let row = row![ + &service.to_string(), + &format!("{:?}", info.state), + &info + .bindings + .into_iter() + .map(|(port, addr)| lazy_format!("{port} -> {addr}")) + .join("; ") + ]; table.add_row(row); } table.print_tty(false)?; Ok(()) } -pub async fn list_services(ctx: RpcContext, _: Empty) -> Result, Error> { +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum OnionServiceState { + Shutdown, + Bootstrapping, + DegradedReachable, + DegradedUnreachable, + Running, + Recovering, + Broken, +} +impl From for OnionServiceState { + fn from(value: ArtiOnionServiceState) -> Self { + match value { + ArtiOnionServiceState::Shutdown => Self::Shutdown, + ArtiOnionServiceState::Bootstrapping => Self::Bootstrapping, + ArtiOnionServiceState::DegradedReachable => Self::DegradedReachable, + ArtiOnionServiceState::DegradedUnreachable => Self::DegradedUnreachable, + ArtiOnionServiceState::Running => Self::Running, + ArtiOnionServiceState::Recovering => Self::Recovering, + ArtiOnionServiceState::Broken => Self::Broken, + _ => unreachable!(), + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OnionServiceInfo { + pub state: OnionServiceState, + pub bindings: BTreeMap, +} + +pub async fn list_services( + ctx: RpcContext, + _: Empty, +) -> Result, Error> { ctx.net_controller.tor.list_services().await } -pub fn logs() -> ParentHandler { - crate::logs::logs::(|_: &RpcContext, _| async { - Ok(LogSource::Unit(SYSTEMD_UNIT)) - }) +#[derive(Clone)] +pub struct TorController(Arc); +struct TorControllerInner { + client: Watch<(usize, TorClient)>, + _bootstrapper: NonDetachingJoinHandle<()>, + services: SyncMutex>, + reset: Arc, } - -fn event_handler(_event: AsyncEvent<'static>) -> BoxFuture<'static, Result<(), ConnError>> { - async move { Ok(()) }.boxed() -} - -pub struct TorController(TorControl); impl TorController { - pub fn new(tor_control: SocketAddr, tor_socks: SocketAddr) -> Self { - TorController(TorControl::new(tor_control, tor_socks)) - } - - pub async fn add( - &self, - key: TorSecretKeyV3, - bindings: Vec<(u16, SocketAddr)>, - ) -> Result>, Error> { - let (reply, res) = oneshot::channel(); - self.0 - .send - .send(TorCommand::AddOnion { - key, - bindings, - reply, - }) - .map_err(|_| Error::new(eyre!("TorControl died"), ErrorKind::Tor))?; - res.await - .map_err(|_| Error::new(eyre!("TorControl died"), ErrorKind::Tor)) - } - - pub async fn gc( - &self, - addr: Option, - external: Option, - ) -> Result<(), Error> { - self.0 - .send - .send(TorCommand::GC { addr, external }) - .ok() - .ok_or_else(|| Error::new(eyre!("TorControl died"), ErrorKind::Tor)) - } - - pub async fn reset(&self, wipe_state: bool, context: Error) -> Result<(), Error> { - self.0 - .send - .send(TorCommand::Reset { - wipe_state, - context, - }) - .ok() - .ok_or_else(|| Error::new(eyre!("TorControl died"), ErrorKind::Tor)) - } - - pub async fn list_services(&self) -> Result, Error> { - let (reply, res) = oneshot::channel(); - self.0 - .send - .send(TorCommand::GetInfo { - query: "onions/current".into(), - reply, - }) - .ok() - .ok_or_else(|| Error::new(eyre!("TorControl died"), ErrorKind::Tor))?; - res.await - .ok() - .ok_or_else(|| Error::new(eyre!("TorControl died"), ErrorKind::Tor))?? - .lines() - .map(|l| l.trim()) - .filter(|l| !l.is_empty()) - .map(|l| l.parse::().with_kind(ErrorKind::Tor)) - .collect() - } -} - -type AuthenticatedConnection = AuthenticatedConn< - TcpStream, - Box) -> BoxFuture<'static, Result<(), ConnError>> + Send + Sync>, ->; - -enum TorCommand { - AddOnion { - key: TorSecretKeyV3, - bindings: Vec<(u16, SocketAddr)>, - reply: oneshot::Sender>>, - }, - GC { - addr: Option, - external: Option, - }, - GetInfo { - query: String, - reply: oneshot::Sender>, - }, - Reset { - wipe_state: bool, - context: Error, - }, -} - -#[instrument(skip_all)] -async fn torctl( - tor_control: SocketAddr, - tor_socks: SocketAddr, - recv: &mut mpsc::UnboundedReceiver, - services: &mut BTreeMap< - OnionAddressV3, - ( - TorSecretKeyV3, - BTreeMap>>, - ), - >, - wipe_state: &AtomicBool, - health_timeout: &mut Duration, -) -> Result<(), Error> { - let bootstrap = async { - if Command::new("systemctl") - .arg("is-active") - .arg("--quiet") - .arg("tor") - .invoke(ErrorKind::Tor) - .await - .is_ok() - { - Command::new("systemctl") - .arg("stop") - .arg("tor") - .invoke(ErrorKind::Tor) - .await?; - for _ in 0..30 { - if TcpStream::connect(tor_control).await.is_err() { - break; - } - tokio::time::sleep(Duration::from_secs(1)).await; - } - if TcpStream::connect(tor_control).await.is_ok() { - return Err(Error::new( - eyre!("Tor is failing to shut down"), - ErrorKind::Tor, - )); - } - } - if wipe_state.load(std::sync::atomic::Ordering::SeqCst) { - tokio::fs::remove_dir_all("/var/lib/tor").await?; - wipe_state.store(false, std::sync::atomic::Ordering::SeqCst); - } - tokio::fs::create_dir_all("/var/lib/tor").await?; - Command::new("chown") - .arg("-R") - .arg("debian-tor") - .arg("/var/lib/tor") - .invoke(ErrorKind::Filesystem) - .await?; - Command::new("systemctl") - .arg("start") - .arg("tor") - .invoke(ErrorKind::Tor) - .await?; - - let logs = journalctl( - LogSource::Unit(SYSTEMD_UNIT), - Some(0), - None, - Some("0"), - false, - true, - ) - .await?; - - let mut tcp_stream = None; - for _ in 0..60 { - if let Ok(conn) = TcpStream::connect(tor_control).await { - tcp_stream = Some(conn); - break; - } - tokio::time::sleep(Duration::from_secs(1)).await; - } - let tcp_stream = tcp_stream.ok_or_else(|| { - Error::new(eyre!("Timed out waiting for tor to start"), ErrorKind::Tor) - })?; - tracing::info!("Tor is started"); - - let mut conn = torut::control::UnauthenticatedConn::new(tcp_stream); - let auth = conn - .load_protocol_info() - .await? - .make_auth_data()? - .ok_or_else(|| eyre!("Cookie Auth Not Available")) - .with_kind(crate::ErrorKind::Tor)?; - conn.authenticate(&auth).await?; - let mut connection: AuthenticatedConnection = conn.into_authenticated().await; - connection.set_async_event_handler(Some(Box::new(|event| event_handler(event)))); - - let mut bootstrapped = false; - let mut last_increment = (String::new(), Instant::now()); - for _ in 0..300 { - match connection.get_info("status/bootstrap-phase").await { - Ok(a) => { - if a.contains("TAG=done") { - bootstrapped = true; - break; - } - if let Some(p) = PROGRESS_REGEX.captures(&a) { - if let Some(p) = p.get(1) { - if p.as_str() != &*last_increment.0 { - last_increment = (p.as_str().into(), Instant::now()); + pub fn new() -> Result { + let mut config = TorClientConfig::builder(); + config + .storage() + .keystore() + .primary() + .kind(ArtiKeystoreKind::Ephemeral.into()); + let client = Watch::new(( + 0, + TorClient::with_runtime(TokioRustlsRuntime::current()?) + .config(config.build().with_kind(ErrorKind::Tor)?) + .local_resource_timeout(Duration::from_secs(0)) + .create_unbootstrapped()?, + )); + let reset = Arc::new(Notify::new()); + let bootstrapper_reset = reset.clone(); + let bootstrapper_client = client.clone(); + let bootstrapper = tokio::spawn(async move { + loop { + let (epoch, client): (usize, _) = bootstrapper_client.read(); + if let Err(e) = Until::new() + .with_async_fn(|| bootstrapper_reset.notified().map(Ok)) + .run(async { + let mut events = client.bootstrap_events(); + let bootstrap_fut = + client.bootstrap().map(|res| res.with_kind(ErrorKind::Tor)); + let failure_fut = async { + let mut prev_frac = 0_f32; + let mut prev_inst = Instant::now(); + while let Some(event) = + tokio::time::timeout(BOOTSTRAP_PROGRESS_TIMEOUT, events.next()) + .await + .with_kind(ErrorKind::Tor)? + { + if event.ready_for_traffic() { + return Ok::<_, Error>(()); + } + let frac = event.as_frac(); + if frac == prev_frac { + if prev_inst.elapsed() > BOOTSTRAP_PROGRESS_TIMEOUT { + return Err(Error::new( + eyre!( + "Bootstrap has not made progress for {}", + crate::util::serde::Duration::from( + BOOTSTRAP_PROGRESS_TIMEOUT + ) + ), + ErrorKind::Tor, + )); + } + } else { + prev_frac = frac; + prev_inst = Instant::now(); + } } - } - } - } - Err(e) => { - let e = Error::from(e); - tracing::error!("{}", e); - tracing::debug!("{:?}", e); - } - } - if last_increment.1.elapsed() > Duration::from_secs(30) { - return Err(Error::new( - eyre!("Tor stuck bootstrapping at {}%", last_increment.0), - ErrorKind::Tor, - )); - } - tokio::time::sleep(Duration::from_secs(1)).await; - } - if !bootstrapped { - return Err(Error::new( - eyre!("Timed out waiting for tor to bootstrap"), - ErrorKind::Tor, - )); - } - Ok((connection, logs)) - }; - let pre_handler = async { - while let Some(command) = recv.recv().await { - match command { - TorCommand::AddOnion { - key, - bindings, - reply, - } => { - let addr = key.public().get_onion_address(); - let mut service = if let Some((_key, service)) = services.remove(&addr) { - debug_assert_eq!(key, _key); - service - } else { - BTreeMap::new() - }; - let mut rcs = Vec::with_capacity(bindings.len()); - for (external, target) in bindings { - let mut binding = service.remove(&external).unwrap_or_default(); - let rc = if let Some(rc) = - Weak::upgrade(&binding.remove(&target).unwrap_or_default()) - { - rc - } else { - Arc::new(()) + futures::future::pending().await }; - binding.insert(target, Arc::downgrade(&rc)); - service.insert(external, binding); - rcs.push(rc); - } - services.insert(addr, (key, service)); - reply.send(rcs).unwrap_or_default(); - } - TorCommand::GetInfo { reply, .. } => { - reply - .send(Err(Error::new( - eyre!("Tor has not finished bootstrapping..."), - ErrorKind::Tor, - ))) - .unwrap_or_default(); - } - TorCommand::GC { .. } => (), - TorCommand::Reset { - wipe_state: new_wipe_state, - context, - } => { - wipe_state.fetch_or(new_wipe_state, std::sync::atomic::Ordering::SeqCst); - return Err(context); - } - } - } - Ok(()) - }; - - let (mut connection, mut logs) = tokio::select! { - res = bootstrap => res?, - res = pre_handler => return res, - }; - - let hck_key = TorSecretKeyV3::generate(); - connection - .add_onion_v3( - &hck_key, - false, - false, - false, - None, - &mut [(80, SocketAddr::from(([127, 0, 0, 1], 80)))].iter(), - ) - .await?; - - for (addr, (key, service)) in std::mem::take(services) { - let bindings = service - .iter() - .flat_map(|(ext, int)| { - int.iter() - .find(|(_, rc)| rc.strong_count() > 0) - .map(|(addr, _)| (*ext, SocketAddr::from(*addr))) - }) - .collect::>(); - if !bindings.is_empty() { - services.insert(addr, (key.clone(), service)); - connection - .add_onion_v3(&key, false, false, false, None, &mut bindings.iter()) - .await?; - } - } - - let handler = async { - while let Some(command) = recv.recv().await { - match command { - TorCommand::AddOnion { - key, - bindings, - reply, - } => { - let mut rm_res = Ok(()); - let addr = key.public().get_onion_address(); - let onion_base = addr.get_address_without_dot_onion(); - let mut service = if let Some((_key, service)) = services.remove(&addr) { - debug_assert_eq!(_key, key); - rm_res = connection.del_onion(&onion_base).await; - service - } else { - BTreeMap::new() - }; - let mut rcs = Vec::with_capacity(bindings.len()); - for (external, target) in bindings { - let mut binding = service.remove(&external).unwrap_or_default(); - let rc = if let Some(rc) = - Weak::upgrade(&binding.remove(&target).unwrap_or_default()) - { - rc + if let Err::<(), Error>(e) = tokio::select! { + res = bootstrap_fut => res, + res = failure_fut => res, + } { + tracing::error!("Tor Bootstrap Error: {e}"); + tracing::debug!("{e:?}"); } else { - Arc::new(()) - }; - binding.insert(target, Arc::downgrade(&rc)); - service.insert(external, binding); - rcs.push(rc); - } - let bindings = service - .iter() - .flat_map(|(ext, int)| { - int.iter() - .find(|(_, rc)| rc.strong_count() > 0) - .map(|(addr, _)| (*ext, SocketAddr::from(*addr))) - }) - .collect::>(); - services.insert(addr, (key.clone(), service)); - reply.send(rcs).unwrap_or_default(); - rm_res?; - connection - .add_onion_v3(&key, false, false, false, None, &mut bindings.iter()) - .await?; - } - TorCommand::GC { addr, external } => { - for addr in if addr.is_some() { - itertools::Either::Left(addr.into_iter()) - } else { - itertools::Either::Right(services.keys().cloned().collect_vec().into_iter()) - } { - if let Some((key, mut service)) = services.remove(&addr) { - let onion_base: String = addr.get_address_without_dot_onion(); - for external in if external.is_some() { - itertools::Either::Left(external.into_iter()) - } else { - itertools::Either::Right( - service.keys().copied().collect_vec().into_iter(), - ) - } { - if let Some(mut binding) = service.remove(&external) { - binding = binding - .into_iter() - .filter(|(_, rc)| rc.strong_count() > 0) - .collect(); - if !binding.is_empty() { - service.insert(external, binding); + bootstrapper_client.send_modify(|_| ()); + + for _ in 0..HEALTH_CHECK_FAILURE_ALLOWANCE { + if let Err::<(), Error>(e) = async { + loop { + let (bg, mut runner) = BackgroundJobQueue::new(); + runner + .run_while(async { + const PING_BUF_LEN: usize = 8; + let key = TorSecretKey::generate(); + let onion = key.onion_address(); + let (hs, stream) = client + .launch_onion_service_with_hsid( + OnionServiceConfigBuilder::default() + .nickname( + onion + .to_string() + .trim_end_matches(".onion") + .parse::() + .with_kind(ErrorKind::Tor)?, + ) + .build() + .with_kind(ErrorKind::Tor)?, + key.clone().0, + ) + .with_kind(ErrorKind::Tor)?; + bg.add_job(async move { + if let Err(e) = async { + let mut stream = + tor_hsservice::handle_rend_requests( + stream, + ); + while let Some(req) = stream.next().await { + let mut stream = req + .accept(Connected::new_empty()) + .await + .with_kind(ErrorKind::Tor)?; + let mut buf = [0; PING_BUF_LEN]; + stream.read_exact(&mut buf).await?; + stream.write_all(&buf).await?; + stream.flush().await?; + stream.shutdown().await?; + } + Ok::<_, Error>(()) + } + .await + { + tracing::error!("Tor Health Error: {e}"); + tracing::debug!("{e:?}"); + } + }); + + tokio::time::timeout(HS_BOOTSTRAP_TIMEOUT, async { + let mut status = hs.status_events(); + while let Some(status) = status.next().await { + if status.state().is_fully_reachable() { + return Ok(()); + } + } + Err(Error::new( + eyre!("status event stream ended"), + ErrorKind::Tor, + )) + }) + .await + .with_kind(ErrorKind::Tor)??; + + let mut stream = client + .connect((onion.to_string(), 8080)) + .await?; + let mut ping_buf = [0; PING_BUF_LEN]; + rand::fill(&mut ping_buf); + stream.write_all(&ping_buf).await?; + stream.flush().await?; + let mut ping_res = [0; PING_BUF_LEN]; + stream.read_exact(&mut ping_res).await?; + ensure_code!( + ping_buf == ping_res, + ErrorKind::Tor, + "ping buffer mismatch" + ); + stream.shutdown().await?; + + Ok::<_, Error>(()) + }) + .await?; + tokio::time::sleep(HEALTH_CHECK_COOLDOWN).await; } } - } - let rm_res = connection.del_onion(&onion_base).await; - if !service.is_empty() { - let bindings = service - .iter() - .flat_map(|(ext, int)| { - int.iter() - .find(|(_, rc)| rc.strong_count() > 0) - .map(|(addr, _)| (*ext, SocketAddr::from(*addr))) - }) - .collect::>(); - if !bindings.is_empty() { - services.insert(addr, (key.clone(), service)); + .await + { + tracing::error!("Tor Client Health Error: {e}"); + tracing::debug!("{e:?}"); } - rm_res?; - if !bindings.is_empty() { - connection - .add_onion_v3( - &key, - false, - false, - false, - None, - &mut bindings.iter(), - ) - .await?; - } - } else { - rm_res?; } + tracing::error!( + "Client failed health check {} times, recycling", + HEALTH_CHECK_FAILURE_ALLOWANCE + ); } - } - } - TorCommand::GetInfo { query, reply } => { - reply - .send(connection.get_info(&query).await.with_kind(ErrorKind::Tor)) - .unwrap_or_default(); - } - TorCommand::Reset { - wipe_state: new_wipe_state, - context, - } => { - wipe_state.fetch_or(new_wipe_state, std::sync::atomic::Ordering::SeqCst); - return Err(context); - } - } - } - Ok(()) - }; - let log_parser = async { - while let Some(log) = logs.try_next().await? { - for (regex, severity) in &*LOG_REGEXES { - if regex.is_match(&log.message) { - let (check, wipe_state) = match severity { - ErrorLogSeverity::Fatal { wipe_state } => (false, *wipe_state), - ErrorLogSeverity::Unknown { wipe_state } => (true, *wipe_state), - }; - if !check - || tokio::time::timeout( - Duration::from_secs(30), - tokio_socks::tcp::Socks5Stream::connect( - tor_socks, - (hck_key.public().get_onion_address().to_string(), 80), - ), - ) - .await - .map_err(|e| tracing::warn!("Tor is confirmed to be down: {e}")) - .and_then(|a| { - a.map_err(|e| tracing::warn!("Tor is confirmed to be down: {e}")) - }) - .is_err() - { - if wipe_state { - Command::new("systemctl") - .arg("stop") - .arg("tor") - .invoke(ErrorKind::Tor) - .await?; - tokio::fs::remove_dir_all("/var/lib/tor").await?; - } - return Err(Error::new(eyre!("{}", log.message), ErrorKind::Tor)); - } - } - } - } - Err(Error::new(eyre!("Log stream terminated"), ErrorKind::Tor)) - }; - let health_checker = async { - let mut last_success = Instant::now(); - loop { - tokio::time::sleep(Duration::from_secs(30)).await; - if tokio::time::timeout( - Duration::from_secs(30), - tokio_socks::tcp::Socks5Stream::connect( - tor_socks, - (hck_key.public().get_onion_address().to_string(), 80), - ), - ) - .await - .map_err(|e| e.to_string()) - .and_then(|e| e.map_err(|e| e.to_string())) - .is_err() - { - if last_success.elapsed() > *health_timeout { - let err = Error::new(eyre!("Tor health check failed for longer than current timeout ({health_timeout:?})"), crate::ErrorKind::Tor); - *health_timeout *= 2; - wipe_state.store(true, std::sync::atomic::Ordering::SeqCst); - return Err(err); - } - } else { - last_success = Instant::now(); - } - } - }; - tokio::select! { - res = handler => res?, - res = log_parser => res?, - res = health_checker => res?, - } - - Ok(()) -} - -struct TorControl { - _thread: NonDetachingJoinHandle<()>, - send: mpsc::UnboundedSender, -} -impl TorControl { - pub fn new(tor_control: SocketAddr, tor_socks: SocketAddr) -> Self { - let (send, mut recv) = mpsc::unbounded_channel(); - Self { - _thread: tokio::spawn(async move { - let mut services = BTreeMap::new(); - let wipe_state = AtomicBool::new(false); - let mut health_timeout = Duration::from_secs(STARTING_HEALTH_TIMEOUT); - while let Err(e) = torctl( - tor_control, - tor_socks, - &mut recv, - &mut services, - &wipe_state, - &mut health_timeout, - ) - .await + Ok(()) + }) + .await { - tracing::error!("{e}: Restarting tor"); + tracing::error!("Tor Bootstrapper Error: {e}"); tracing::debug!("{e:?}"); } - tracing::info!("TorControl is shut down.") + if let Err::<(), Error>(e) = async { + tokio::time::sleep(RETRY_COOLDOWN).await; + bootstrapper_client.send(( + epoch.wrapping_add(1), + TorClient::with_runtime(TokioRustlsRuntime::current()?) + .config(config.build().with_kind(ErrorKind::Tor)?) + .local_resource_timeout(Duration::from_secs(0)) + .create_unbootstrapped_async() + .await?, + )); + tracing::debug!("TorClient recycled"); + Ok(()) + } + .await + { + tracing::error!("Tor Client Creation Error: {e}"); + tracing::debug!("{e:?}"); + } + } + }) + .into(); + Ok(Self(Arc::new(TorControllerInner { + client, + _bootstrapper: bootstrapper, + services: SyncMutex::new(BTreeMap::new()), + reset, + }))) + } + + pub fn service(&self, key: TorSecretKey) -> Result { + self.0.services.mutate(|s| { + use std::collections::btree_map::Entry; + let addr = key.onion_address(); + match s.entry(addr) { + Entry::Occupied(e) => Ok(e.get().clone()), + Entry::Vacant(e) => Ok(e + .insert(OnionService::launch(self.0.client.clone(), key)?) + .clone()), + } + }) + } + + pub async fn gc(&self, addr: Option) -> Result<(), Error> { + if let Some(addr) = addr { + if let Some(s) = self.0.services.mutate(|s| { + let rm = if let Some(s) = s.get(&addr) { + !s.gc() + } else { + false + }; + if rm { + s.remove(&addr) + } else { + None + } + }) { + s.shutdown().await + } else { + Ok(()) + } + } else { + for s in self.0.services.mutate(|s| { + let mut rm = Vec::new(); + s.retain(|_, s| { + if s.gc() { + true + } else { + rm.push(s.clone()); + false + } + }); + rm + }) { + s.shutdown().await?; + } + Ok(()) + } + } + + pub async fn reset(&self, wipe_state: bool) -> Result<(), Error> { + self.0.reset.notify_waiters(); + Ok(()) + } + + pub async fn list_services(&self) -> Result, Error> { + Ok(self + .0 + .services + .peek(|s| s.iter().map(|(a, s)| (a.clone(), s.info())).collect())) + } + + pub async fn connect_onion( + &self, + addr: &OnionAddress, + port: u16, + ) -> Result, Error> { + if let Some(target) = self.0.services.peek(|s| { + s.get(addr).and_then(|s| { + s.0.bindings.peek(|b| { + b.get(&port).and_then(|b| { + b.iter() + .find(|(_, rc)| rc.strong_count() > 0) + .map(|(a, _)| *a) + }) + }) }) - .into(), - send, + }) { + Ok(Box::new( + TcpStream::connect(target) + .await + .with_kind(ErrorKind::Network)?, + )) + } else { + let mut client = self.0.client.clone(); + client + .wait_for(|(_, c)| c.bootstrap_status().ready_for_traffic()) + .await; + let stream = client + .read() + .1 + .connect((addr.to_string(), port)) + .await + .with_kind(ErrorKind::Tor)?; + Ok(Box::new(stream)) } } } -#[tokio::test] -#[ignore] -async fn test() { - let mut conn = torut::control::UnauthenticatedConn::new( - TcpStream::connect(SocketAddr::from(([127, 0, 0, 1], 9051))) - .await - .unwrap(), - ); - let auth = conn - .load_protocol_info() - .await - .unwrap() - .make_auth_data() - .unwrap() - .ok_or_else(|| eyre!("Cookie Auth Not Available")) - .with_kind(crate::ErrorKind::Tor) - .unwrap(); - conn.authenticate(&auth).await.unwrap(); - let mut connection: AuthenticatedConn< - TcpStream, - fn(AsyncEvent<'static>) -> BoxFuture<'static, Result<(), ConnError>>, - > = conn.into_authenticated().await; - let tor_key = torut::onion::TorSecretKeyV3::generate(); - connection.get_conf("SocksPort").await.unwrap(); - connection - .add_onion_v3( - &tor_key, - false, - false, - false, - None, - &mut [(443_u16, SocketAddr::from(([127, 0, 0, 1], 8443)))].iter(), - ) - .await - .unwrap(); - connection - .del_onion( - &tor_key - .public() - .get_onion_address() - .get_address_without_dot_onion(), - ) - .await - .unwrap(); - connection - .add_onion_v3( - &tor_key, - false, - false, - false, - None, - &mut [(8443_u16, SocketAddr::from(([127, 0, 0, 1], 8443)))].iter(), - ) - .await - .unwrap(); +#[derive(Clone)] +pub struct OnionService(Arc); +struct OnionServiceData { + service: Arc>>>, + bindings: Arc>>>>, + _thread: NonDetachingJoinHandle<()>, +} +impl OnionService { + fn launch( + mut client: Watch<(usize, TorClient)>, + key: TorSecretKey, + ) -> Result { + let service = Arc::new(SyncMutex::new(None)); + let bindings = Arc::new(SyncRwLock::new(BTreeMap::< + u16, + BTreeMap>, + >::new())); + Ok(Self(Arc::new(OnionServiceData { + service: service.clone(), + bindings: bindings.clone(), + _thread: tokio::spawn(async move { + let (bg, mut runner) = BackgroundJobQueue::new(); + runner + .run_while(async { + loop { + if let Err(e) = async { + client.wait_for(|(_,c)| c.bootstrap_status().ready_for_traffic()).await; + let epoch = client.peek(|(e, c)| { + ensure_code!(c.bootstrap_status().ready_for_traffic(), ErrorKind::Tor, "TorClient recycled"); + Ok::<_, Error>(*e) + })?; + let addr = key.onion_address(); + let (new_service, stream) = client.peek(|(_, c)| { + c.launch_onion_service_with_hsid( + OnionServiceConfigBuilder::default() + .nickname( + addr + .to_string() + .trim_end_matches(".onion") + .parse::() + .with_kind(ErrorKind::Tor)?, + ) + .build() + .with_kind(ErrorKind::Tor)?, + key.clone().0, + ) + .with_kind(ErrorKind::Tor) + })?; + let mut status_stream = new_service.status_events(); + let mut status = new_service.status(); + if status.state().is_fully_reachable() { + tracing::debug!("{addr} is fully reachable"); + } else { + tracing::debug!("{addr} is not fully reachable"); + } + bg.add_job(async move { + while let Some(new_status) = status_stream.next().await { + if status.state().is_fully_reachable() && !new_status.state().is_fully_reachable() { + tracing::debug!("{addr} is no longer fully reachable"); + } else if !status.state().is_fully_reachable() && new_status.state().is_fully_reachable() { + tracing::debug!("{addr} is now fully reachable"); + } + status = new_status; + // TODO: health daemon? + } + }); + service.replace(Some(new_service)); + let mut stream = tor_hsservice::handle_rend_requests(stream); + while let Some(req) = tokio::select! { + req = stream.next() => req, + _ = client.wait_for(|(e, _)| *e != epoch) => None + } { + bg.add_job({ + let bg = bg.clone(); + let bindings = bindings.clone(); + async move { + if let Err(e) = async { + let IncomingStreamRequest::Begin(begin) = + req.request() + else { + return req + .reject(tor_cell::relaycell::msg::End::new_with_reason( + tor_cell::relaycell::msg::EndReason::DONE, + )) + .await + .with_kind(ErrorKind::Tor); + }; + let Some(target) = bindings.peek(|b| { + b.get(&begin.port()).and_then(|a| { + a.iter() + .find(|(_, rc)| rc.strong_count() > 0) + .map(|(addr, _)| *addr) + }) + }) else { + return req + .reject(tor_cell::relaycell::msg::End::new_with_reason( + tor_cell::relaycell::msg::EndReason::DONE, + )) + .await + .with_kind(ErrorKind::Tor); + }; + bg.add_job(async move { + if let Err(e) = async { + let mut outgoing = + TcpStream::connect(target) + .await + .with_kind(ErrorKind::Network)?; + let mut incoming = req + .accept(Connected::new_empty()) + .await + .with_kind(ErrorKind::Tor)?; + if let Err(e) = + tokio::io::copy_bidirectional( + &mut outgoing, + &mut incoming, + ) + .await + { + tracing::trace!("Tor Stream Error: {e}"); + tracing::trace!("{e:?}"); + } + + Ok::<_, Error>(()) + } + .await + { + tracing::trace!("Tor Stream Error: {e}"); + tracing::trace!("{e:?}"); + } + }); + Ok::<_, Error>(()) + } + .await + { + tracing::trace!("Tor Request Error: {e}"); + tracing::trace!("{e:?}"); + } + } + }); + } + Ok::<_, Error>(()) + } + .await + { + tracing::error!("Tor Client Error: {e}"); + tracing::debug!("{e:?}"); + } + } + }) + .await + }) + .into(), + }))) + } + + pub fn proxy_all>>( + &self, + bindings: impl IntoIterator, + ) -> Rcs { + self.0.bindings.mutate(|b| { + bindings + .into_iter() + .map(|(port, target)| { + let entry = b.entry(port).or_default().entry(target).or_default(); + if let Some(rc) = entry.upgrade() { + rc + } else { + let rc = Arc::new(()); + *entry = Arc::downgrade(&rc); + rc + } + }) + .collect() + }) + } + + pub fn gc(&self) -> bool { + self.0.bindings.mutate(|b| { + b.retain(|_, targets| { + targets.retain(|_, rc| rc.strong_count() > 0); + !targets.is_empty() + }); + !b.is_empty() + }) + } + + pub async fn shutdown(self) -> Result<(), Error> { + self.0.service.replace(None); + self.0._thread.abort(); + Ok(()) + } + + pub fn state(&self) -> OnionServiceState { + self.0 + .service + .peek(|s| s.as_ref().map(|s| s.status().state().into())) + .unwrap_or(OnionServiceState::Bootstrapping) + } + + pub fn info(&self) -> OnionServiceInfo { + OnionServiceInfo { + state: self.state(), + bindings: self.0.bindings.peek(|b| { + b.iter() + .filter_map(|(port, b)| { + b.iter() + .find(|(_, rc)| rc.strong_count() > 0) + .map(|(addr, _)| (*port, *addr)) + }) + .collect() + }), + } + } } diff --git a/core/startos/src/net/tunnel.rs b/core/startos/src/net/tunnel.rs new file mode 100644 index 000000000..4ff7446fa --- /dev/null +++ b/core/startos/src/net/tunnel.rs @@ -0,0 +1,138 @@ +use clap::Parser; +use imbl_value::InternedString; +use models::GatewayId; +use patch_db::json_ptr::JsonPointer; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use serde::{Deserialize, Serialize}; +use tokio::process::Command; +use ts_rs::TS; + +use crate::context::{CliContext, RpcContext}; +use crate::db::model::public::{NetworkInterfaceInfo, NetworkInterfaceType}; +use crate::prelude::*; +use crate::util::io::{write_file_atomic, TmpDir}; +use crate::util::Invoke; + +pub fn tunnel_api() -> ParentHandler { + ParentHandler::new() + .subcommand( + "add", + from_fn_async(add_tunnel) + .with_about("Add a new tunnel") + .with_call_remote::(), + ) + .subcommand( + "remove", + from_fn_async(remove_tunnel) + .no_display() + .with_about("Remove a tunnel") + .with_call_remote::(), + ) +} + +#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] +#[ts(export)] +pub struct AddTunnelParams { + name: InternedString, + config: String, + public: bool, +} + +pub async fn add_tunnel( + ctx: RpcContext, + AddTunnelParams { + name, + config, + public, + }: AddTunnelParams, +) -> Result { + let ifaces = ctx.net_controller.net_iface.watcher.subscribe(); + let mut iface = GatewayId::from("wg0"); + if !ifaces.send_if_modified(|i| { + for id in 1..256 { + if !i.contains_key(&iface) { + i.insert( + iface.clone(), + NetworkInterfaceInfo { + name: Some(name), + public: Some(public), + secure: None, + ip_info: None, + }, + ); + return true; + } + iface = InternedString::from_display(&lazy_format!("wg{id}")).into(); + } + false + }) { + return Err(Error::new( + eyre!("too many wireguard interfaces"), + ErrorKind::InvalidRequest, + )); + } + + let mut sub = ctx + .db + .subscribe( + "/public/serverInfo/network/gateways" + .parse::() + .with_kind(ErrorKind::Database)? + .join_end(iface.as_str()) + .join_end("ipInfo"), + ) + .await; + + let tmpdir = TmpDir::new().await?; + let conf = tmpdir.join(&iface).with_extension("conf"); + write_file_atomic(&conf, &config).await?; + Command::new("nmcli") + .arg("connection") + .arg("import") + .arg("type") + .arg("wireguard") + .arg("file") + .arg(&conf) + .invoke(ErrorKind::Network) + .await?; + tmpdir.delete().await?; + + sub.recv().await; + + Ok(iface) +} + +#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] +#[ts(export)] +pub struct RemoveTunnelParams { + id: GatewayId, +} +pub async fn remove_tunnel( + ctx: RpcContext, + RemoveTunnelParams { id }: RemoveTunnelParams, +) -> Result<(), Error> { + let Some(existing) = ctx + .db + .peek() + .await + .into_public() + .into_server_info() + .into_network() + .into_gateways() + .into_idx(&id) + .and_then(|e| e.into_ip_info().transpose()) + else { + return Ok(()); + }; + + if existing.as_device_type().de()? != Some(NetworkInterfaceType::Wireguard) { + return Err(Error::new( + eyre!("network interface {id} is not a proxy"), + ErrorKind::InvalidRequest, + )); + } + + ctx.net_controller.net_iface.delete_iface(&id).await?; + + Ok(()) +} diff --git a/core/startos/src/net/vhost.rs b/core/startos/src/net/vhost.rs index 11c4e5ce8..40fb1441c 100644 --- a/core/startos/src/net/vhost.rs +++ b/core/startos/src/net/vhost.rs @@ -2,7 +2,7 @@ use std::collections::{BTreeMap, BTreeSet}; use std::net::{IpAddr, SocketAddr}; use std::sync::{Arc, Weak}; -use async_acme::acme::{Identifier, ACME_TLS_ALPN_NAME}; +use async_acme::acme::{ACME_TLS_ALPN_NAME, Identifier}; use axum::body::Body; use axum::extract::Request; use axum::response::Response; @@ -10,9 +10,11 @@ use color_eyre::eyre::eyre; use futures::FutureExt; use helpers::NonDetachingJoinHandle; use http::Uri; +use imbl::OrdMap; use imbl_value::InternedString; -use models::ResultExt; -use rpc_toolkit::{from_fn, Context, HandlerArgs, HandlerExt, ParentHandler}; +use itertools::Itertools; +use models::{GatewayId, ResultExt}; +use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn}; use serde::{Deserialize, Serialize}; use tokio::io::AsyncWriteExt; use tokio::net::TcpStream; @@ -25,21 +27,24 @@ use tokio_rustls::rustls::server::{Acceptor, ResolvesServerCert}; use tokio_rustls::rustls::sign::CertifiedKey; use tokio_rustls::rustls::{RootCertStore, ServerConfig}; use tokio_rustls::{LazyConfigAcceptor, TlsConnector}; -use tokio_stream::wrappers::WatchStream; use tokio_stream::StreamExt; +use tokio_stream::wrappers::WatchStream; use tracing::instrument; use ts_rs::TS; use crate::context::{CliContext, RpcContext}; use crate::db::model::Database; +use crate::db::model::public::NetworkInterfaceInfo; use crate::net::acme::{AcmeCertCache, AcmeProvider}; -use crate::net::network_interface::{ - Accepted, NetworkInterfaceController, NetworkInterfaceListener, +use crate::net::gateway::{ + Accepted, AnyFilter, DynInterfaceFilter, InterfaceFilter, NetworkInterfaceController, + NetworkInterfaceListener, }; use crate::net::static_server::server_error; use crate::prelude::*; +use crate::util::collections::EqSet; use crate::util::io::BackTrackingIO; -use crate::util::serde::{display_serializable, HandlerExtSerde, MaybeUtf8String}; +use crate::util::serde::{HandlerExtSerde, MaybeUtf8String, display_serializable}; use crate::util::sync::SyncMutex; pub fn vhost_api() -> ParentHandler { @@ -51,12 +56,13 @@ pub fn vhost_api() -> ParentHandler { use prettytable::*; if let Some(format) = params.format { - display_serializable(format, res); + display_serializable(format, res)?; return Ok::<_, Error>(()); } let mut table = Table::new(); - table.add_row(row![bc => "FROM", "TO", "PUBLIC", "ACME", "CONNECT SSL", "ACTIVE"]); + table + .add_row(row![bc => "FROM", "TO", "GATEWAYS", "ACME", "CONNECT SSL", "ACTIVE"]); for (external, targets) in res { for (host, targets) in targets { @@ -68,7 +74,7 @@ pub fn vhost_api() -> ParentHandler { external.0 ), target.addr, - target.public, + target.gateways.iter().join(", "), target.acme.as_ref().map(|a| a.0.as_str()).unwrap_or("NONE"), target.connect_ssl.is_ok(), idx == 0 @@ -117,12 +123,7 @@ impl VHostController { &self, hostname: Option, external: u16, - TargetInfo { - public, - acme, - addr, - connect_ssl, - }: TargetInfo, + target: TargetInfo, ) -> Result, Error> { self.servers.mutate(|writable| { let server = if let Some(server) = writable.remove(&external) { @@ -136,15 +137,7 @@ impl VHostController { self.acme_tls_alpn_cache.clone(), )? }; - let rc = server.add( - hostname, - TargetInfo { - public, - acme, - addr, - connect_ssl, - }, - ); + let rc = server.add(hostname, target); writable.insert(external, server); Ok(rc?) }) @@ -152,8 +145,9 @@ impl VHostController { pub fn dump_table( &self, - ) -> BTreeMap, BTreeMap>, BTreeSet>> + ) -> BTreeMap, BTreeMap>, EqSet>> { + let ip_info = self.interfaces.watcher.ip_info(); self.servers.peek(|s| { s.iter() .map(|(k, v)| { @@ -167,8 +161,7 @@ impl VHostController { JsonKey::new(k.clone()), v.iter() .filter(|(_, v)| v.strong_count() > 0) - .map(|(k, _)| k) - .cloned() + .map(|(k, _)| ShowTargetInfo::new(k.clone(), &ip_info)) .collect(), ) }) @@ -192,14 +185,45 @@ impl VHostController { } } -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct TargetInfo { - pub public: bool, + pub filter: DynInterfaceFilter, pub acme: Option, pub addr: SocketAddr, pub connect_ssl: Result<(), AlpnInfo>, // Ok: yes, connect using ssl, pass through alpn; Err: connect tcp, use provided strategy for alpn } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub struct ShowTargetInfo { + pub gateways: BTreeSet, + pub acme: Option, + pub addr: SocketAddr, + pub connect_ssl: Result<(), AlpnInfo>, // Ok: yes, connect using ssl, pass through alpn; Err: connect tcp, use provided strategy for alpn +} +impl ShowTargetInfo { + pub fn new( + TargetInfo { + filter, + acme, + addr, + connect_ssl, + }: TargetInfo, + ip_info: &OrdMap, + ) -> Self { + ShowTargetInfo { + gateways: ip_info + .iter() + .filter(|(id, info)| filter.filter(*id, *info)) + .map(|(k, _)| k) + .cloned() + .collect(), + acme, + addr, + connect_ssl, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] @@ -222,6 +246,21 @@ struct VHostServer { _thread: NonDetachingJoinHandle<()>, } +impl<'a> From<&'a BTreeMap, BTreeMap>>> for AnyFilter { + fn from(value: &'a BTreeMap, BTreeMap>>) -> Self { + Self( + value + .iter() + .flat_map(|(_, v)| { + v.iter() + .filter(|(_, r)| r.strong_count() > 0) + .map(|(t, _)| t.filter.clone()) + }) + .collect(), + ) + } +} + impl VHostServer { async fn accept( listener: &mut NetworkInterfaceListener, @@ -233,35 +272,35 @@ impl VHostServer { let accepted; loop { - let any_public = mapping - .borrow() - .iter() - .any(|(_, targets)| targets.iter().any(|(target, _)| target.public)); + let any_filter = AnyFilter::from(&*mapping.borrow()); - let changed_public = mapping - .wait_for(|m| { - m.iter() - .any(|(_, targets)| targets.iter().any(|(target, _)| target.public)) - != any_public - }) + let changed_filter = mapping + .wait_for(|m| any_filter != AnyFilter::from(m)) .boxed(); tokio::select! { - a = listener.accept(any_public) => { + a = listener.accept(&any_filter) => { accepted = a?; break; } - _ = changed_public => { - tracing::debug!("port {} {} public bindings", listener.port(), if any_public { "no longer has" } else { "now has" }); + _ = changed_filter => { + tracing::debug!("port {} filter changed", listener.port()); } } } + let check = listener.check_filter(); tokio::spawn(async move { let bind = accepted.bind; - if let Err(e) = - Self::handle_stream(accepted, mapping, db, acme_tls_alpn_cache, crypto_provider) - .await + if let Err(e) = Self::handle_stream( + accepted, + check, + mapping, + db, + acme_tls_alpn_cache, + crypto_provider, + ) + .await { tracing::error!("Error in VHostController on {bind}: {e}"); tracing::debug!("{e:?}") @@ -273,11 +312,11 @@ impl VHostServer { async fn handle_stream( Accepted { stream, - is_public, wan_ip, bind, .. }: Accepted, + check_filter: impl FnOnce(SocketAddr, &DynInterfaceFilter) -> bool, mapping: watch::Receiver, db: TypedPatchDb, acme_tls_alpn_cache: AcmeTlsAlpnCache, @@ -431,10 +470,8 @@ impl VHostServer { .map(|(target, _)| target.clone()) }; if let Some(target) = target { - if is_public && !target.public { - log::warn!( - "Rejecting connection from public interface to private bind: {bind} -> {target:?}" - ); + if !check_filter(bind, &target.filter) { + log::warn!("Connection from {bind} to {target:?} rejected by filter"); return Ok(()); } let peek = db.peek().await; @@ -660,7 +697,10 @@ impl VHostServer { crypto_provider: Arc, acme_tls_alpn_cache: AcmeTlsAlpnCache, ) -> Result { - let mut listener = iface_ctrl.bind(port).with_kind(crate::ErrorKind::Network)?; + let mut listener = iface_ctrl + .watcher + .bind(port) + .with_kind(crate::ErrorKind::Network)?; let (map_send, map_recv) = watch::channel(BTreeMap::new()); Ok(Self { mapping: map_send, diff --git a/core/startos/src/net/web_server.rs b/core/startos/src/net/web_server.rs index b9c5d0703..8a2a66d7d 100644 --- a/core/startos/src/net/web_server.rs +++ b/core/startos/src/net/web_server.rs @@ -1,8 +1,7 @@ use std::future::Future; use std::net::SocketAddr; use std::ops::Deref; -use std::sync::atomic::AtomicBool; -use std::sync::{Arc, RwLock}; +use std::sync::Arc; use std::task::Poll; use std::time::Duration; @@ -15,8 +14,8 @@ use tokio::net::{TcpListener, TcpStream}; use tokio::sync::oneshot; use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext}; -use crate::net::network_interface::{ - NetworkInterfaceListener, SelfContainedNetworkInterfaceListener, +use crate::net::gateway::{ + lookup_info_by_addr, NetworkInterfaceListener, SelfContainedNetworkInterfaceListener, }; use crate::net::static_server::{ diagnostic_ui_router, init_ui_router, install_ui_router, main_ui_router, redirecter, refresher, @@ -24,7 +23,7 @@ use crate::net::static_server::{ }; use crate::prelude::*; use crate::util::actor::background::BackgroundJobQueue; -use crate::util::sync::{SyncMutex, Watch}; +use crate::util::sync::{SyncRwLock, Watch}; pub struct Accepted { pub https_redirect: bool, @@ -50,10 +49,15 @@ impl Accept for Vec { } impl Accept for NetworkInterfaceListener { fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { - NetworkInterfaceListener::poll_accept(self, cx, true).map(|res| { - res.map(|a| Accepted { - https_redirect: a.is_public, - stream: a.stream, + NetworkInterfaceListener::poll_accept(self, cx, &true).map(|res| { + res.map(|a| { + let public = self + .ip_info + .peek(|i| lookup_info_by_addr(i, a.bind).map_or(true, |(_, i)| i.public())); + Accepted { + https_redirect: public, + stream: a.stream, + } }) }) } @@ -166,7 +170,7 @@ impl WebServer { let thread = NonDetachingJoinHandle::from(tokio::spawn(async move { #[derive(Clone)] struct QueueRunner { - queue: Arc>>, + queue: Arc>>, } impl hyper::rt::Executor for QueueRunner where @@ -211,7 +215,7 @@ impl WebServer { } } - let queue_cell = Arc::new(SyncMutex::new(None)); + let queue_cell = Arc::new(SyncRwLock::new(None)); let graceful = hyper_util::server::graceful::GracefulShutdown::new(); let mut server = hyper_util::server::conn::auto::Builder::new(QueueRunner { queue: queue_cell.clone(), @@ -227,27 +231,39 @@ impl WebServer { .keep_alive_interval(Duration::from_secs(60)) .keep_alive_timeout(Duration::from_secs(300)); let (queue, mut runner) = BackgroundJobQueue::new(); - queue_cell.mutate(|q| *q = Some(queue.clone())); + queue_cell.replace(Some(queue.clone())); let handler = async { loop { - if let Err(e) = async { - let accepted = acceptor.accept().await?; - queue.add_job( - graceful.watch( - server - .serve_connection_with_upgrades( - TokioIo::new(accepted.stream), - SwappableRouter(service.clone(), accepted.https_redirect), - ) - .into_owned(), - ), - ); + let mut err = None; + for _ in 0..5 { + if let Err(e) = async { + let accepted = acceptor.accept().await?; + queue.add_job( + graceful.watch( + server + .serve_connection_with_upgrades( + TokioIo::new(accepted.stream), + SwappableRouter( + service.clone(), + accepted.https_redirect, + ), + ) + .into_owned(), + ), + ); - Ok::<_, Error>(()) + Ok::<_, Error>(()) + } + .await + { + err = Some(e); + tokio::time::sleep(Duration::from_millis(100)).await; + } else { + break; + } } - .await - { + if let Some(e) = err { tracing::error!("Error accepting HTTP connection: {e}"); tracing::debug!("{e:?}"); } @@ -262,7 +278,7 @@ impl WebServer { } drop(queue); - drop(queue_cell.mutate(|q| q.take())); + drop(queue_cell.replace(None)); if !runner.is_empty() { tokio::time::timeout(Duration::from_secs(60), runner) diff --git a/core/startos/src/net/wifi.rs b/core/startos/src/net/wifi.rs index 73fa69db7..44465e501 100644 --- a/core/startos/src/net/wifi.rs +++ b/core/startos/src/net/wifi.rs @@ -3,12 +3,12 @@ use std::path::Path; use std::sync::Arc; use std::time::Duration; -use clap::builder::TypedValueParser; use clap::Parser; +use clap::builder::TypedValueParser; use isocountry::CountryCode; use lazy_static::lazy_static; use regex::Regex; -use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, Empty, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use tokio::process::Command; use tokio::sync::RwLock; @@ -16,11 +16,11 @@ use tracing::instrument; use ts_rs::TS; use crate::context::{CliContext, RpcContext}; -use crate::db::model::public::WifiInfo; use crate::db::model::Database; +use crate::db::model::public::WifiInfo; use crate::prelude::*; -use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; use crate::util::Invoke; +use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable}; use crate::{Error, ErrorKind}; type WifiManager = Arc>>; @@ -315,7 +315,12 @@ pub async fn remove(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result< let is_current_removed_and_no_hardwire = is_current_being_removed && !interface_connected(&ctx.ethernet_interface).await?; if is_current_removed_and_no_hardwire { - return Err(Error::new(color_eyre::eyre::eyre!("Forbidden: Deleting this network would make your server unreachable. Either connect to ethernet or connect to a different WiFi network to remedy this."), ErrorKind::Wifi)); + return Err(Error::new( + color_eyre::eyre::eyre!( + "Forbidden: Deleting this network would make your server unreachable. Either connect to ethernet or connect to a different WiFi network to remedy this." + ), + ErrorKind::Wifi, + )); } wpa_supplicant.remove_network(ctx.db.clone(), &ssid).await?; diff --git a/core/startos/src/notifications.rs b/core/startos/src/notifications.rs index d982f3962..f4eee6da9 100644 --- a/core/startos/src/notifications.rs +++ b/core/startos/src/notifications.rs @@ -3,13 +3,13 @@ use std::fmt; use std::str::FromStr; use chrono::{DateTime, Utc}; -use clap::builder::ValueParserFactory; use clap::Parser; +use clap::builder::ValueParserFactory; use color_eyre::eyre::eyre; use helpers::const_true; use imbl_value::InternedString; use models::{FromStrParser, PackageId}; -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; @@ -110,10 +110,6 @@ pub async fn list( }) }) .collect::, Error>>()?; - db.as_public_mut() - .as_server_info_mut() - .as_unread_notification_count_mut() - .ser(&0)?; Ok(notifs) } Some(before) => { @@ -195,22 +191,23 @@ pub async fn mark_seen( ) -> Result<(), Error> { ctx.db .mutate(|db| { - let mut diff = 0; let n = db.as_private_mut().as_notifications_mut(); for id in ids { - if !n - .as_idx_mut(&id) + n.as_idx_mut(&id) .or_not_found(lazy_format!("Notification #{id}"))? .as_seen_mut() - .replace(&true)? - { - diff += 1; + .ser(&true)?; + } + let mut unread = 0; + for (_, n) in n.as_entries()? { + if !n.as_seen().de()? { + unread += 1; } } db.as_public_mut() .as_server_info_mut() .as_unread_notification_count_mut() - .mutate(|n| Ok(*n -= diff))?; + .ser(&unread)?; Ok(()) }) .await @@ -223,22 +220,23 @@ pub async fn mark_seen_before( ) -> Result<(), Error> { ctx.db .mutate(|db| { - let mut diff = 0; let n = db.as_private_mut().as_notifications_mut(); for id in n.keys()?.range(..before) { - if !n - .as_idx_mut(&id) + n.as_idx_mut(&id) .or_not_found(lazy_format!("Notification #{id}"))? .as_seen_mut() - .replace(&true)? - { - diff += 1; + .ser(&true)?; + } + let mut unread = 0; + for (_, n) in n.as_entries()? { + if !n.as_seen().de()? { + unread += 1; } } db.as_public_mut() .as_server_info_mut() .as_unread_notification_count_mut() - .mutate(|n| Ok(*n -= diff))?; + .ser(&unread)?; Ok(()) }) .await @@ -251,21 +249,23 @@ pub async fn mark_unseen( ) -> Result<(), Error> { ctx.db .mutate(|db| { - let mut diff = 0; let n = db.as_private_mut().as_notifications_mut(); for id in ids { - if n.as_idx_mut(&id) + n.as_idx_mut(&id) .or_not_found(lazy_format!("Notification #{id}"))? .as_seen_mut() - .replace(&false)? - { - diff += 1; + .ser(&false)?; + } + let mut unread = 0; + for (_, n) in n.as_entries()? { + if !n.as_seen().de()? { + unread += 1; } } db.as_public_mut() .as_server_info_mut() .as_unread_notification_count_mut() - .mutate(|n| Ok(*n += diff))?; + .ser(&unread)?; Ok(()) }) .await diff --git a/core/startos/src/os_install/gpt.rs b/core/startos/src/os_install/gpt.rs index bb9712207..491c62431 100644 --- a/core/startos/src/os_install/gpt.rs +++ b/core/startos/src/os_install/gpt.rs @@ -1,10 +1,10 @@ use std::path::Path; -use gpt::disk::LogicalBlockSize; use gpt::GptConfig; +use gpt::disk::LogicalBlockSize; -use crate::disk::util::DiskInfo; use crate::disk::OsPartitionInfo; +use crate::disk::util::DiskInfo; use crate::os_install::partition_for; use crate::prelude::*; diff --git a/core/startos/src/os_install/mbr.rs b/core/startos/src/os_install/mbr.rs index 72319023c..6d85e29a3 100644 --- a/core/startos/src/os_install/mbr.rs +++ b/core/startos/src/os_install/mbr.rs @@ -1,10 +1,10 @@ use color_eyre::eyre::eyre; -use mbrman::{MBRPartitionEntry, CHS, MBR}; +use mbrman::{CHS, MBR, MBRPartitionEntry}; -use crate::disk::util::DiskInfo; -use crate::disk::OsPartitionInfo; -use crate::os_install::partition_for; use crate::Error; +use crate::disk::OsPartitionInfo; +use crate::disk::util::DiskInfo; +use crate::os_install::partition_for; pub async fn partition(disk: &DiskInfo, overwrite: bool) -> Result { { diff --git a/core/startos/src/os_install/mod.rs b/core/startos/src/os_install/mod.rs index 4cd8aac2d..651838cfb 100644 --- a/core/startos/src/os_install/mod.rs +++ b/core/startos/src/os_install/mod.rs @@ -3,13 +3,15 @@ use std::path::{Path, PathBuf}; use clap::Parser; use color_eyre::eyre::eyre; use models::Error; -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use tokio::process::Command; use ts_rs::TS; +use crate::ARCH; use crate::context::config::ServerConfig; use crate::context::{CliContext, InstallContext}; +use crate::disk::OsPartitionInfo; use crate::disk::mount::filesystem::bind::Bind; use crate::disk::mount::filesystem::block_dev::BlockDev; use crate::disk::mount::filesystem::efivarfs::EfiVarFs; @@ -17,14 +19,12 @@ use crate::disk::mount::filesystem::overlayfs::OverlayFs; use crate::disk::mount::filesystem::{MountType, ReadWrite}; use crate::disk::mount::guard::{GenericMountGuard, MountGuard, TmpMountGuard}; use crate::disk::util::{DiskInfo, PartitionTable}; -use crate::disk::OsPartitionInfo; use crate::net::utils::find_eth_iface; use crate::prelude::*; use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; -use crate::util::io::{delete_file, open_file, TmpDir}; -use crate::util::serde::IoFormat; use crate::util::Invoke; -use crate::ARCH; +use crate::util::io::{TmpDir, delete_file, open_file}; +use crate::util::serde::IoFormat; mod gpt; mod mbr; diff --git a/core/startos/src/registry/admin.rs b/core/startos/src/registry/admin.rs index 7619cdebc..e28397b5a 100644 --- a/core/startos/src/registry/admin.rs +++ b/core/startos/src/registry/admin.rs @@ -3,18 +3,18 @@ use std::path::PathBuf; use clap::Parser; use itertools::Itertools; -use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; use crate::context::CliContext; use crate::prelude::*; -use crate::registry::context::RegistryContext; -use crate::registry::signer::sign::AnyVerifyingKey; -use crate::registry::signer::{ContactInfo, SignerInfo}; use crate::registry::RegistryDatabase; +use crate::registry::context::RegistryContext; +use crate::registry::signer::{ContactInfo, SignerInfo}; use crate::rpc_continuations::Guid; -use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; +use crate::sign::AnyVerifyingKey; +use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable}; pub fn admin_api() -> ParentHandler { ParentHandler::new() diff --git a/core/startos/src/registry/asset.rs b/core/startos/src/registry/asset.rs index f79dee343..fb317c153 100644 --- a/core/startos/src/registry/asset.rs +++ b/core/startos/src/registry/asset.rs @@ -11,13 +11,13 @@ use url::Url; use crate::prelude::*; use crate::progress::PhaseProgressTrackerHandle; -use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment; -use crate::registry::signer::commitment::{Commitment, Digestable}; -use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey}; use crate::registry::signer::AcceptSigners; +use crate::s9pk::S9pk; use crate::s9pk::merkle_archive::source::http::HttpSource; use crate::s9pk::merkle_archive::source::{ArchiveSource, Section}; -use crate::s9pk::S9pk; +use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment; +use crate::sign::commitment::{Commitment, Digestable}; +use crate::sign::{AnySignature, AnyVerifyingKey}; use crate::upload::UploadingFile; #[derive(Debug, Deserialize, Serialize, TS)] diff --git a/core/startos/src/registry/context.rs b/core/startos/src/registry/context.rs index 1da189c91..b97a2679f 100644 --- a/core/startos/src/registry/context.rs +++ b/core/startos/src/registry/context.rs @@ -3,26 +3,33 @@ use std::ops::Deref; use std::path::{Path, PathBuf}; use std::sync::Arc; +use chrono::Utc; use clap::Parser; use imbl_value::InternedString; use patch_db::PatchDb; use reqwest::{Client, Proxy}; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{CallRemote, Context, Empty}; +use rpc_toolkit::{CallRemote, Context, Empty, RpcRequest}; use serde::{Deserialize, Serialize}; use sqlx::PgPool; use tokio::sync::broadcast::Sender; use tracing::instrument; +use ts_rs::TS; use url::Url; -use crate::context::config::{ContextConfig, CONFIG_PATH}; +use crate::context::config::{CONFIG_PATH, ContextConfig}; use crate::context::{CliContext, RpcContext}; +use crate::middleware::signature::SignatureAuthContext; use crate::prelude::*; -use crate::registry::auth::{SignatureHeader, AUTH_SIG_HEADER}; -use crate::registry::device_info::{DeviceInfo, DEVICE_INFO_HEADER}; -use crate::registry::signer::sign::AnySigningKey; use crate::registry::RegistryDatabase; +use crate::registry::device_info::{DEVICE_INFO_HEADER, DeviceInfo}; +use crate::registry::signer::SignerInfo; use crate::rpc_continuations::RpcContinuations; +use crate::sign::AnyVerifyingKey; +use crate::util::io::append_file; + +const DEFAULT_REGISTRY_LISTEN: SocketAddr = + SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::LOCALHOST), 5959); #[derive(Debug, Clone, Default, Deserialize, Serialize, Parser)] #[serde(rename_all = "kebab-case")] @@ -31,9 +38,9 @@ pub struct RegistryConfig { #[arg(short = 'c', long = "config")] pub config: Option, #[arg(short = 'l', long = "listen")] - pub listen: Option, - #[arg(short = 'h', long = "hostname")] - pub hostname: Option, + pub registry_listen: Option, + #[arg(short = 'H', long = "hostname")] + pub registry_hostname: Vec, #[arg(short = 'p', long = "tor-proxy")] pub tor_proxy: Option, #[arg(short = 'd', long = "datadir")] @@ -45,9 +52,9 @@ impl ContextConfig for RegistryConfig { fn next(&mut self) -> Option { self.config.take() } - fn merge_with(&mut self, other: Self) { - self.listen = self.listen.take().or(other.listen); - self.hostname = self.hostname.take().or(other.hostname); + fn merge_with(&mut self, mut other: Self) { + self.registry_listen = self.registry_listen.take().or(other.registry_listen); + self.registry_hostname.append(&mut other.registry_hostname); self.tor_proxy = self.tor_proxy.take().or(other.tor_proxy); self.datadir = self.datadir.take().or(other.datadir); } @@ -63,7 +70,7 @@ impl RegistryConfig { } pub struct RegistryContextSeed { - pub hostname: InternedString, + pub hostnames: Vec, pub listen: SocketAddr, pub db: TypedPatchDb, pub datadir: PathBuf, @@ -105,20 +112,15 @@ impl RegistryContext { }, None => None, }; + if config.registry_hostname.is_empty() { + return Err(Error::new( + eyre!("missing required configuration: registry-hostname"), + ErrorKind::NotFound, + )); + } Ok(Self(Arc::new(RegistryContextSeed { - hostname: config - .hostname - .as_ref() - .ok_or_else(|| { - Error::new( - eyre!("missing required configuration: hostname"), - ErrorKind::NotFound, - ) - })? - .clone(), - listen: config - .listen - .unwrap_or(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 5959)), + hostnames: config.registry_hostname.clone(), + listen: config.registry_listen.unwrap_or(DEFAULT_REGISTRY_LISTEN), db, datadir, rpc_continuations: RpcContinuations::new(), @@ -163,64 +165,28 @@ impl CallRemote for CliContext { params: Value, _: Empty, ) -> Result { - use reqwest::header::{ACCEPT, CONTENT_LENGTH, CONTENT_TYPE}; - use reqwest::Method; - use rpc_toolkit::yajrc::{GenericRpcMethod, Id, RpcRequest}; - use rpc_toolkit::RpcResponse; - - let url = self - .registry_url - .clone() - .ok_or_else(|| Error::new(eyre!("`--registry` required"), ErrorKind::InvalidRequest))?; - method = method.strip_prefix("registry.").unwrap_or(method); - - let rpc_req = RpcRequest { - id: Some(Id::Number(0.into())), - method: GenericRpcMethod::<_, _, Value>::new(method), - params, - }; - let body = serde_json::to_vec(&rpc_req)?; - let host = url.host().or_not_found("registry hostname")?.to_string(); - let mut req = self - .client - .request(Method::POST, url) - .header(CONTENT_TYPE, "application/json") - .header(ACCEPT, "application/json") - .header(CONTENT_LENGTH, body.len()); - if let Ok(key) = self.developer_key() { - req = req.header( - AUTH_SIG_HEADER, - SignatureHeader::sign(&AnySigningKey::Ed25519(key.clone()), &body, &host)? - .to_header(), + let url = if let Some(url) = self.registry_url.clone() { + url + } else if self.registry_hostname.is_some() { + format!( + "http://{}", + self.registry_listen.unwrap_or(DEFAULT_REGISTRY_LISTEN) + ) + .parse() + .map_err(Error::from)? + } else { + return Err( + Error::new(eyre!("`--registry` required"), ErrorKind::InvalidRequest).into(), ); - } - let res = req.body(body).send().await?; + }; + method = method.strip_prefix("registry.").unwrap_or(method); + let sig_context = self + .registry_hostname + .clone() + .or(url.host().as_ref().map(InternedString::from_display)) + .or_not_found("registry hostname")?; - if !res.status().is_success() { - let status = res.status(); - let txt = res.text().await?; - let mut res = Err(Error::new( - eyre!("{}", status.canonical_reason().unwrap_or(status.as_str())), - ErrorKind::Network, - )); - if !txt.is_empty() { - res = res.with_ctx(|_| (ErrorKind::Network, txt)); - } - return res.map_err(From::from); - } - - match res - .headers() - .get(CONTENT_TYPE) - .and_then(|v| v.to_str().ok()) - { - Some("application/json") => { - serde_json::from_slice::(&*res.bytes().await?) - .with_kind(ErrorKind::Deserialization)? - .result - } - _ => Err(Error::new(eyre!("unknown content type"), ErrorKind::Network).into()), - } + crate::middleware::signature::call_remote(self, url, &sig_context, method, params).await } } @@ -231,10 +197,10 @@ impl CallRemote for RpcContext { params: Value, RegistryUrlParams { registry }: RegistryUrlParams, ) -> Result { - use reqwest::header::{ACCEPT, CONTENT_LENGTH, CONTENT_TYPE}; use reqwest::Method; - use rpc_toolkit::yajrc::{GenericRpcMethod, Id, RpcRequest}; + use reqwest::header::{ACCEPT, CONTENT_LENGTH, CONTENT_TYPE}; use rpc_toolkit::RpcResponse; + use rpc_toolkit::yajrc::{GenericRpcMethod, Id, RpcRequest}; let url = registry.join("rpc/v0")?; method = method.strip_prefix("registry.").unwrap_or(method); @@ -286,3 +252,72 @@ impl CallRemote for RpcContext { } } } + +#[derive(Deserialize)] +pub struct RegistryAuthMetadata { + #[serde(default)] + admin: bool, +} + +#[derive(Serialize, Deserialize, TS)] +pub struct AdminLogRecord { + pub timestamp: String, + pub name: String, + #[ts(type = "{ id: string | number | null; method: string; params: any }")] + pub request: RpcRequest, + pub key: AnyVerifyingKey, +} + +impl SignatureAuthContext for RegistryContext { + type Database = RegistryDatabase; + type AdditionalMetadata = RegistryAuthMetadata; + type CheckPubkeyRes = Option<(AnyVerifyingKey, SignerInfo)>; + fn db(&self) -> &TypedPatchDb { + &self.db + } + async fn sig_context( + &self, + ) -> impl IntoIterator + Send, Error>> + Send { + self.hostnames.iter().map(Ok) + } + fn check_pubkey( + db: &Model, + pubkey: Option<&AnyVerifyingKey>, + metadata: Self::AdditionalMetadata, + ) -> Result { + if metadata.admin { + if let Some(pubkey) = pubkey { + let (guid, admin) = db.as_index().as_signers().get_signer_info(pubkey)?; + if db.as_admins().de()?.contains(&guid) { + return Ok(Some((pubkey.clone(), admin))); + } + } + Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization)) + } else { + Ok(None) + } + } + async fn post_auth_hook( + &self, + check_pubkey_res: Self::CheckPubkeyRes, + request: &RpcRequest, + ) -> Result<(), Error> { + use tokio::io::AsyncWriteExt; + if let Some((pubkey, admin)) = check_pubkey_res { + let mut log = append_file(self.datadir.join("admin.log")).await?; + log.write_all( + (serde_json::to_string(&AdminLogRecord { + timestamp: Utc::now().to_rfc3339(), + name: admin.name, + request: request.clone(), + key: pubkey, + }) + .with_kind(ErrorKind::Serialization)? + + "\n") + .as_bytes(), + ) + .await?; + } + Ok(()) + } +} diff --git a/core/startos/src/registry/db.rs b/core/startos/src/registry/db.rs index 59a55d9fb..3c3da4c12 100644 --- a/core/startos/src/registry/db.rs +++ b/core/startos/src/registry/db.rs @@ -2,19 +2,19 @@ use std::path::PathBuf; use clap::Parser; use itertools::Itertools; -use patch_db::json_ptr::{JsonPointer, ROOT}; use patch_db::Dump; +use patch_db::json_ptr::{JsonPointer, ROOT}; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; use crate::context::CliContext; use crate::prelude::*; -use crate::registry::context::RegistryContext; use crate::registry::RegistryDatabase; -use crate::util::serde::{apply_expr, HandlerExtSerde}; +use crate::registry::context::RegistryContext; +use crate::util::serde::{HandlerExtSerde, apply_expr}; pub fn db_api() -> ParentHandler { ParentHandler::new() diff --git a/core/startos/src/registry/device_info.rs b/core/startos/src/registry/device_info.rs index 410e45f8f..cb3f19089 100644 --- a/core/startos/src/registry/device_info.rs +++ b/core/startos/src/registry/device_info.rs @@ -15,8 +15,8 @@ use url::Url; use crate::context::RpcContext; use crate::prelude::*; use crate::registry::context::RegistryContext; -use crate::util::lshw::{LshwDevice, LshwDisplay, LshwProcessor}; use crate::util::VersionString; +use crate::util::lshw::{LshwDevice, LshwDisplay, LshwProcessor}; use crate::version::VersionT; pub const DEVICE_INFO_HEADER: &str = "X-StartOS-Device-Info"; diff --git a/core/startos/src/registry/info.rs b/core/startos/src/registry/info.rs index 10efda2cb..2793180a1 100644 --- a/core/startos/src/registry/info.rs +++ b/core/startos/src/registry/info.rs @@ -5,7 +5,7 @@ use clap::Parser; use imbl_value::InternedString; use itertools::Itertools; use models::DataUrl; -use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -51,7 +51,6 @@ pub fn info_api() -> ParentHandler> { pub struct RegistryInfo { pub name: Option, pub icon: Option>, - #[ts(as = "BTreeMap::")] pub categories: BTreeMap, } diff --git a/core/startos/src/registry/mod.rs b/core/startos/src/registry/mod.rs index 3b865cb96..811188d4e 100644 --- a/core/startos/src/registry/mod.rs +++ b/core/startos/src/registry/mod.rs @@ -3,16 +3,16 @@ use std::collections::{BTreeMap, BTreeSet}; use axum::Router; use futures::future::ready; use models::DataUrl; -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler, Server}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, Server, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; use crate::context::CliContext; use crate::middleware::cors::Cors; +use crate::middleware::signature::SignatureAuth; use crate::net::static_server::{bad_request, not_found, server_error}; use crate::net::web_server::{Accept, WebServer}; use crate::prelude::*; -use crate::registry::auth::Auth; use crate::registry::context::RegistryContext; use crate::registry::device_info::DeviceInfoMiddleware; use crate::registry::os::index::OsIndex; @@ -23,7 +23,6 @@ use crate::util::serde::HandlerExtSerde; pub mod admin; pub mod asset; -pub mod auth; pub mod context; pub mod db; pub mod device_info; @@ -95,7 +94,7 @@ pub fn registry_router(ctx: RegistryContext) -> Router { any( Server::new(move || ready(Ok(ctx.clone())), registry_api()) .middleware(Cors::new()) - .middleware(Auth::new()) + .middleware(SignatureAuth::new()) .middleware(DeviceInfoMiddleware::new()), ) }) diff --git a/core/startos/src/registry/os/asset/add.rs b/core/startos/src/registry/os/asset/add.rs index ffea754e3..e5ca3a934 100644 --- a/core/startos/src/registry/os/asset/add.rs +++ b/core/startos/src/registry/os/asset/add.rs @@ -7,7 +7,7 @@ use clap::Parser; use exver::Version; use imbl_value::InternedString; use itertools::Itertools; -use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; use url::Url; @@ -17,15 +17,15 @@ use crate::prelude::*; use crate::progress::{FullProgressTracker, ProgressUnits}; use crate::registry::asset::RegistryAsset; use crate::registry::context::RegistryContext; -use crate::registry::os::index::OsVersionInfo; use crate::registry::os::SIG_CONTEXT; -use crate::registry::signer::commitment::blake3::Blake3Commitment; -use crate::registry::signer::sign::ed25519::Ed25519; -use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey, SignatureScheme}; +use crate::registry::os::index::OsVersionInfo; use crate::s9pk::merkle_archive::hash::VerifyingWriter; +use crate::s9pk::merkle_archive::source::ArchiveSource; use crate::s9pk::merkle_archive::source::http::HttpSource; use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; -use crate::s9pk::merkle_archive::source::ArchiveSource; +use crate::sign::commitment::blake3::Blake3Commitment; +use crate::sign::ed25519::Ed25519; +use crate::sign::{AnySignature, AnyVerifyingKey, SignatureScheme}; use crate::util::io::open_file; use crate::util::serde::Base64; @@ -101,10 +101,10 @@ async fn add_asset( commitment, }: AddAssetParams, accessor: impl FnOnce( - &mut Model, - ) -> &mut Model>> - + UnwindSafe - + Send, + &mut Model, + ) -> &mut Model>> + + UnwindSafe + + Send, ) -> Result<(), Error> { signer .scheme() @@ -207,7 +207,7 @@ pub async fn cli_add_asset( return Err(Error::new( eyre!("Unknown extension"), ErrorKind::InvalidRequest, - )) + )); } }; @@ -302,10 +302,10 @@ async fn remove_asset( signer, }: RemoveAssetParams, accessor: impl FnOnce( - &mut Model, - ) -> &mut Model>> - + UnwindSafe - + Send, + &mut Model, + ) -> &mut Model>> + + UnwindSafe + + Send, ) -> Result<(), Error> { ctx.db .mutate(|db| { diff --git a/core/startos/src/registry/os/asset/get.rs b/core/startos/src/registry/os/asset/get.rs index cf9baca37..d3723c894 100644 --- a/core/startos/src/registry/os/asset/get.rs +++ b/core/startos/src/registry/os/asset/get.rs @@ -5,9 +5,9 @@ use std::path::{Path, PathBuf}; use clap::Parser; use exver::Version; use helpers::AtomicFile; -use imbl_value::{json, InternedString}; +use imbl_value::{InternedString, json}; use itertools::Itertools; -use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -16,11 +16,11 @@ use crate::prelude::*; use crate::progress::{FullProgressTracker, ProgressUnits}; use crate::registry::asset::RegistryAsset; use crate::registry::context::RegistryContext; -use crate::registry::os::index::OsVersionInfo; use crate::registry::os::SIG_CONTEXT; -use crate::registry::signer::commitment::blake3::Blake3Commitment; -use crate::registry::signer::commitment::Commitment; +use crate::registry::os::index::OsVersionInfo; use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; +use crate::sign::commitment::Commitment; +use crate::sign::commitment::blake3::Blake3Commitment; use crate::util::io::open_file; pub fn get_api() -> ParentHandler { @@ -62,10 +62,10 @@ async fn get_os_asset( ctx: RegistryContext, GetOsAssetParams { version, platform }: GetOsAssetParams, accessor: impl FnOnce( - &Model, - ) -> &Model>> - + UnwindSafe - + Send, + &Model, + ) -> &Model>> + + UnwindSafe + + Send, ) -> Result, Error> { accessor( ctx.db diff --git a/core/startos/src/registry/os/asset/mod.rs b/core/startos/src/registry/os/asset/mod.rs index 9cafa14a6..39b881128 100644 --- a/core/startos/src/registry/os/asset/mod.rs +++ b/core/startos/src/registry/os/asset/mod.rs @@ -1,4 +1,4 @@ -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; pub mod add; pub mod get; diff --git a/core/startos/src/registry/os/asset/sign.rs b/core/startos/src/registry/os/asset/sign.rs index a2094741b..61501b46e 100644 --- a/core/startos/src/registry/os/asset/sign.rs +++ b/core/startos/src/registry/os/asset/sign.rs @@ -6,7 +6,7 @@ use clap::Parser; use exver::Version; use imbl_value::InternedString; use itertools::Itertools; -use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -15,13 +15,13 @@ use crate::prelude::*; use crate::progress::FullProgressTracker; use crate::registry::asset::RegistryAsset; use crate::registry::context::RegistryContext; -use crate::registry::os::index::OsVersionInfo; use crate::registry::os::SIG_CONTEXT; -use crate::registry::signer::commitment::blake3::Blake3Commitment; -use crate::registry::signer::sign::ed25519::Ed25519; -use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey, SignatureScheme}; -use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; +use crate::registry::os::index::OsVersionInfo; use crate::s9pk::merkle_archive::source::ArchiveSource; +use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; +use crate::sign::commitment::blake3::Blake3Commitment; +use crate::sign::ed25519::Ed25519; +use crate::sign::{AnySignature, AnyVerifyingKey, SignatureScheme}; use crate::util::io::open_file; use crate::util::serde::Base64; @@ -70,10 +70,10 @@ async fn sign_asset( signature, }: SignAssetParams, accessor: impl FnOnce( - &mut Model, - ) -> &mut Model>> - + UnwindSafe - + Send, + &mut Model, + ) -> &mut Model>> + + UnwindSafe + + Send, ) -> Result<(), Error> { ctx.db .mutate(|db| { @@ -165,7 +165,7 @@ pub async fn cli_sign_asset( return Err(Error::new( eyre!("Unknown extension"), ErrorKind::InvalidRequest, - )) + )); } }; diff --git a/core/startos/src/registry/os/index.rs b/core/startos/src/registry/os/index.rs index b61cb8f96..9d02bcad8 100644 --- a/core/startos/src/registry/os/index.rs +++ b/core/startos/src/registry/os/index.rs @@ -8,8 +8,8 @@ use ts_rs::TS; use crate::prelude::*; use crate::registry::asset::RegistryAsset; use crate::registry::context::RegistryContext; -use crate::registry::signer::commitment::blake3::Blake3Commitment; use crate::rpc_continuations::Guid; +use crate::sign::commitment::blake3::Blake3Commitment; #[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)] #[serde(rename_all = "camelCase")] @@ -44,11 +44,8 @@ pub struct OsVersionInfo { #[ts(type = "string")] pub source_version: VersionRange, pub authorized: BTreeSet, - #[ts(as = "BTreeMap::>")] pub iso: BTreeMap>, // platform (i.e. x86_64-nonfree) -> asset - #[ts(as = "BTreeMap::>")] pub squashfs: BTreeMap>, // platform (i.e. x86_64-nonfree) -> asset - #[ts(as = "BTreeMap::>")] pub img: BTreeMap>, // platform (i.e. raspberrypi) -> asset } diff --git a/core/startos/src/registry/os/mod.rs b/core/startos/src/registry/os/mod.rs index a1d18cb03..3c2b95864 100644 --- a/core/startos/src/registry/os/mod.rs +++ b/core/startos/src/registry/os/mod.rs @@ -1,4 +1,4 @@ -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use crate::context::CliContext; use crate::util::serde::HandlerExtSerde; diff --git a/core/startos/src/registry/os/version/mod.rs b/core/startos/src/registry/os/version/mod.rs index 263ce5073..4fb2b10e9 100644 --- a/core/startos/src/registry/os/version/mod.rs +++ b/core/startos/src/registry/os/version/mod.rs @@ -1,13 +1,12 @@ use std::collections::BTreeMap; -use chrono::Utc; +use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc}; use clap::Parser; use exver::{Version, VersionRange}; use imbl_value::InternedString; use itertools::Itertools; -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; -use sqlx::query; use ts_rs::TS; use crate::context::CliContext; @@ -15,8 +14,8 @@ use crate::prelude::*; use crate::registry::context::RegistryContext; use crate::registry::device_info::DeviceInfo; use crate::registry::os::index::OsVersionInfo; -use crate::registry::signer::sign::AnyVerifyingKey; -use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; +use crate::sign::AnyVerifyingKey; +use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable}; pub mod signer; @@ -151,6 +150,33 @@ pub struct GetOsVersionParams { pub device_info: Option, } +struct PgDateTime(DateTime); +impl sqlx::Type for PgDateTime { + fn type_info() -> ::TypeInfo { + sqlx::postgres::PgTypeInfo::with_oid(sqlx::postgres::types::Oid(1184)) + } +} +impl sqlx::Encode<'_, sqlx::Postgres> for PgDateTime { + fn encode_by_ref( + &self, + buf: &mut ::ArgumentBuffer<'_>, + ) -> Result { + fn postgres_epoch_datetime() -> NaiveDateTime { + NaiveDate::from_ymd_opt(2000, 1, 1) + .expect("expected 2000-01-01 to be a valid NaiveDate") + .and_hms_opt(0, 0, 0) + .expect("expected 2000-01-01T00:00:00 to be a valid NaiveDateTime") + } + let micros = (self.0.naive_utc() - postgres_epoch_datetime()) + .num_microseconds() + .ok_or_else(|| format!("NaiveDateTime out of range for Postgres: {:?}", self.0))?; + micros.encode(buf) + } + fn size_hint(&self) -> usize { + std::mem::size_of::() + } +} + pub async fn get_version( ctx: RegistryContext, GetOsVersionParams { @@ -166,14 +192,13 @@ pub async fn get_version( if let (Some(pool), Some(server_id), Some(arch)) = (&ctx.pool, server_id, &platform) { let created_at = Utc::now(); - query!( - "INSERT INTO user_activity (created_at, server_id, arch) VALUES ($1, $2, $3)", - created_at, - server_id, - &**arch - ) - .execute(pool) - .await?; + sqlx::query("INSERT INTO user_activity (created_at, server_id, arch) VALUES ($1, $2, $3)") + .bind(PgDateTime(created_at)) + .bind(server_id) + .bind(&**arch) + .execute(pool) + .await + .with_kind(ErrorKind::Database)?; } let target = target.unwrap_or(VersionRange::Any); ctx.db diff --git a/core/startos/src/registry/os/version/signer.rs b/core/startos/src/registry/os/version/signer.rs index d73311219..eab8215a3 100644 --- a/core/startos/src/registry/os/version/signer.rs +++ b/core/startos/src/registry/os/version/signer.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use clap::Parser; use exver::Version; -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; diff --git a/core/startos/src/registry/package/add.rs b/core/startos/src/registry/package/add.rs index e3e1a0bb6..7bd836ff9 100644 --- a/core/startos/src/registry/package/add.rs +++ b/core/startos/src/registry/package/add.rs @@ -15,13 +15,13 @@ use crate::prelude::*; use crate::progress::{FullProgressTracker, ProgressTrackerWriter, ProgressUnits}; use crate::registry::context::RegistryContext; use crate::registry::package::index::PackageVersionInfo; -use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment; -use crate::registry::signer::sign::ed25519::Ed25519; -use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey, SignatureScheme}; -use crate::s9pk::merkle_archive::source::http::HttpSource; -use crate::s9pk::merkle_archive::source::ArchiveSource; -use crate::s9pk::v2::SIG_CONTEXT; use crate::s9pk::S9pk; +use crate::s9pk::merkle_archive::source::ArchiveSource; +use crate::s9pk::merkle_archive::source::http::HttpSource; +use crate::s9pk::v2::SIG_CONTEXT; +use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment; +use crate::sign::ed25519::Ed25519; +use crate::sign::{AnySignature, AnyVerifyingKey, SignatureScheme}; use crate::util::io::TrackingIO; #[derive(Debug, Deserialize, Serialize, TS)] diff --git a/core/startos/src/registry/package/category.rs b/core/startos/src/registry/package/category.rs index 57ffb27a9..92e7d0df7 100644 --- a/core/startos/src/registry/package/category.rs +++ b/core/startos/src/registry/package/category.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use clap::Parser; use imbl_value::InternedString; use models::PackageId; -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -11,7 +11,7 @@ use crate::context::CliContext; use crate::prelude::*; use crate::registry::context::RegistryContext; use crate::registry::package::index::Category; -use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; +use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable}; pub fn category_api() -> ParentHandler { ParentHandler::new() diff --git a/core/startos/src/registry/package/get.rs b/core/startos/src/registry/package/get.rs index 32a279fde..cdaef66a8 100644 --- a/core/startos/src/registry/package/get.rs +++ b/core/startos/src/registry/package/get.rs @@ -12,8 +12,8 @@ use crate::prelude::*; use crate::registry::context::RegistryContext; use crate::registry::device_info::DeviceInfo; use crate::registry::package::index::{PackageIndex, PackageVersionInfo}; -use crate::util::serde::{display_serializable, WithIoFormat}; use crate::util::VersionString; +use crate::util::serde::{WithIoFormat, display_serializable}; #[derive( Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS, ValueEnum, diff --git a/core/startos/src/registry/package/index.rs b/core/startos/src/registry/package/index.rs index dc8df7461..22357d8ef 100644 --- a/core/startos/src/registry/package/index.rs +++ b/core/startos/src/registry/package/index.rs @@ -12,20 +12,19 @@ use crate::prelude::*; use crate::registry::asset::RegistryAsset; use crate::registry::context::RegistryContext; use crate::registry::device_info::DeviceInfo; -use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment; -use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey}; use crate::rpc_continuations::Guid; +use crate::s9pk::S9pk; use crate::s9pk::git_hash::GitHash; use crate::s9pk::manifest::{Alerts, Description, HardwareRequirements}; use crate::s9pk::merkle_archive::source::FileSource; -use crate::s9pk::S9pk; +use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment; +use crate::sign::{AnySignature, AnyVerifyingKey}; #[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)] #[serde(rename_all = "camelCase")] #[model = "Model"] #[ts(export)] pub struct PackageIndex { - #[ts(as = "BTreeMap::")] pub categories: BTreeMap, pub packages: BTreeMap, } diff --git a/core/startos/src/registry/package/mod.rs b/core/startos/src/registry/package/mod.rs index 621e040f4..db9059a7f 100644 --- a/core/startos/src/registry/package/mod.rs +++ b/core/startos/src/registry/package/mod.rs @@ -1,4 +1,4 @@ -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use crate::context::CliContext; use crate::prelude::*; diff --git a/core/startos/src/registry/package/signer.rs b/core/startos/src/registry/package/signer.rs index 393c1c48e..09ed2f1c2 100644 --- a/core/startos/src/registry/package/signer.rs +++ b/core/startos/src/registry/package/signer.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use clap::Parser; use models::PackageId; -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; diff --git a/core/startos/src/registry/signer/mod.rs b/core/startos/src/registry/signer.rs similarity index 96% rename from core/startos/src/registry/signer/mod.rs rename to core/startos/src/registry/signer.rs index 137c40f0f..f38ff81a8 100644 --- a/core/startos/src/registry/signer/mod.rs +++ b/core/startos/src/registry/signer.rs @@ -9,11 +9,8 @@ use ts_rs::TS; use url::Url; use crate::prelude::*; -use crate::registry::signer::commitment::Digestable; -use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey, SignatureScheme}; - -pub mod commitment; -pub mod sign; +use crate::sign::commitment::Digestable; +use crate::sign::{AnySignature, AnyVerifyingKey, SignatureScheme}; #[derive(Debug, Deserialize, Serialize, HasModel, TS)] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/rpc_continuations.rs b/core/startos/src/rpc_continuations.rs index 4614f8fa6..a81d21a23 100644 --- a/core/startos/src/rpc_continuations.rs +++ b/core/startos/src/rpc_continuations.rs @@ -5,8 +5,8 @@ use std::sync::Mutex as SyncMutex; use std::task::{Context, Poll}; use std::time::Duration; -use axum::extract::ws::WebSocket; use axum::extract::Request; +use axum::extract::ws::WebSocket; use axum::response::Response; use clap::builder::ValueParserFactory; use futures::future::BoxFuture; @@ -14,7 +14,7 @@ use futures::{Future, FutureExt}; use helpers::TimedResource; use imbl_value::InternedString; use models::FromStrParser; -use tokio::sync::{broadcast, Mutex as AsyncMutex}; +use tokio::sync::{Mutex as AsyncMutex, broadcast}; use ts_rs::TS; #[allow(unused_imports)] diff --git a/core/startos/src/s9pk/merkle_archive/directory_contents.rs b/core/startos/src/s9pk/merkle_archive/directory_contents.rs index b39789222..5b85ab6c9 100644 --- a/core/startos/src/s9pk/merkle_archive/directory_contents.rs +++ b/core/startos/src/s9pk/merkle_archive/directory_contents.rs @@ -4,20 +4,20 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use blake3::Hash; -use futures::future::BoxFuture; use futures::FutureExt; +use futures::future::BoxFuture; use imbl::OrdMap; use imbl_value::InternedString; use itertools::Itertools; use tokio::io::AsyncRead; +use crate::CAP_10_MiB; use crate::prelude::*; use crate::s9pk::merkle_archive::sink::Sink; use crate::s9pk::merkle_archive::source::{ArchiveSource, DynFileSource, FileSource, Section}; use crate::s9pk::merkle_archive::write_queue::WriteQueue; -use crate::s9pk::merkle_archive::{varint, Entry, EntryContents}; +use crate::s9pk::merkle_archive::{Entry, EntryContents, varint}; use crate::util::io::{ParallelBlake3Writer, TrackingIO}; -use crate::CAP_10_MiB; #[derive(Clone)] pub struct DirectoryContents { @@ -145,7 +145,12 @@ impl DirectoryContents { { dir = d; } else { - return Err(Error::new(eyre!("failed to insert entry at path {path:?}: ancestor exists and is not a directory"), ErrorKind::Pack)); + return Err(Error::new( + eyre!( + "failed to insert entry at path {path:?}: ancestor exists and is not a directory" + ), + ErrorKind::Pack, + )); } } dir.insert(file.into(), entry); diff --git a/core/startos/src/s9pk/merkle_archive/expected.rs b/core/startos/src/s9pk/merkle_archive/expected.rs index 71088f243..5e49a5d64 100644 --- a/core/startos/src/s9pk/merkle_archive/expected.rs +++ b/core/startos/src/s9pk/merkle_archive/expected.rs @@ -2,9 +2,9 @@ use std::ffi::OsStr; use std::path::Path; use crate::prelude::*; +use crate::s9pk::merkle_archive::Entry; use crate::s9pk::merkle_archive::directory_contents::DirectoryContents; use crate::s9pk::merkle_archive::source::FileSource; -use crate::s9pk::merkle_archive::Entry; /// An object for tracking the files expected to be in an s9pk pub struct Expected<'a, T> { diff --git a/core/startos/src/s9pk/merkle_archive/file_contents.rs b/core/startos/src/s9pk/merkle_archive/file_contents.rs index c34193e31..c604e92c1 100644 --- a/core/startos/src/s9pk/merkle_archive/file_contents.rs +++ b/core/startos/src/s9pk/merkle_archive/file_contents.rs @@ -1,11 +1,11 @@ use blake3::Hash; use tokio::io::AsyncRead; +use crate::CAP_10_MiB; use crate::prelude::*; use crate::s9pk::merkle_archive::sink::Sink; use crate::s9pk::merkle_archive::source::{ArchiveSource, DynFileSource, FileSource, Section}; use crate::util::io::{ParallelBlake3Writer, TrackingIO}; -use crate::CAP_10_MiB; #[derive(Debug, Clone)] pub struct FileContents(S); diff --git a/core/startos/src/s9pk/merkle_archive/hash.rs b/core/startos/src/s9pk/merkle_archive/hash.rs index c7ad470e4..55d129f85 100644 --- a/core/startos/src/s9pk/merkle_archive/hash.rs +++ b/core/startos/src/s9pk/merkle_archive/hash.rs @@ -4,9 +4,9 @@ use blake3::Hash; use tokio::io::AsyncWrite; use tokio_util::either::Either; +use crate::CAP_10_MiB; use crate::prelude::*; use crate::util::io::{ParallelBlake3Writer, TeeWriter}; -use crate::CAP_10_MiB; #[pin_project::pin_project] pub struct VerifyingWriter { diff --git a/core/startos/src/s9pk/merkle_archive/mod.rs b/core/startos/src/s9pk/merkle_archive/mod.rs index 3f30a4ce1..6a8cfe301 100644 --- a/core/startos/src/s9pk/merkle_archive/mod.rs +++ b/core/startos/src/s9pk/merkle_archive/mod.rs @@ -6,17 +6,17 @@ use imbl_value::InternedString; use sha2::{Digest, Sha512}; use tokio::io::AsyncRead; +use crate::CAP_1_MiB; use crate::prelude::*; -use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment; -use crate::registry::signer::sign::ed25519::Ed25519; -use crate::registry::signer::sign::SignatureScheme; use crate::s9pk::merkle_archive::directory_contents::DirectoryContents; use crate::s9pk::merkle_archive::file_contents::FileContents; use crate::s9pk::merkle_archive::sink::Sink; use crate::s9pk::merkle_archive::source::{ArchiveSource, DynFileSource, FileSource, Section}; use crate::s9pk::merkle_archive::write_queue::WriteQueue; +use crate::sign::SignatureScheme; +use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment; +use crate::sign::ed25519::Ed25519; use crate::util::serde::Base64; -use crate::CAP_1_MiB; pub mod directory_contents; pub mod expected; @@ -128,7 +128,9 @@ impl MerkleArchive> { } else { if max_size > CAP_1_MiB as u64 { return Err(Error::new( - eyre!("root directory max size over 1MiB, cancelling download in case of DOS attack"), + eyre!( + "root directory max size over 1MiB, cancelling download in case of DOS attack" + ), ErrorKind::InvalidSignature, )); } diff --git a/core/startos/src/s9pk/merkle_archive/source/http.rs b/core/startos/src/s9pk/merkle_archive/source/http.rs index e58208277..c5a3facfd 100644 --- a/core/startos/src/s9pk/merkle_archive/source/http.rs +++ b/core/startos/src/s9pk/merkle_archive/source/http.rs @@ -12,8 +12,8 @@ use tokio_util::io::StreamReader; use crate::prelude::*; use crate::s9pk::merkle_archive::source::ArchiveSource; -use crate::util::io::TrackingIO; use crate::util::Apply; +use crate::util::io::TrackingIO; pub struct HttpSource { url: Url, diff --git a/core/startos/src/s9pk/merkle_archive/source/mod.rs b/core/startos/src/s9pk/merkle_archive/source/mod.rs index 6550d1829..c2daed6da 100644 --- a/core/startos/src/s9pk/merkle_archive/source/mod.rs +++ b/core/startos/src/s9pk/merkle_archive/source/mod.rs @@ -12,7 +12,7 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeekExt, AsyncWrite, Take}; use crate::prelude::*; use crate::s9pk::merkle_archive::hash::VerifyingWriter; -use crate::util::io::{open_file, TmpDir}; +use crate::util::io::{TmpDir, open_file}; pub mod http; pub mod multi_cursor_file; diff --git a/core/startos/src/s9pk/merkle_archive/test.rs b/core/startos/src/s9pk/merkle_archive/test.rs index 820e5be92..7948ce63e 100644 --- a/core/startos/src/s9pk/merkle_archive/test.rs +++ b/core/startos/src/s9pk/merkle_archive/test.rs @@ -87,7 +87,7 @@ fn test(files: Vec<(PathBuf, String)>) -> Result<(), Error> { return Err(Error::new( eyre!("expected file at {path:?}"), ErrorKind::ParseS9pk, - )) + )); } } } diff --git a/core/startos/src/s9pk/mod.rs b/core/startos/src/s9pk/mod.rs index a06218d40..1eb30a95b 100644 --- a/core/startos/src/s9pk/mod.rs +++ b/core/startos/src/s9pk/mod.rs @@ -7,7 +7,7 @@ pub mod v2; use std::sync::Arc; use tokio::io::{AsyncReadExt, AsyncSeek}; -pub use v2::{manifest, S9pk}; +pub use v2::{S9pk, manifest}; use crate::prelude::*; use crate::progress::FullProgressTracker; diff --git a/core/startos/src/s9pk/rpc.rs b/core/startos/src/s9pk/rpc.rs index 98b46ac77..eced48e97 100644 --- a/core/startos/src/s9pk/rpc.rs +++ b/core/startos/src/s9pk/rpc.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use clap::Parser; use models::ImageId; -use rpc_toolkit::{from_fn_async, Empty, HandlerExt, ParentHandler}; +use rpc_toolkit::{Empty, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -11,11 +11,11 @@ use crate::context::CliContext; use crate::prelude::*; use crate::s9pk::manifest::Manifest; use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; -use crate::s9pk::v2::pack::ImageConfig; use crate::s9pk::v2::SIG_CONTEXT; -use crate::util::io::{create_file, open_file, TmpDir}; -use crate::util::serde::{apply_expr, HandlerExtSerde}; +use crate::s9pk::v2::pack::ImageConfig; use crate::util::Apply; +use crate::util::io::{TmpDir, create_file, open_file}; +use crate::util::serde::{HandlerExtSerde, apply_expr}; pub const SKIP_ENV: &[&str] = &["TERM", "container", "HOME", "HOSTNAME"]; diff --git a/core/startos/src/s9pk/v1/builder.rs b/core/startos/src/s9pk/v1/builder.rs index 199742439..3bf9bd15a 100644 --- a/core/startos/src/s9pk/v1/builder.rs +++ b/core/startos/src/s9pk/v1/builder.rs @@ -3,11 +3,11 @@ use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, SeekFrom}; use tracing::instrument; use typed_builder::TypedBuilder; +use super::SIG_CONTEXT; use super::header::{FileSection, Header}; use super::manifest::Manifest; -use super::SIG_CONTEXT; -use crate::util::io::to_cbor_async_writer; use crate::util::HashWriter; +use crate::util::io::to_cbor_async_writer; use crate::{Error, ResultExt}; #[derive(TypedBuilder)] @@ -31,15 +31,15 @@ pub struct S9pkPacker< scripts: Option, } impl< - 'a, - W: AsyncWriteExt + AsyncSeekExt + Unpin, - RLicense: AsyncReadExt + Unpin, - RInstructions: AsyncReadExt + Unpin, - RIcon: AsyncReadExt + Unpin, - RDockerImages: AsyncReadExt + Unpin, - RAssets: AsyncReadExt + Unpin, - RScripts: AsyncReadExt + Unpin, - > S9pkPacker<'a, W, RLicense, RInstructions, RIcon, RDockerImages, RAssets, RScripts> + 'a, + W: AsyncWriteExt + AsyncSeekExt + Unpin, + RLicense: AsyncReadExt + Unpin, + RInstructions: AsyncReadExt + Unpin, + RIcon: AsyncReadExt + Unpin, + RDockerImages: AsyncReadExt + Unpin, + RAssets: AsyncReadExt + Unpin, + RScripts: AsyncReadExt + Unpin, +> S9pkPacker<'a, W, RLicense, RInstructions, RIcon, RDockerImages, RAssets, RScripts> { /// BLOCKING #[instrument(skip_all)] diff --git a/core/startos/src/s9pk/v1/reader.rs b/core/startos/src/s9pk/v1/reader.rs index 5748d4d1d..c181eae89 100644 --- a/core/startos/src/s9pk/v1/reader.rs +++ b/core/startos/src/s9pk/v1/reader.rs @@ -15,12 +15,12 @@ use tokio::fs::File; use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, BufReader, ReadBuf}; use tracing::instrument; -use super::header::{FileSection, Header, TableOfContents}; use super::SIG_CONTEXT; +use super::header::{FileSection, Header, TableOfContents}; use crate::prelude::*; use crate::s9pk::v1::docker::DockerReader; -use crate::util::io::open_file; use crate::util::VersionString; +use crate::util::io::open_file; #[pin_project::pin_project] #[derive(Debug)] diff --git a/core/startos/src/s9pk/v2/compat.rs b/core/startos/src/s9pk/v2/compat.rs index 568d8ed4f..923b07bb3 100644 --- a/core/startos/src/s9pk/v2/compat.rs +++ b/core/startos/src/s9pk/v2/compat.rs @@ -8,7 +8,7 @@ use models::{ImageId, VolumeId}; use tokio::io::{AsyncRead, AsyncSeek, AsyncWriteExt}; use tokio::process::Command; -use crate::dependencies::{DepInfo, Dependencies}; +use crate::dependencies::{DepInfo, Dependencies, MetadataSrc}; use crate::prelude::*; use crate::s9pk::manifest::{DeviceFilter, Manifest}; use crate::s9pk::merkle_archive::directory_contents::DirectoryContents; @@ -225,7 +225,7 @@ impl TryFrom for Manifest { DepInfo { description: value.description, optional: !value.requirement.required(), - s9pk: None, + metadata: None, }, ) }) diff --git a/core/startos/src/s9pk/v2/manifest.rs b/core/startos/src/s9pk/v2/manifest.rs index 420c5bf9d..6dd3adf03 100644 --- a/core/startos/src/s9pk/v2/manifest.rs +++ b/core/startos/src/s9pk/v2/manifest.rs @@ -5,7 +5,7 @@ use color_eyre::eyre::eyre; use exver::{Version, VersionRange}; use imbl_value::InternedString; pub use models::PackageId; -use models::{mime, ImageId, VolumeId}; +use models::{ImageId, VolumeId, mime}; use serde::{Deserialize, Serialize}; use ts_rs::TS; use url::Url; @@ -16,8 +16,8 @@ use crate::s9pk::git_hash::GitHash; use crate::s9pk::merkle_archive::directory_contents::DirectoryContents; use crate::s9pk::merkle_archive::expected::{Expected, Filter}; use crate::s9pk::v2::pack::ImageConfig; -use crate::util::serde::Regex; use crate::util::VersionString; +use crate::util::serde::Regex; use crate::version::{Current, VersionT}; fn current_version() -> Version { @@ -153,7 +153,12 @@ impl Manifest { check_arch(&arch)?; } } else { - return Err(Error::new(eyre!("`emulateMissingAs` required for all images if no `arch` specified in `hardwareRequirements`"), ErrorKind::ParseS9pk)); + return Err(Error::new( + eyre!( + "`emulateMissingAs` required for all images if no `arch` specified in `hardwareRequirements`" + ), + ErrorKind::ParseS9pk, + )); } } Ok(expected.into_filter()) diff --git a/core/startos/src/s9pk/v2/mod.rs b/core/startos/src/s9pk/v2/mod.rs index 30a91de18..29dd543e0 100644 --- a/core/startos/src/s9pk/v2/mod.rs +++ b/core/startos/src/s9pk/v2/mod.rs @@ -3,12 +3,11 @@ use std::path::Path; use std::sync::Arc; use imbl_value::InternedString; -use models::{mime, DataUrl, PackageId}; +use models::{DataUrl, PackageId, mime}; use tokio::fs::File; use crate::dependencies::DependencyMetadata; use crate::prelude::*; -use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment; use crate::s9pk::manifest::Manifest; use crate::s9pk::merkle_archive::sink::Sink; use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; @@ -17,7 +16,8 @@ use crate::s9pk::merkle_archive::source::{ }; use crate::s9pk::merkle_archive::{Entry, MerkleArchive}; use crate::s9pk::v2::pack::{ImageSource, PackSource}; -use crate::util::io::{open_file, TmpDir}; +use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment; +use crate::util::io::{TmpDir, open_file}; use crate::util::serde::IoFormat; const MAGIC_AND_VERSION: &[u8] = &[0x3b, 0x3b, 0x02]; @@ -34,7 +34,6 @@ pub mod recipe; ├── manifest.json ├── icon. ├── LICENSE.md - ├── instructions.md ├── dependencies │ └── │ ├── metadata.json diff --git a/core/startos/src/s9pk/v2/pack.rs b/core/startos/src/s9pk/v2/pack.rs index fa9f46fc3..6e8371191 100644 --- a/core/startos/src/s9pk/v2/pack.rs +++ b/core/startos/src/s9pk/v2/pack.rs @@ -6,7 +6,7 @@ use clap::Parser; use futures::future::{ready, BoxFuture}; use futures::{FutureExt, TryStreamExt}; use imbl_value::InternedString; -use models::{ImageId, PackageId, VersionString}; +use models::{DataUrl, ImageId, PackageId, VersionString}; use serde::{Deserialize, Serialize}; use tokio::process::Command; use tokio::sync::OnceCell; @@ -15,7 +15,7 @@ use tracing::{debug, warn}; use ts_rs::TS; use crate::context::CliContext; -use crate::dependencies::DependencyMetadata; +use crate::dependencies::{DependencyMetadata, MetadataSrc}; use crate::prelude::*; use crate::rpc_continuations::Guid; use crate::s9pk::git_hash::GitHash; @@ -147,8 +147,6 @@ pub struct PackParams { pub icon: Option, #[arg(long)] pub license: Option, - #[arg(long)] - pub instructions: Option, #[arg(long, conflicts_with = "no-assets")] pub assets: Option, #[arg(long, conflicts_with = "assets")] @@ -240,12 +238,6 @@ impl PackParams { .await? } } - fn instructions(&self) -> PathBuf { - self.instructions - .as_ref() - .cloned() - .unwrap_or_else(|| self.path().join("instructions.md")) - } fn assets(&self) -> PathBuf { self.assets .as_ref() @@ -705,53 +697,77 @@ pub async fn pack(ctx: CliContext, params: PackParams) -> Result<(), Error> { let mut to_insert = Vec::new(); for (id, dependency) in &mut s9pk.as_manifest_mut().dependencies.0 { - if let Some(s9pk) = dependency.s9pk.take() { - let s9pk = match s9pk { - PathOrUrl::Path(path) => { - S9pk::deserialize(&MultiCursorFile::from(open_file(path).await?), None) - .await? - .into_dyn() - } - PathOrUrl::Url(url) => { - if url.scheme() == "http" || url.scheme() == "https" { - S9pk::deserialize( - &Arc::new(HttpSource::new(ctx.client.clone(), url).await?), - None, - ) - .await? - .into_dyn() - } else { - return Err(Error::new( - eyre!("unknown scheme: {}", url.scheme()), - ErrorKind::InvalidRequest, - )); + if let Some((title, icon)) = match dependency.metadata.take() { + Some(MetadataSrc::Metadata(metadata)) => { + let icon = match metadata.icon { + PathOrUrl::Path(path) => DataUrl::from_path(path).await?, + PathOrUrl::Url(url) => { + if url.scheme() == "http" || url.scheme() == "https" { + DataUrl::from_response(ctx.client.get(url).send().await?).await? + } else if url.scheme() == "data" { + url.as_str().parse()? + } else { + return Err(Error::new( + eyre!("unknown scheme: {}", url.scheme()), + ErrorKind::InvalidRequest, + )); + } } - } - }; + }; + Some((metadata.title, icon)) + } + Some(MetadataSrc::S9pk(Some(s9pk))) => { + let s9pk = match s9pk { + PathOrUrl::Path(path) => { + S9pk::deserialize(&MultiCursorFile::from(open_file(path).await?), None) + .await? + .into_dyn() + } + PathOrUrl::Url(url) => { + if url.scheme() == "http" || url.scheme() == "https" { + S9pk::deserialize( + &Arc::new(HttpSource::new(ctx.client.clone(), url).await?), + None, + ) + .await? + .into_dyn() + } else { + return Err(Error::new( + eyre!("unknown scheme: {}", url.scheme()), + ErrorKind::InvalidRequest, + )); + } + } + }; + Some(( + s9pk.as_manifest().title.clone(), + s9pk.icon_data_url().await?, + )) + } + Some(MetadataSrc::S9pk(None)) | None => { + warn!("no metadata specified for {id}, leaving metadata empty"); + None + } + } { let dep_path = Path::new("dependencies").join(id); to_insert.push(( dep_path.join("metadata.json"), Entry::file(TmpSource::new( tmp_dir.clone(), PackSource::Buffered( - IoFormat::Json - .to_vec(&DependencyMetadata { - title: s9pk.as_manifest().title.clone(), - })? - .into(), + IoFormat::Json.to_vec(&DependencyMetadata { title })?.into(), ), )), )); - let icon = s9pk.icon().await?; to_insert.push(( - dep_path.join(&*icon.0), + dep_path + .join("icon") + .with_extension(icon.canonical_ext().unwrap_or("ico")), Entry::file(TmpSource::new( tmp_dir.clone(), - PackSource::Buffered(icon.1.expect_file()?.to_vec(icon.1.hash()).await?.into()), + PackSource::Buffered(icon.data.into_owned().into()), )), )); - } else { - warn!("no s9pk specified for {id}, leaving metadata empty"); } } for (path, source) in to_insert { @@ -797,24 +813,23 @@ pub async fn list_ingredients(_: CliContext, params: PackParams) -> Result { warn!("failed to load manifest: {e}"); debug!("{e:?}"); - return Ok(vec![ - js_path, - params.icon().await?, - params.license().await?, - params.instructions(), - ]); + return Ok(vec![js_path, params.icon().await?, params.license().await?]); } }; - let mut ingredients = vec![ - js_path, - params.icon().await?, - params.license().await?, - params.instructions(), - ]; + let mut ingredients = vec![js_path, params.icon().await?, params.license().await?]; for (_, dependency) in manifest.dependencies.0 { - if let Some(PathOrUrl::Path(p)) = dependency.s9pk { - ingredients.push(p); + match dependency.metadata { + Some(MetadataSrc::Metadata(crate::dependencies::Metadata { + icon: PathOrUrl::Path(icon), + .. + })) => { + ingredients.push(icon); + } + Some(MetadataSrc::S9pk(Some(PathOrUrl::Path(s9pk)))) => { + ingredients.push(s9pk); + } + _ => (), } } diff --git a/core/startos/src/service/action.rs b/core/startos/src/service/action.rs index b2c11260c..ae7b17de8 100644 --- a/core/startos/src/service/action.rs +++ b/core/startos/src/service/action.rs @@ -115,7 +115,7 @@ pub fn update_tasks( } pub(super) struct RunAction { - id: ActionId, + action_id: ActionId, input: Value, } impl Handler for ServiceActor { @@ -127,7 +127,7 @@ impl Handler for ServiceActor { &mut self, id: Guid, RunAction { - id: ref action_id, + ref action_id, input, }: RunAction, jobs: &BackgroundJobQueue, @@ -145,7 +145,7 @@ impl Handler for ServiceActor { .into_idx(package_id) .or_not_found(package_id)? .into_actions() - .into_idx(&action_id) + .into_idx(action_id) .or_not_found(lazy_format!("{package_id} action {action_id}"))? .de()?; if matches!(&action.visibility, ActionVisibility::Disabled(_)) { @@ -226,14 +226,6 @@ impl Service { action_id: ActionId, input: Value, ) -> Result, Error> { - self.actor - .send( - id, - RunAction { - id: action_id, - input, - }, - ) - .await? + self.actor.send(id, RunAction { action_id, input }).await? } } diff --git a/core/startos/src/service/cli.rs b/core/startos/src/service/cli.rs index eaf1f1b95..2db07040c 100644 --- a/core/startos/src/service/cli.rs +++ b/core/startos/src/service/cli.rs @@ -5,7 +5,7 @@ use clap::Parser; use imbl_value::Value; use once_cell::sync::OnceCell; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{call_remote_socket, yajrc, CallRemote, Context, Empty}; +use rpc_toolkit::{CallRemote, Context, Empty, call_remote_socket, yajrc}; use tokio::runtime::Runtime; use crate::lxc::HOST_RPC_SERVER_SOCKET; diff --git a/core/startos/src/service/effects/action.rs b/core/startos/src/service/effects/action.rs index 6f1bef852..f4f030bd0 100644 --- a/core/startos/src/service/effects/action.rs +++ b/core/startos/src/service/effects/action.rs @@ -1,9 +1,9 @@ use std::collections::BTreeSet; use models::{ActionId, PackageId, ReplayId}; -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; -use crate::action::{display_action_result, ActionInput, ActionResult}; +use crate::action::{ActionInput, ActionResult, display_action_result}; use crate::db::model::package::{ ActionMetadata, Task, TaskCondition, TaskEntry, TaskSeverity, TaskTrigger, }; diff --git a/core/startos/src/service/effects/callbacks.rs b/core/startos/src/service/effects/callbacks.rs index 4ae45b451..8493f8a02 100644 --- a/core/startos/src/service/effects/callbacks.rs +++ b/core/startos/src/service/effects/callbacks.rs @@ -264,7 +264,6 @@ impl CallbackHandler { } } pub async fn call(mut self, args: Vector) -> Result<(), Error> { - crate::dbg!(eyre!("callback fired: {}", self.handle.is_active())); if let Some(seed) = self.seed.upgrade() { seed.persistent_container .callback(self.handle.take(), args) diff --git a/core/startos/src/service/effects/control.rs b/core/startos/src/service/effects/control.rs index a6e2eb87b..ff4ed5f27 100644 --- a/core/startos/src/service/effects/control.rs +++ b/core/startos/src/service/effects/control.rs @@ -3,9 +3,9 @@ use std::str::FromStr; use clap::builder::ValueParserFactory; use models::{FromStrParser, PackageId}; +use crate::service::RebuildParams; use crate::service::effects::prelude::*; use crate::service::rpc::CallbackId; -use crate::service::RebuildParams; use crate::status::MainStatus; pub async fn rebuild(context: EffectContext) -> Result<(), Error> { @@ -21,21 +21,15 @@ pub async fn rebuild(context: EffectContext) -> Result<(), Error> { Ok(()) } -pub async fn restart( - context: EffectContext, - ProcedureId { procedure_id }: ProcedureId, -) -> Result<(), Error> { +pub async fn restart(context: EffectContext, EventId { event_id }: EventId) -> Result<(), Error> { let context = context.deref()?; - context.restart(procedure_id, false).await?; + context.restart(event_id, false).await?; Ok(()) } -pub async fn shutdown( - context: EffectContext, - ProcedureId { procedure_id }: ProcedureId, -) -> Result<(), Error> { +pub async fn shutdown(context: EffectContext, EventId { event_id }: EventId) -> Result<(), Error> { let context = context.deref()?; - context.stop(procedure_id, false).await?; + context.stop(event_id, false).await?; Ok(()) } diff --git a/core/startos/src/service/effects/dependency.rs b/core/startos/src/service/effects/dependency.rs index 7c2377239..38feeb90f 100644 --- a/core/startos/src/service/effects/dependency.rs +++ b/core/startos/src/service/effects/dependency.rs @@ -8,6 +8,7 @@ use imbl::OrdMap; use imbl_value::InternedString; use models::{FromStrParser, HealthCheckId, PackageId, ReplayId, VersionString, VolumeId}; +use crate::DATA_DIR; use crate::db::model::package::{ CurrentDependencies, CurrentDependencyInfo, CurrentDependencyKind, ManifestPreference, TaskEntry, @@ -19,7 +20,6 @@ use crate::disk::mount::util::{is_mountpoint, unmount}; use crate::service::effects::prelude::*; use crate::status::health_check::NamedHealthCheckResult; use crate::volume::data_dir; -use crate::DATA_DIR; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts(export)] diff --git a/core/startos/src/service/effects/health.rs b/core/startos/src/service/effects/health.rs index 26524c1f5..013172a92 100644 --- a/core/startos/src/service/effects/health.rs +++ b/core/startos/src/service/effects/health.rs @@ -1,8 +1,8 @@ use models::HealthCheckId; use crate::service::effects::prelude::*; -use crate::status::health_check::NamedHealthCheckResult; use crate::status::MainStatus; +use crate::status::health_check::NamedHealthCheckResult; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(rename_all = "camelCase")] @@ -31,8 +31,7 @@ pub async fn set_health( .as_status_mut() .mutate(|main| { match main { - MainStatus::Running { ref mut health, .. } - | MainStatus::Starting { ref mut health } => { + MainStatus::Running { health, .. } | MainStatus::Starting { health } => { health.insert(id, result); } _ => (), diff --git a/core/startos/src/service/effects/mod.rs b/core/startos/src/service/effects/mod.rs index ab1092ec5..eb2a52bc9 100644 --- a/core/startos/src/service/effects/mod.rs +++ b/core/startos/src/service/effects/mod.rs @@ -1,11 +1,11 @@ use std::net::Ipv4Addr; -use rpc_toolkit::{from_fn, from_fn_async, from_fn_blocking, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn, from_fn_async, from_fn_blocking}; use crate::prelude::*; use crate::service::cli::ContainerCliContext; use crate::service::effects::context::EffectContext; -use crate::{echo, HOST_IP}; +use crate::{HOST_IP, echo}; mod action; pub mod callbacks; diff --git a/core/startos/src/service/effects/net/bind.rs b/core/startos/src/service/effects/net/bind.rs index 2db3eb2ca..732249d74 100644 --- a/core/startos/src/service/effects/net/bind.rs +++ b/core/startos/src/service/effects/net/bind.rs @@ -89,5 +89,6 @@ pub async fn get_service_port_forward( .de()? .get(&internal_port) .or_not_found(lazy_format!("binding for port {internal_port}"))? - .net) + .net + .clone()) } diff --git a/core/startos/src/service/effects/net/ssl.rs b/core/startos/src/service/effects/net/ssl.rs index 51d8d316f..be8a5d7d7 100644 --- a/core/startos/src/service/effects/net/ssl.rs +++ b/core/startos/src/service/effects/net/ssl.rs @@ -6,11 +6,11 @@ use ipnet::IpNet; use itertools::Itertools; use openssl::pkey::{PKey, Private}; +use crate::HOST_IP; use crate::service::effects::callbacks::CallbackHandler; use crate::service::effects::prelude::*; use crate::service::rpc::CallbackId; use crate::util::serde::Pem; -use crate::HOST_IP; #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, TS, PartialEq, Eq)] #[serde(rename_all = "camelCase")] @@ -59,7 +59,8 @@ pub async fn get_ssl_certificate( .de()? .iter() .map(InternedString::from_display) - .chain(m.as_domains().keys()?) + .chain(m.as_public_domains().keys()?) + .chain(m.as_private_domains().de()?) .chain( m.as_hostname_info() .de()? @@ -90,7 +91,7 @@ pub async fn get_ssl_certificate( .as_public() .as_server_info() .as_network() - .as_network_interfaces() + .as_gateways() .as_entries()? .into_iter() .flat_map(|(_, net)| net.as_ip_info().transpose_ref()) @@ -184,7 +185,8 @@ pub async fn get_ssl_key( .de()? .iter() .map(InternedString::from_display) - .chain(m.as_domains().keys()?) + .chain(m.as_public_domains().keys()?) + .chain(m.as_private_domains().de()?) .chain( m.as_hostname_info() .de()? diff --git a/core/startos/src/service/effects/prelude.rs b/core/startos/src/service/effects/prelude.rs index 2dc848c0c..74a3ce476 100644 --- a/core/startos/src/service/effects/prelude.rs +++ b/core/startos/src/service/effects/prelude.rs @@ -9,8 +9,8 @@ pub(super) use crate::service::effects::context::EffectContext; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] -pub struct ProcedureId { +pub struct EventId { #[serde(default)] #[arg(default_value_t, long)] - pub procedure_id: Guid, + pub event_id: Guid, } diff --git a/core/startos/src/service/effects/subcontainer/mod.rs b/core/startos/src/service/effects/subcontainer/mod.rs index e7b4f73cf..395f5fcd1 100644 --- a/core/startos/src/service/effects/subcontainer/mod.rs +++ b/core/startos/src/service/effects/subcontainer/mod.rs @@ -11,14 +11,14 @@ use crate::service::effects::prelude::*; use crate::service::persistent_container::Subcontainer; use crate::util::Invoke; -#[cfg(feature = "container-runtime")] +#[cfg(feature = "cli-container")] mod sync; -#[cfg(not(feature = "container-runtime"))] +#[cfg(not(feature = "cli-container"))] mod sync_dummy; pub use sync::*; -#[cfg(not(feature = "container-runtime"))] +#[cfg(not(feature = "cli-container"))] use sync_dummy as sync; #[derive(Debug, Deserialize, Serialize, Parser, TS)] @@ -61,7 +61,9 @@ pub async fn destroy_subcontainer_fs( } overlay.overlay.unmount(true).await?; } else { - tracing::warn!("Could not find a subcontainer fs to destroy; assumming that it already is destroyed and will be skipping"); + tracing::warn!( + "Could not find a subcontainer fs to destroy; assumming that it already is destroyed and will be skipping" + ); } Ok(()) } diff --git a/core/startos/src/service/effects/subcontainer/sync.rs b/core/startos/src/service/effects/subcontainer/sync.rs index d088516d5..16ffca3cd 100644 --- a/core/startos/src/service/effects/subcontainer/sync.rs +++ b/core/startos/src/service/effects/subcontainer/sync.rs @@ -1,5 +1,5 @@ use std::collections::BTreeMap; -use std::ffi::{c_int, OsStr, OsString}; +use std::ffi::{OsStr, OsString, c_int}; use std::fs::File; use std::io::{IsTerminal, Read}; use std::os::unix::process::{CommandExt, ExitStatusExt}; @@ -13,10 +13,10 @@ use signal_hook::consts::signal::*; use termion::raw::IntoRawMode; use tokio::sync::oneshot; -use crate::service::effects::prelude::*; -use crate::service::effects::ContainerCliContext; -use crate::util::io::TermSize; use crate::CAP_1_KiB; +use crate::service::effects::ContainerCliContext; +use crate::service::effects::prelude::*; +use crate::util::io::TermSize; const FWD_SIGNALS: &[c_int] = &[ SIGABRT, SIGALRM, SIGCONT, SIGHUP, SIGINT, SIGIO, SIGPIPE, SIGPROF, SIGQUIT, SIGTERM, SIGTRAP, @@ -284,7 +284,7 @@ pub fn launch( if tty { use pty_process::blocking as pty_process; let (pty, pts) = pty_process::open().with_kind(ErrorKind::Filesystem)?; - let mut cmd = pty_process::Command::new("/usr/bin/start-cli"); + let mut cmd = pty_process::Command::new("/usr/bin/start-container"); cmd = cmd.arg("subcontainer").arg("launch-init"); if let Some(env) = env { cmd = cmd.arg("--env").arg(env); @@ -339,7 +339,7 @@ pub fn launch( )) } } else { - let mut cmd = StdCommand::new("/usr/bin/start-cli"); + let mut cmd = StdCommand::new("/usr/bin/start-container"); cmd.arg("subcontainer").arg("launch-init"); if let Some(env) = env { cmd.arg("--env").arg(env); @@ -534,7 +534,7 @@ pub fn exec( if tty { use pty_process::blocking as pty_process; let (pty, pts) = pty_process::open().with_kind(ErrorKind::Filesystem)?; - let mut cmd = pty_process::Command::new("/usr/bin/start-cli"); + let mut cmd = pty_process::Command::new("/usr/bin/start-container"); cmd = cmd.arg("subcontainer").arg("exec-command"); if let Some(env) = env { cmd = cmd.arg("--env").arg(env); @@ -589,7 +589,7 @@ pub fn exec( )) } } else { - let mut cmd = StdCommand::new("/usr/bin/start-cli"); + let mut cmd = StdCommand::new("/usr/bin/start-container"); cmd.arg("subcontainer").arg("exec-command"); if let Some(env) = env { cmd.arg("--env").arg(env); diff --git a/core/startos/src/service/effects/subcontainer/sync_dummy.rs b/core/startos/src/service/effects/subcontainer/sync_dummy.rs index 285bdcbc1..f7d566394 100644 --- a/core/startos/src/service/effects/subcontainer/sync_dummy.rs +++ b/core/startos/src/service/effects/subcontainer/sync_dummy.rs @@ -1,5 +1,5 @@ -use crate::service::effects::prelude::*; use crate::service::effects::ContainerCliContext; +use crate::service::effects::prelude::*; pub fn launch(_: ContainerCliContext) -> Result<(), Error> { Err(Error::new( diff --git a/core/startos/src/service/effects/version.rs b/core/startos/src/service/effects/version.rs index 8595be3ea..185e1f629 100644 --- a/core/startos/src/service/effects/version.rs +++ b/core/startos/src/service/effects/version.rs @@ -1,9 +1,9 @@ use std::path::Path; +use crate::DATA_DIR; use crate::service::effects::prelude::*; use crate::util::io::{delete_file, maybe_read_file_to_string, write_file_atomic}; use crate::volume::PKG_VOLUME_DIR; -use crate::DATA_DIR; #[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/service/mod.rs b/core/startos/src/service/mod.rs index 722fd5240..c3804fa01 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -15,12 +15,12 @@ use futures::future::BoxFuture; use futures::stream::FusedStream; use futures::{FutureExt, SinkExt, StreamExt, TryStreamExt}; use helpers::NonDetachingJoinHandle; -use imbl_value::{json, InternedString}; +use imbl_value::{InternedString, json}; use itertools::Itertools; use models::{ActionId, HostId, ImageId, PackageId}; use nix::sys::signal::Signal; use persistent_container::{PersistentContainer, Subcontainer}; -use rpc_toolkit::{from_fn_async, CallRemoteHandler, Empty, HandlerArgs, HandlerFor}; +use rpc_toolkit::{CallRemoteHandler, Empty, HandlerArgs, HandlerFor, from_fn_async}; use serde::{Deserialize, Serialize}; use service_actor::ServiceActor; use start_stop::StartStop; @@ -47,11 +47,11 @@ use crate::service::action::update_tasks; use crate::service::rpc::{ExitParams, InitKind}; use crate::service::service_map::InstallProgressHandles; use crate::service::uninstall::cleanup; +use crate::util::Never; use crate::util::actor::concurrent::ConcurrentActor; -use crate::util::io::{create_file, delete_file, AsyncReadStream, TermSize}; +use crate::util::io::{AsyncReadStream, TermSize, create_file, delete_file}; use crate::util::net::WebSocketExt; use crate::util::serde::Pem; -use crate::util::Never; use crate::volume::data_dir; use crate::{CAP_1_KiB, DATA_DIR}; @@ -852,7 +852,9 @@ pub async fn attach( .map(format_subcontainer_pair) .join("\n"); return Err(Error::new( - eyre!("no matching subcontainers are running for {id}; some possible choices are:\n{subcontainers}"), + eyre!( + "no matching subcontainers are running for {id}; some possible choices are:\n{subcontainers}" + ), ErrorKind::NotFound, )); }; @@ -910,7 +912,7 @@ pub async fn attach( cmd.arg(&*container_id) .arg("--") - .arg("start-cli") + .arg("start-container") .arg("subcontainer") .arg("exec") .arg("--env") diff --git a/core/startos/src/service/persistent_container.rs b/core/startos/src/service/persistent_container.rs index b6a29c945..28fcb0687 100644 --- a/core/startos/src/service/persistent_container.rs +++ b/core/startos/src/service/persistent_container.rs @@ -4,16 +4,16 @@ use std::path::Path; use std::sync::{Arc, Weak}; use std::time::Duration; -use futures::future::ready; use futures::Future; +use futures::future::ready; use helpers::NonDetachingJoinHandle; -use imbl::{vector, Vector}; +use imbl::{Vector, vector}; use imbl_value::InternedString; use models::{ImageId, ProcedureName, VolumeId}; use rpc_toolkit::{Empty, Server, ShutdownHandle}; use serde::de::DeserializeOwned; use tokio::process::Command; -use tokio::sync::{oneshot, watch, Mutex, OnceCell}; +use tokio::sync::{Mutex, OnceCell, oneshot, watch}; use tracing::instrument; use crate::context::RpcContext; @@ -23,12 +23,12 @@ use crate::disk::mount::filesystem::loop_dev::LoopDev; use crate::disk::mount::filesystem::overlayfs::OverlayGuard; use crate::disk::mount::filesystem::{MountType, ReadOnly}; use crate::disk::mount::guard::{GenericMountGuard, MountGuard}; -use crate::lxc::{LxcConfig, LxcContainer, HOST_RPC_SERVER_SOCKET}; +use crate::lxc::{HOST_RPC_SERVER_SOCKET, LxcConfig, LxcContainer}; use crate::net::net_controller::NetService; use crate::prelude::*; use crate::rpc_continuations::Guid; -use crate::s9pk::merkle_archive::source::FileSource; use crate::s9pk::S9pk; +use crate::s9pk::merkle_archive::source::FileSource; use crate::service::effects::context::EffectContext; use crate::service::effects::handler; use crate::service::rpc::{ @@ -36,10 +36,10 @@ use crate::service::rpc::{ }; use crate::service::start_stop::StartStop; use crate::service::transition::{TransitionKind, TransitionState}; -use crate::service::{rpc, RunningStatus, Service}; +use crate::service::{RunningStatus, Service, rpc}; +use crate::util::Invoke; use crate::util::io::create_file; use crate::util::rpc_client::UnixRpcClient; -use crate::util::Invoke; use crate::volume::data_dir; use crate::{ARCH, DATA_DIR, PACKAGE_DATA}; @@ -105,7 +105,6 @@ pub struct PersistentContainer { pub(super) lxc_container: OnceCell, pub(super) rpc_client: UnixRpcClient, pub(super) rpc_server: watch::Sender, ShutdownHandle)>>, - // procedures: Mutex>, js_mount: MountGuard, volumes: BTreeMap, assets: Vec, diff --git a/core/startos/src/service/rpc.rs b/core/startos/src/service/rpc.rs index 33949587e..7b8f0a598 100644 --- a/core/startos/src/service/rpc.rs +++ b/core/startos/src/service/rpc.rs @@ -8,8 +8,8 @@ use exver::{ExtendedVersion, VersionRange}; use imbl::Vector; use imbl_value::{InternedString, Value}; use models::{FromStrParser, ProcedureName}; -use rpc_toolkit::yajrc::RpcMethod; use rpc_toolkit::Empty; +use rpc_toolkit::yajrc::RpcMethod; use ts_rs::TS; use crate::prelude::*; diff --git a/core/startos/src/service/service_actor.rs b/core/startos/src/service/service_actor.rs index 0019cd854..697245c5d 100644 --- a/core/startos/src/service/service_actor.rs +++ b/core/startos/src/service/service_actor.rs @@ -3,15 +3,15 @@ use std::time::Duration; use imbl::vector; -use super::start_stop::StartStop; use super::ServiceActorSeed; +use super::start_stop::StartStop; use crate::prelude::*; +use crate::service::SYNC_RETRY_COOLDOWN_SECONDS; use crate::service::persistent_container::ServiceStateKinds; use crate::service::transition::TransitionKind; -use crate::service::SYNC_RETRY_COOLDOWN_SECONDS; use crate::status::MainStatus; -use crate::util::actor::background::BackgroundJobQueue; use crate::util::actor::Actor; +use crate::util::actor::background::BackgroundJobQueue; #[derive(Clone)] pub(super) struct ServiceActor(pub(super) Arc); diff --git a/core/startos/src/service/service_map.rs b/core/startos/src/service/service_map.rs index c14aca6b4..355ef8316 100644 --- a/core/startos/src/service/service_map.rs +++ b/core/startos/src/service/service_map.rs @@ -10,30 +10,30 @@ use futures::{Future, FutureExt, StreamExt, TryFutureExt}; use helpers::NonDetachingJoinHandle; use imbl::OrdMap; use models::ErrorData; -use tokio::sync::{oneshot, OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock}; +use tokio::sync::{OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock, oneshot}; use tracing::instrument; use url::Url; +use crate::DATA_DIR; use crate::context::RpcContext; use crate::db::model::package::{ InstallingInfo, InstallingState, PackageDataEntry, PackageState, UpdatingState, }; use crate::disk::mount::guard::GenericMountGuard; use crate::install::PKG_ARCHIVE_DIR; -use crate::notifications::{notify, NotificationLevel}; +use crate::notifications::{NotificationLevel, notify}; use crate::prelude::*; use crate::progress::{FullProgressTracker, PhaseProgressTrackerHandle, ProgressTrackerWriter}; -use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment; +use crate::s9pk::S9pk; use crate::s9pk::manifest::PackageId; use crate::s9pk::merkle_archive::source::FileSource; -use crate::s9pk::S9pk; use crate::service::rpc::ExitParams; use crate::service::start_stop::StartStop; use crate::service::{LoadDisposition, Service, ServiceRef}; +use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment; use crate::status::MainStatus; use crate::util::serde::{Base32, Pem}; use crate::util::sync::SyncMutex; -use crate::DATA_DIR; pub type DownloadInstallFuture = BoxFuture<'static, Result>; pub type InstallFuture = BoxFuture<'static, Result<(), Error>>; @@ -382,7 +382,7 @@ impl ServiceMap { id: PackageId, soft: bool, force: bool, - ) -> Result> + Send, Error> { + ) -> Result> + Send + 'static, Error> { let mut guard = self.get_mut(&id).await; ctx.db .mutate(|db| { diff --git a/core/startos/src/service/transition/backup.rs b/core/startos/src/service/transition/backup.rs index 6205cdd61..f61ffa4ae 100644 --- a/core/startos/src/service/transition/backup.rs +++ b/core/startos/src/service/transition/backup.rs @@ -1,17 +1,17 @@ use std::path::PathBuf; use std::sync::Arc; -use futures::future::BoxFuture; use futures::FutureExt; +use futures::future::BoxFuture; use models::ProcedureName; use super::TempDesiredRestore; use crate::disk::mount::filesystem::ReadWrite; use crate::prelude::*; use crate::rpc_continuations::Guid; +use crate::service::ServiceActor; use crate::service::action::GetActionInput; use crate::service::transition::{TransitionKind, TransitionState}; -use crate::service::ServiceActor; use crate::util::actor::background::BackgroundJobQueue; use crate::util::actor::{ConflictBuilder, Handler}; use crate::util::future::RemoteCancellable; diff --git a/core/startos/src/service/transition/restart.rs b/core/startos/src/service/transition/restart.rs index 786c8cc8f..a271e7f44 100644 --- a/core/startos/src/service/transition/restart.rs +++ b/core/startos/src/service/transition/restart.rs @@ -1,5 +1,5 @@ -use futures::future::BoxFuture; use futures::FutureExt; +use futures::future::BoxFuture; use super::TempDesiredRestore; use crate::prelude::*; diff --git a/core/startos/src/setup.rs b/core/startos/src/setup.rs index 466fea16f..b206119b9 100644 --- a/core/startos/src/setup.rs +++ b/core/startos/src/setup.rs @@ -8,7 +8,7 @@ use const_format::formatcp; use josekit::jwk::Jwk; use patch_db::json_ptr::ROOT; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, Empty, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use tokio::io::AsyncWriteExt; use tokio::process::Command; @@ -24,23 +24,24 @@ use crate::context::rpc::InitRpcContextPhases; use crate::context::setup::SetupResult; use crate::context::{RpcContext, SetupContext}; use crate::db::model::Database; +use crate::disk::REPAIR_DISK_PATH; use crate::disk::fsck::RepairStrategy; use crate::disk::main::DEFAULT_PASSWORD; -use crate::disk::mount::filesystem::cifs::Cifs; use crate::disk::mount::filesystem::ReadWrite; +use crate::disk::mount::filesystem::cifs::Cifs; use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard}; -use crate::disk::util::{pvscan, recovery_info, DiskInfo, StartOsRecoveryInfo}; -use crate::disk::REPAIR_DISK_PATH; -use crate::init::{init, InitPhases, InitResult}; +use crate::disk::util::{DiskInfo, StartOsRecoveryInfo, pvscan, recovery_info}; +use crate::init::{InitPhases, InitResult, init}; use crate::net::ssl::root_ca_start_time; use crate::prelude::*; use crate::progress::{FullProgress, PhaseProgressTrackerHandle, ProgressUnits}; use crate::rpc_continuations::Guid; +use crate::shutdown::Shutdown; use crate::system::sync_kiosk; -use crate::util::crypto::EncryptedWire; -use crate::util::io::{create_file, dir_copy, dir_size, Counter}; use crate::util::Invoke; -use crate::{Error, ErrorKind, ResultExt, DATA_DIR, MAIN_DATA, PACKAGE_DATA, PLATFORM}; +use crate::util::crypto::EncryptedWire; +use crate::util::io::{Counter, create_file, dir_copy, dir_size}; +use crate::{DATA_DIR, Error, ErrorKind, MAIN_DATA, PACKAGE_DATA, PLATFORM, ResultExt}; pub fn setup() -> ParentHandler { ParentHandler::new() @@ -67,6 +68,7 @@ pub fn setup() -> ParentHandler { "logs", from_fn_async(crate::logs::cli_logs::).no_display(), ) + .subcommand("restart", from_fn_async(restart).no_cli()) } pub fn disk() -> ParentHandler { @@ -172,6 +174,7 @@ pub async fn attach( if disk_guid.ends_with("_UNENC") { None } else { Some(DEFAULT_PASSWORD) }, ) .await?; + let _ = setup_ctx.disk_guid.set(disk_guid.clone()); if tokio::fs::metadata(REPAIR_DISK_PATH).await.is_ok() { tokio::fs::remove_file(REPAIR_DISK_PATH) .await @@ -328,7 +331,7 @@ pub async fn execute( return Err(Error::new( color_eyre::eyre::eyre!("Couldn't decode startOsPassword"), crate::ErrorKind::Unknown, - )) + )); } }; let recovery = match recovery_source { @@ -390,9 +393,19 @@ pub async fn complete(ctx: SetupContext) -> Result { } #[instrument(skip_all)] -// #[command(rpc_only)] pub async fn exit(ctx: SetupContext) -> Result<(), Error> { - ctx.shutdown.send(()).expect("failed to shutdown"); + ctx.shutdown.send(None).expect("failed to shutdown"); + Ok(()) +} + +#[instrument(skip_all)] +pub async fn restart(ctx: SetupContext) -> Result<(), Error> { + ctx.shutdown + .send(Some(Shutdown { + disk_guid: ctx.disk_guid.get().cloned(), + restart: true, + })) + .expect("failed to shutdown"); Ok(()) } @@ -435,6 +448,7 @@ pub async fn execute_inner( ); let _ = crate::disk::main::import(&*guid, DATA_DIR, RepairStrategy::Preen, encryption_password) .await?; + let _ = ctx.disk_guid.set(guid.clone()); disk_phase.complete(); let progress = SetupExecuteProgress { diff --git a/core/startos/src/shutdown.rs b/core/startos/src/shutdown.rs index d71989da5..5df2317a3 100644 --- a/core/startos/src/shutdown.rs +++ b/core/startos/src/shutdown.rs @@ -1,17 +1,16 @@ -use std::path::{Path, PathBuf}; use std::sync::Arc; +use crate::PLATFORM; use crate::context::RpcContext; use crate::disk::main::export; use crate::init::{STANDBY_MODE_PATH, SYSTEM_REBUILD_PATH}; use crate::prelude::*; use crate::sound::SHUTDOWN; use crate::util::Invoke; -use crate::{DATA_DIR, PLATFORM}; #[derive(Debug, Clone)] pub struct Shutdown { - pub export_args: Option<(Arc, PathBuf)>, + pub disk_guid: Option>, pub restart: bool, } impl Shutdown { @@ -41,8 +40,8 @@ impl Shutdown { tracing::error!("Error Stopping Journald: {}", e); tracing::debug!("{:?}", e); } - if let Some((guid, datadir)) = &self.export_args { - if let Err(e) = export(guid, datadir).await { + if let Some(guid) = &self.disk_guid { + if let Err(e) = export(guid, crate::DATA_DIR).await { tracing::error!("Error Exporting Volume Group: {}", e); tracing::debug!("{:?}", e); } @@ -87,7 +86,7 @@ pub async fn shutdown(ctx: RpcContext) -> Result<(), Error> { .result?; ctx.shutdown .send(Some(Shutdown { - export_args: Some((ctx.disk_guid.clone(), Path::new(DATA_DIR).to_owned())), + disk_guid: Some(ctx.disk_guid.clone()), restart: false, })) .map_err(|_| eyre!("receiver dropped")) @@ -108,7 +107,7 @@ pub async fn restart(ctx: RpcContext) -> Result<(), Error> { .result?; ctx.shutdown .send(Some(Shutdown { - export_args: Some((ctx.disk_guid.clone(), Path::new(DATA_DIR).to_owned())), + disk_guid: Some(ctx.disk_guid.clone()), restart: true, })) .map_err(|_| eyre!("receiver dropped")) diff --git a/core/startos/src/registry/signer/commitment/blake3.rs b/core/startos/src/sign/commitment/blake3.rs similarity index 96% rename from core/startos/src/registry/signer/commitment/blake3.rs rename to core/startos/src/sign/commitment/blake3.rs index d99e68c16..d16acaed3 100644 --- a/core/startos/src/registry/signer/commitment/blake3.rs +++ b/core/startos/src/sign/commitment/blake3.rs @@ -4,13 +4,13 @@ use serde::{Deserialize, Serialize}; use tokio::io::AsyncWrite; use ts_rs::TS; +use crate::CAP_10_MiB; use crate::prelude::*; -use crate::registry::signer::commitment::{Commitment, Digestable}; use crate::s9pk::merkle_archive::hash::VerifyingWriter; use crate::s9pk::merkle_archive::source::ArchiveSource; +use crate::sign::commitment::{Commitment, Digestable}; use crate::util::io::{ParallelBlake3Writer, TrackingIO}; use crate::util::serde::Base64; -use crate::CAP_10_MiB; #[derive(Clone, Debug, Deserialize, Serialize, HasModel, PartialEq, Eq, TS)] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/registry/signer/commitment/merkle_archive.rs b/core/startos/src/sign/commitment/merkle_archive.rs similarity index 98% rename from core/startos/src/registry/signer/commitment/merkle_archive.rs rename to core/startos/src/sign/commitment/merkle_archive.rs index b27fb7ef4..111923aef 100644 --- a/core/startos/src/registry/signer/commitment/merkle_archive.rs +++ b/core/startos/src/sign/commitment/merkle_archive.rs @@ -4,10 +4,10 @@ use tokio::io::AsyncWrite; use ts_rs::TS; use crate::prelude::*; -use crate::registry::signer::commitment::{Commitment, Digestable}; -use crate::s9pk::merkle_archive::source::FileSource; -use crate::s9pk::merkle_archive::MerkleArchive; use crate::s9pk::S9pk; +use crate::s9pk::merkle_archive::MerkleArchive; +use crate::s9pk::merkle_archive::source::FileSource; +use crate::sign::commitment::{Commitment, Digestable}; use crate::util::io::TrackingIO; use crate::util::serde::Base64; diff --git a/core/startos/src/registry/signer/commitment/mod.rs b/core/startos/src/sign/commitment/mod.rs similarity index 100% rename from core/startos/src/registry/signer/commitment/mod.rs rename to core/startos/src/sign/commitment/mod.rs diff --git a/core/startos/src/registry/signer/commitment/request.rs b/core/startos/src/sign/commitment/request.rs similarity index 98% rename from core/startos/src/registry/signer/commitment/request.rs rename to core/startos/src/sign/commitment/request.rs index e5bb776bf..4d9109d69 100644 --- a/core/startos/src/registry/signer/commitment/request.rs +++ b/core/startos/src/sign/commitment/request.rs @@ -13,8 +13,8 @@ use ts_rs::TS; use url::Url; use crate::prelude::*; -use crate::registry::signer::commitment::{Commitment, Digestable}; use crate::s9pk::merkle_archive::hash::VerifyingWriter; +use crate::sign::commitment::{Commitment, Digestable}; use crate::util::serde::Base64; #[derive(Clone, Debug, Deserialize, Serialize, HasModel, PartialEq, Eq, TS)] diff --git a/core/startos/src/registry/signer/sign/ed25519.rs b/core/startos/src/sign/ed25519.rs similarity index 94% rename from core/startos/src/registry/signer/sign/ed25519.rs rename to core/startos/src/sign/ed25519.rs index 3ec4c136e..5411bc3ec 100644 --- a/core/startos/src/registry/signer/sign/ed25519.rs +++ b/core/startos/src/sign/ed25519.rs @@ -2,7 +2,7 @@ use ed25519_dalek::{Signature, SigningKey, VerifyingKey}; use sha2::Sha512; use crate::prelude::*; -use crate::registry::signer::sign::SignatureScheme; +use crate::sign::SignatureScheme; pub struct Ed25519; impl SignatureScheme for Ed25519 { diff --git a/core/startos/src/registry/signer/sign/mod.rs b/core/startos/src/sign/mod.rs similarity index 98% rename from core/startos/src/registry/signer/sign/mod.rs rename to core/startos/src/sign/mod.rs index 6a95a2490..2c8c1289c 100644 --- a/core/startos/src/registry/signer/sign/mod.rs +++ b/core/startos/src/sign/mod.rs @@ -12,10 +12,11 @@ use sha2::Sha512; use ts_rs::TS; use crate::prelude::*; -use crate::registry::signer::commitment::Digestable; -use crate::registry::signer::sign::ed25519::Ed25519; +use crate::sign::commitment::Digestable; +use crate::sign::ed25519::Ed25519; use crate::util::serde::{deserialize_from_str, serialize_display}; +pub mod commitment; pub mod ed25519; pub trait SignatureScheme { @@ -60,6 +61,7 @@ pub trait SignatureScheme { } } +#[non_exhaustive] pub enum AnyScheme { Ed25519(Ed25519), } @@ -118,6 +120,7 @@ impl SignatureScheme for AnyScheme { #[derive(Clone, Debug, PartialEq, Eq, TS)] #[ts(export, type = "string")] +#[non_exhaustive] pub enum AnySigningKey { Ed25519(::SigningKey), } @@ -189,6 +192,7 @@ impl Serialize for AnySigningKey { #[derive(Clone, Debug, PartialEq, Eq, Hash, TS)] #[ts(export, type = "string")] +#[non_exhaustive] pub enum AnyVerifyingKey { Ed25519(::VerifyingKey), } @@ -261,6 +265,7 @@ impl ValueParserFactory for AnyVerifyingKey { } #[derive(Clone, Debug)] +#[non_exhaustive] pub enum AnyDigest { Sha512(Sha512), } diff --git a/core/startos/src/ssh.rs b/core/startos/src/ssh.rs index dc2f9440a..c54989f37 100644 --- a/core/startos/src/ssh.rs +++ b/core/startos/src/ssh.rs @@ -1,11 +1,11 @@ use std::collections::BTreeMap; use std::path::Path; -use clap::builder::ValueParserFactory; use clap::Parser; +use clap::builder::ValueParserFactory; use imbl_value::InternedString; use models::FromStrParser; -use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, Empty, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use tokio::fs::OpenOptions; use tokio::process::Command; @@ -15,13 +15,13 @@ use ts_rs::TS; use crate::context::{CliContext, RpcContext}; use crate::hostname::Hostname; use crate::prelude::*; -use crate::util::io::create_file; -use crate::util::serde::{display_serializable, HandlerExtSerde, Pem, WithIoFormat}; use crate::util::Invoke; +use crate::util::io::create_file; +use crate::util::serde::{HandlerExtSerde, Pem, WithIoFormat, display_serializable}; pub const SSH_DIR: &str = "/home/start9/.ssh"; -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct SshKeys(BTreeMap>); impl SshKeys { pub fn new() -> Self { diff --git a/core/startos/src/system.rs b/core/startos/src/system.rs index 996fd334b..e4befb77f 100644 --- a/core/startos/src/system.rs +++ b/core/startos/src/system.rs @@ -9,7 +9,7 @@ use color_eyre::eyre::eyre; use futures::{FutureExt, TryStreamExt}; use imbl::vector; use imbl_value::InternedString; -use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, Empty, HandlerExt, ParentHandler, from_fn_async}; use rustls::RootCertStore; use rustls_pki_types::CertificateDer; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -24,12 +24,12 @@ use crate::logs::{LogSource, LogsParams, SYSTEM_UNIT}; use crate::prelude::*; use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations}; use crate::shutdown::Shutdown; -use crate::util::cpupower::{get_available_governors, set_governor, Governor}; +use crate::util::Invoke; +use crate::util::cpupower::{Governor, get_available_governors, set_governor}; use crate::util::io::open_file; use crate::util::net::WebSocketExt; -use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; +use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable}; use crate::util::sync::Watch; -use crate::util::Invoke; use crate::{MAIN_DATA, PACKAGE_DATA}; pub fn experimental() -> ParentHandler { @@ -1039,8 +1039,8 @@ pub async fn test_smtp( ) -> Result<(), Error> { #[cfg(feature = "mail-send")] { - use mail_send::mail_builder::{self, MessageBuilder}; use mail_send::SmtpClientBuilder; + use mail_send::mail_builder::{self, MessageBuilder}; use rustls_pki_types::pem::PemObject; let Some(pass_val) = password else { diff --git a/core/startos/src/tunnel/api.rs b/core/startos/src/tunnel/api.rs new file mode 100644 index 000000000..11c5ec512 --- /dev/null +++ b/core/startos/src/tunnel/api.rs @@ -0,0 +1,130 @@ +use std::net::Ipv4Addr; + +use clap::Parser; +use ipnet::Ipv4Net; +use rpc_toolkit::{Context, Empty, HandlerExt, ParentHandler, from_fn_async}; +use serde::{Deserialize, Serialize}; + +use crate::context::CliContext; +use crate::prelude::*; +use crate::tunnel::context::TunnelContext; +use crate::tunnel::wg::WgSubnetConfig; + +pub fn tunnel_api() -> ParentHandler { + ParentHandler::new() + .subcommand( + "db", + super::db::db_api::() + .with_about("Commands to interact with the db i.e. dump and apply"), + ) + .subcommand( + "subnet", + subnet_api::().with_about("Add, remove, or modify subnets"), + ) + // .subcommand( + // "forward", + // ParentHandler::::new() + // .subcommand( + // "add", + // from_fn_async(add_forward) + // .with_metadata("sync_db", Value::Bool(true)) + // .no_display() + // .with_about("Add a new port forward") + // .with_call_remote::(), + // ) + // .subcommand( + // "remove", + // from_fn_async(remove_forward) + // .with_metadata("sync_db", Value::Bool(true)) + // .no_display() + // .with_about("Remove a port forward") + // .with_call_remote::(), + // ), + // ) +} + +#[derive(Deserialize, Serialize, Parser)] +pub struct SubnetParams { + subnet: Ipv4Net, +} + +pub fn subnet_api() -> ParentHandler { + ParentHandler::new() + .subcommand( + "add", + from_fn_async(add_subnet) + .with_metadata("sync_db", Value::Bool(true)) + .with_inherited(|a, _| a) + .no_display() + .with_about("Add a new subnet") + .with_call_remote::(), + ) + .subcommand( + "remove", + from_fn_async(remove_subnet) + .with_metadata("sync_db", Value::Bool(true)) + .with_inherited(|a, _| a) + .no_display() + .with_about("Remove a subnet") + .with_call_remote::(), + ) + // .subcommand( + // "set-default-forward-target", + // from_fn_async(set_default_forward_target) + // .with_metadata("sync_db", Value::Bool(true)) + // .no_display() + // .with_about("Set the default target for port forwarding") + // .with_call_remote::(), + // ) + // .subcommand( + // "add-client", + // from_fn_async(add_client) + // .with_metadata("sync_db", Value::Bool(true)) + // .no_display() + // .with_about("Add a client to a subnet") + // .with_call_remote::(), + // ) + // .subcommand( + // "remove-client", + // from_fn_async(remove_client) + // .with_metadata("sync_db", Value::Bool(true)) + // .no_display() + // .with_about("Remove a client from a subnet") + // .with_call_remote::(), + // ) +} + +pub async fn add_subnet( + ctx: TunnelContext, + _: Empty, + SubnetParams { subnet }: SubnetParams, +) -> Result<(), Error> { + let server = ctx + .db + .mutate(|db| { + let map = db.as_wg_mut().as_subnets_mut(); + if !map.contains_key(&subnet)? { + map.insert(&subnet, &WgSubnetConfig::new())?; + } + db.as_wg().de() + }) + .await + .result?; + server.sync().await +} + +pub async fn remove_subnet( + ctx: TunnelContext, + _: Empty, + SubnetParams { subnet }: SubnetParams, +) -> Result<(), Error> { + let server = ctx + .db + .mutate(|db| { + db.as_wg_mut().as_subnets_mut().remove(&subnet)?; + db.as_wg().de() + }) + .await + .result?; + server.sync().await +} diff --git a/core/startos/src/tunnel/client.conf.template b/core/startos/src/tunnel/client.conf.template new file mode 100644 index 000000000..2c7735eb9 --- /dev/null +++ b/core/startos/src/tunnel/client.conf.template @@ -0,0 +1,10 @@ +[Interface] +Address = {addr}/24 +PrivateKey = {privkey} + +[Peer] +PublicKey = {server_pubkey} +PresharedKey = {psk} +AllowedIPs = 0.0.0.0/0, ::/0 +Endpoint = {server_addr} +PersistentKeepalive = 25 \ No newline at end of file diff --git a/core/startos/src/tunnel/context.rs b/core/startos/src/tunnel/context.rs new file mode 100644 index 000000000..ff4b15a56 --- /dev/null +++ b/core/startos/src/tunnel/context.rs @@ -0,0 +1,224 @@ +use std::collections::BTreeSet; +use std::net::{IpAddr, Ipv6Addr, SocketAddr}; +use std::ops::Deref; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +use clap::Parser; +use imbl::OrdMap; +use imbl_value::InternedString; +use patch_db::PatchDb; +use rpc_toolkit::yajrc::RpcError; +use rpc_toolkit::{CallRemote, Context, Empty}; +use serde::{Deserialize, Serialize}; +use tokio::sync::broadcast::Sender; +use tracing::instrument; + +use crate::auth::{Sessions, check_password}; +use crate::context::CliContext; +use crate::context::config::ContextConfig; +use crate::middleware::auth::AuthContext; +use crate::middleware::signature::SignatureAuthContext; +use crate::net::forward::PortForwardController; +use crate::net::gateway::NetworkInterfaceWatcher; +use crate::prelude::*; +use crate::rpc_continuations::{OpenAuthedContinuations, RpcContinuations}; +use crate::tunnel::TUNNEL_DEFAULT_PORT; +use crate::tunnel::db::TunnelDatabase; +use crate::util::sync::SyncMutex; + +#[derive(Debug, Clone, Default, Deserialize, Serialize, Parser)] +#[serde(rename_all = "kebab-case")] +#[command(rename_all = "kebab-case")] +pub struct TunnelConfig { + #[arg(short = 'c', long = "config")] + pub config: Option, + #[arg(short = 'l', long = "listen")] + pub tunnel_listen: Option, + #[arg(short = 'd', long = "datadir")] + pub datadir: Option, +} +impl ContextConfig for TunnelConfig { + fn next(&mut self) -> Option { + self.config.take() + } + fn merge_with(&mut self, other: Self) { + self.tunnel_listen = self.tunnel_listen.take().or(other.tunnel_listen); + self.datadir = self.datadir.take().or(other.datadir); + } +} + +impl TunnelConfig { + pub fn load(mut self) -> Result { + let path = self.next(); + self.load_path_rec(path)?; + self.load_path_rec(Some("/etc/start-tunneld"))?; + Ok(self) + } +} + +pub struct TunnelContextSeed { + pub listen: SocketAddr, + pub addrs: BTreeSet, + pub db: TypedPatchDb, + pub datadir: PathBuf, + pub rpc_continuations: RpcContinuations, + pub open_authed_continuations: OpenAuthedContinuations>, + pub ephemeral_sessions: SyncMutex, + pub net_iface: NetworkInterfaceWatcher, + pub forward: PortForwardController, + pub shutdown: Sender<()>, +} + +#[derive(Clone)] +pub struct TunnelContext(Arc); +impl TunnelContext { + #[instrument(skip_all)] + pub async fn init(config: &TunnelConfig) -> Result { + let (shutdown, _) = tokio::sync::broadcast::channel(1); + let datadir = config + .datadir + .as_deref() + .unwrap_or_else(|| Path::new("/var/lib/start-tunnel")) + .to_owned(); + if tokio::fs::metadata(&datadir).await.is_err() { + tokio::fs::create_dir_all(&datadir).await?; + } + let db_path = datadir.join("tunnel.db"); + let db = TypedPatchDb::::load_or_init( + PatchDb::open(&db_path).await?, + || async { Ok(Default::default()) }, + ) + .await?; + let listen = config.tunnel_listen.unwrap_or(SocketAddr::new( + Ipv6Addr::UNSPECIFIED.into(), + TUNNEL_DEFAULT_PORT, + )); + let net_iface = NetworkInterfaceWatcher::new(async { OrdMap::new() }, []); + let forward = PortForwardController::new(net_iface.subscribe()); + Ok(Self(Arc::new(TunnelContextSeed { + listen, + addrs: crate::net::utils::all_socket_addrs_for(listen.port()) + .await? + .into_iter() + .map(|(_, a)| a.ip()) + .collect(), + db, + datadir, + rpc_continuations: RpcContinuations::new(), + open_authed_continuations: OpenAuthedContinuations::new(), + ephemeral_sessions: SyncMutex::new(Sessions::new()), + net_iface, + forward, + shutdown, + }))) + } +} +impl AsRef for TunnelContext { + fn as_ref(&self) -> &RpcContinuations { + &self.rpc_continuations + } +} + +impl Context for TunnelContext {} +impl Deref for TunnelContext { + type Target = TunnelContextSeed; + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +#[derive(Debug, Deserialize, Serialize, Parser)] +pub struct TunnelAddrParams { + pub tunnel: IpAddr, +} + +impl SignatureAuthContext for TunnelContext { + type Database = TunnelDatabase; + type AdditionalMetadata = (); + type CheckPubkeyRes = (); + fn db(&self) -> &TypedPatchDb { + &self.db + } + async fn sig_context( + &self, + ) -> impl IntoIterator + Send, Error>> + Send { + self.addrs + .iter() + .filter(|a| !match a { + IpAddr::V4(a) => a.is_loopback() || a.is_unspecified(), + IpAddr::V6(a) => a.is_loopback() || a.is_unspecified(), + }) + .map(|a| InternedString::from_display(&a)) + .map(Ok) + } + fn check_pubkey( + db: &Model, + pubkey: Option<&crate::sign::AnyVerifyingKey>, + _: Self::AdditionalMetadata, + ) -> Result { + if let Some(pubkey) = pubkey { + if db.as_auth_pubkeys().de()?.contains(pubkey) { + return Ok(()); + } + } + + Err(Error::new( + eyre!("Developer Key is not authorized"), + ErrorKind::IncorrectPassword, + )) + } + async fn post_auth_hook( + &self, + _: Self::CheckPubkeyRes, + _: &rpc_toolkit::RpcRequest, + ) -> Result<(), Error> { + Ok(()) + } +} +impl AuthContext for TunnelContext { + const LOCAL_AUTH_COOKIE_PATH: &str = "/run/start-tunnel/rpc.authcookie"; + const LOCAL_AUTH_COOKIE_OWNERSHIP: &str = "root:root"; + fn access_sessions(db: &mut Model) -> &mut Model { + db.as_sessions_mut() + } + fn ephemeral_sessions(&self) -> &SyncMutex { + &self.ephemeral_sessions + } + fn open_authed_continuations(&self) -> &OpenAuthedContinuations> { + &self.open_authed_continuations + } + fn check_password(db: &Model, password: &str) -> Result<(), Error> { + check_password(&db.as_password().de()?, password) + } +} + +impl CallRemote for CliContext { + async fn call_remote( + &self, + mut method: &str, + params: Value, + _: Empty, + ) -> Result { + let tunnel_addr = if let Some(addr) = self.tunnel_addr { + addr + } else if let Some(addr) = self.tunnel_listen { + addr + } else { + return Err(Error::new(eyre!("`--tunnel` required"), ErrorKind::InvalidRequest).into()); + }; + let sig_addr = self.tunnel_listen.unwrap_or(tunnel_addr); + let url = format!("https://{tunnel_addr}").parse()?; + + method = method.strip_prefix("tunnel.").unwrap_or(method); + + crate::middleware::signature::call_remote( + self, + url, + &InternedString::from_display(&sig_addr.ip()), + method, + params, + ) + .await + } +} diff --git a/core/startos/src/tunnel/db.rs b/core/startos/src/tunnel/db.rs new file mode 100644 index 000000000..23ed05c63 --- /dev/null +++ b/core/startos/src/tunnel/db.rs @@ -0,0 +1,197 @@ +use std::collections::{BTreeMap, HashSet}; +use std::net::{Ipv4Addr, SocketAddrV4}; +use std::path::PathBuf; + +use clap::Parser; +use imbl_value::InternedString; +use ipnet::Ipv4Net; +use itertools::Itertools; +use patch_db::Dump; +use patch_db::json_ptr::{JsonPointer, ROOT}; +use rpc_toolkit::yajrc::RpcError; +use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; +use serde::{Deserialize, Serialize}; +use tracing::instrument; +use ts_rs::TS; + +use crate::auth::Sessions; +use crate::context::CliContext; +use crate::prelude::*; +use crate::sign::AnyVerifyingKey; +use crate::tunnel::context::TunnelContext; +use crate::tunnel::wg::WgServer; +use crate::util::serde::{HandlerExtSerde, apply_expr}; + +#[derive(Default, Deserialize, Serialize, HasModel)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +pub struct TunnelDatabase { + pub sessions: Sessions, + pub password: String, + pub auth_pubkeys: HashSet, + pub wg: WgServer, + pub port_forwards: BTreeMap, +} + +pub fn db_api() -> ParentHandler { + ParentHandler::new() + .subcommand( + "dump", + from_fn_async(cli_dump) + .with_display_serializable() + .with_about("Filter/query db to display tables and records"), + ) + .subcommand( + "dump", + from_fn_async(dump) + .with_metadata("admin", Value::Bool(true)) + .no_cli(), + ) + .subcommand( + "apply", + from_fn_async(cli_apply) + .no_display() + .with_about("Update a db record"), + ) + .subcommand( + "apply", + from_fn_async(apply) + .with_metadata("admin", Value::Bool(true)) + .no_cli(), + ) +} + +#[derive(Deserialize, Serialize, Parser)] +#[serde(rename_all = "camelCase")] +#[command(rename_all = "kebab-case")] +pub struct CliDumpParams { + #[arg(long = "pointer", short = 'p')] + pointer: Option, + path: Option, +} + +#[instrument(skip_all)] +async fn cli_dump( + HandlerArgs { + context, + parent_method, + method, + params: CliDumpParams { pointer, path }, + .. + }: HandlerArgs, +) -> Result { + let dump = if let Some(path) = path { + PatchDb::open(path).await?.dump(&ROOT).await + } else { + let method = parent_method.into_iter().chain(method).join("."); + from_value::( + context + .call_remote::(&method, imbl_value::json!({ "pointer": pointer })) + .await?, + )? + }; + + Ok(dump) +} + +#[derive(Deserialize, Serialize, Parser, TS)] +#[serde(rename_all = "camelCase")] +#[command(rename_all = "kebab-case")] +pub struct DumpParams { + #[arg(long = "pointer", short = 'p')] + #[ts(type = "string | null")] + pointer: Option, +} + +pub async fn dump(ctx: TunnelContext, DumpParams { pointer }: DumpParams) -> Result { + Ok(ctx + .db + .dump(&pointer.as_ref().map_or(ROOT, |p| p.borrowed())) + .await) +} + +#[derive(Deserialize, Serialize, Parser)] +#[serde(rename_all = "camelCase")] +#[command(rename_all = "kebab-case")] +pub struct CliApplyParams { + expr: String, + path: Option, +} + +#[instrument(skip_all)] +async fn cli_apply( + HandlerArgs { + context, + parent_method, + method, + params: CliApplyParams { expr, path }, + .. + }: HandlerArgs, +) -> Result<(), RpcError> { + if let Some(path) = path { + PatchDb::open(path) + .await? + .apply_function(|db| { + let res = apply_expr( + serde_json::to_value(patch_db::Value::from(db)) + .with_kind(ErrorKind::Deserialization)? + .into(), + &expr, + )?; + + Ok::<_, Error>(( + to_value( + &serde_json::from_value::(res.clone().into()).with_ctx( + |_| { + ( + crate::ErrorKind::Deserialization, + "result does not match database model", + ) + }, + )?, + )?, + (), + )) + }) + .await + .result?; + } else { + let method = parent_method.into_iter().chain(method).join("."); + context + .call_remote::(&method, imbl_value::json!({ "expr": expr })) + .await?; + } + + Ok(()) +} + +#[derive(Deserialize, Serialize, Parser, TS)] +#[serde(rename_all = "camelCase")] +#[command(rename_all = "kebab-case")] +pub struct ApplyParams { + expr: String, + path: Option, +} + +pub async fn apply(ctx: TunnelContext, ApplyParams { expr, .. }: ApplyParams) -> Result<(), Error> { + ctx.db + .mutate(|db| { + let res = apply_expr( + serde_json::to_value(patch_db::Value::from(db.clone())) + .with_kind(ErrorKind::Deserialization)? + .into(), + &expr, + )?; + + db.ser( + &serde_json::from_value::(res.clone().into()).with_ctx(|_| { + ( + crate::ErrorKind::Deserialization, + "result does not match database model", + ) + })?, + ) + }) + .await + .result +} diff --git a/core/startos/src/tunnel/forward.rs b/core/startos/src/tunnel/forward.rs new file mode 100644 index 000000000..3616db216 --- /dev/null +++ b/core/startos/src/tunnel/forward.rs @@ -0,0 +1 @@ +use crate::prelude::*; diff --git a/core/startos/src/tunnel/mod.rs b/core/startos/src/tunnel/mod.rs new file mode 100644 index 000000000..152526c01 --- /dev/null +++ b/core/startos/src/tunnel/mod.rs @@ -0,0 +1,82 @@ +use axum::Router; +use futures::future::ready; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, Server, from_fn_async}; + +use crate::context::CliContext; +use crate::middleware::auth::Auth; +use crate::middleware::cors::Cors; +use crate::net::static_server::{bad_request, not_found, server_error}; +use crate::net::web_server::{Accept, WebServer}; +use crate::prelude::*; +use crate::rpc_continuations::Guid; +use crate::tunnel::context::TunnelContext; + +pub mod api; +pub mod context; +pub mod db; +pub mod forward; +pub mod wg; + +pub const TUNNEL_DEFAULT_PORT: u16 = 5960; + +pub fn tunnel_router(ctx: TunnelContext) -> Router { + use axum::extract as x; + use axum::routing::{any, get}; + Router::new() + .route("/rpc/{*path}", { + let ctx = ctx.clone(); + any( + Server::new(move || ready(Ok(ctx.clone())), api::tunnel_api()) + .middleware(Cors::new()) + .middleware(Auth::new()) + ) + }) + .route( + "/ws/rpc/{*path}", + get({ + let ctx = ctx.clone(); + move |x::Path(path): x::Path, + ws: axum::extract::ws::WebSocketUpgrade| async move { + match Guid::from(&path) { + None => { + tracing::debug!("No Guid Path"); + bad_request() + } + Some(guid) => match ctx.rpc_continuations.get_ws_handler(&guid).await { + Some(cont) => ws.on_upgrade(cont), + _ => not_found(), + }, + } + } + }), + ) + .route( + "/rest/rpc/{*path}", + any({ + let ctx = ctx.clone(); + move |request: x::Request| async move { + let path = request + .uri() + .path() + .strip_prefix("/rest/rpc/") + .unwrap_or_default(); + match Guid::from(&path) { + None => { + tracing::debug!("No Guid Path"); + bad_request() + } + Some(guid) => match ctx.rpc_continuations.get_rest_handler(&guid).await { + None => not_found(), + Some(cont) => cont(request).await.unwrap_or_else(server_error), + }, + } + } + }), + ) +} + +impl WebServer { + pub fn serve_tunnel(&mut self, ctx: TunnelContext) { + self.serve_router(tunnel_router(ctx)) + } +} diff --git a/core/startos/src/tunnel/server-peer.conf.template b/core/startos/src/tunnel/server-peer.conf.template new file mode 100644 index 000000000..387b5171f --- /dev/null +++ b/core/startos/src/tunnel/server-peer.conf.template @@ -0,0 +1,4 @@ +[Peer] +PublicKey = {pubkey} +PresharedKey = {psk} +AllowedIPs = {addr}/32 diff --git a/core/startos/src/tunnel/server.conf.template b/core/startos/src/tunnel/server.conf.template new file mode 100644 index 000000000..52b56139c --- /dev/null +++ b/core/startos/src/tunnel/server.conf.template @@ -0,0 +1,5 @@ +[Interface] +Address = {subnets} +PrivateKey = {server_privkey} +ListenPort = {server_port} + diff --git a/core/startos/src/tunnel/wg.rs b/core/startos/src/tunnel/wg.rs new file mode 100644 index 000000000..4f5277119 --- /dev/null +++ b/core/startos/src/tunnel/wg.rs @@ -0,0 +1,220 @@ +use std::collections::BTreeMap; +use std::net::{Ipv4Addr, SocketAddrV4}; + +use ed25519_dalek::{SigningKey, VerifyingKey}; +use imbl_value::InternedString; +use ipnet::Ipv4Net; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use tokio::process::Command; + +use crate::prelude::*; +use crate::util::Invoke; +use crate::util::io::write_file_atomic; +use crate::util::serde::Base64; + +#[derive(Deserialize, Serialize, HasModel)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +pub struct WgServer { + pub port: u16, + pub key: Base64, + pub subnets: WgSubnetMap, +} +impl Default for WgServer { + fn default() -> Self { + Self { + port: 51820, + key: Base64(WgKey::generate()), + subnets: WgSubnetMap::default(), + } + } +} +impl WgServer { + pub fn server_config<'a>(&'a self) -> ServerConfig<'a> { + ServerConfig(self) + } + pub async fn sync(&self) -> Result<(), Error> { + Command::new("wg-quick") + .arg("down") + .arg("wg0") + .invoke(ErrorKind::Network) + .await + .or_else(|e| { + let msg = e.source.to_string(); + if msg.contains("does not exist") || msg.contains("is not a WireGuard interface") { + Ok(Vec::new()) + } else { + Err(e) + } + })?; + write_file_atomic( + "/etc/wireguard/wg0.conf", + self.server_config().to_string().as_bytes(), + ) + .await?; + Command::new("wg-quick") + .arg("up") + .arg("wg0") + .invoke(ErrorKind::Network) + .await?; + Ok(()) + } +} + +#[derive(Default, Deserialize, Serialize)] +pub struct WgSubnetMap(pub BTreeMap); +impl Map for WgSubnetMap { + type Key = Ipv4Net; + type Value = WgSubnetConfig; + fn key_str(key: &Self::Key) -> Result, Error> { + Self::key_string(key) + } + fn key_string(key: &Self::Key) -> Result { + Ok(InternedString::from_display(key)) + } +} + +#[derive(Default, Deserialize, Serialize, HasModel)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +pub struct WgSubnetConfig { + pub default_forward_target: Option, + pub clients: BTreeMap, +} +impl WgSubnetConfig { + pub fn new() -> Self { + Self::default() + } + pub fn add_client<'a>( + &'a mut self, + subnet: Ipv4Net, + ) -> Result<(Ipv4Addr, &'a WgConfig), Error> { + let addr = subnet + .hosts() + .find(|a| !self.clients.contains_key(a)) + .ok_or_else(|| Error::new(eyre!("subnet exhausted"), ErrorKind::Network))?; + let config = self.clients.entry(addr).or_insert(WgConfig::generate()); + Ok((addr, config)) + } +} + +pub struct WgKey(SigningKey); +impl WgKey { + pub fn generate() -> Self { + Self(SigningKey::generate( + &mut ssh_key::rand_core::OsRng::default(), + )) + } +} +impl AsRef<[u8]> for WgKey { + fn as_ref(&self) -> &[u8] { + self.0.as_bytes() + } +} +impl TryFrom> for WgKey { + type Error = ed25519_dalek::SignatureError; + fn try_from(value: Vec) -> Result { + Ok(Self(value.as_slice().try_into()?)) + } +} +impl std::ops::Deref for WgKey { + type Target = SigningKey; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl Base64 { + pub fn verifying_key(&self) -> Base64 { + Base64(self.0.verifying_key()) + } +} + +#[derive(Deserialize, Serialize, HasModel)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +pub struct WgConfig { + pub key: Base64, + pub psk: Base64<[u8; 32]>, +} +impl WgConfig { + pub fn generate() -> Self { + Self { + key: Base64(WgKey::generate()), + psk: Base64(rand::random()), + } + } + pub fn server_peer_config<'a>(&'a self, addr: Ipv4Addr) -> ServerPeerConfig<'a> { + ServerPeerConfig { + client_config: self, + client_addr: addr, + } + } + pub fn client_config<'a>( + &'a self, + addr: Ipv4Addr, + server_pubkey: Base64, + server_addr: SocketAddrV4, + ) -> ClientConfig<'a> { + ClientConfig { + client_config: self, + client_addr: addr, + server_pubkey, + server_addr, + } + } +} + +pub struct ServerPeerConfig<'a> { + client_config: &'a WgConfig, + client_addr: Ipv4Addr, +} +impl<'a> std::fmt::Display for ServerPeerConfig<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + include_str!("./server-peer.conf.template"), + pubkey = self.client_config.key.verifying_key().to_padded_string(), + psk = self.client_config.psk.to_padded_string(), + addr = self.client_addr, + ) + } +} + +pub struct ClientConfig<'a> { + client_config: &'a WgConfig, + client_addr: Ipv4Addr, + server_pubkey: Base64, + server_addr: SocketAddrV4, +} +impl<'a> std::fmt::Display for ClientConfig<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + include_str!("./client.conf.template"), + privkey = self.client_config.key.to_padded_string(), + psk = self.client_config.psk, + addr = self.client_addr, + server_pubkey = self.server_pubkey.to_padded_string(), + server_addr = self.server_addr, + ) + } +} + +pub struct ServerConfig<'a>(&'a WgServer); +impl<'a> std::fmt::Display for ServerConfig<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self(server) = *self; + write!( + f, + include_str!("./server.conf.template"), + subnets = server.subnets.0.keys().join(", "), + server_port = server.port, + server_privkey = server.key.to_padded_string(), + )?; + for (addr, peer) in server.subnets.0.values().flat_map(|s| &s.clients) { + write!(f, "{}", peer.server_peer_config(*addr))?; + } + Ok(()) + } +} diff --git a/core/startos/src/update/mod.rs b/core/startos/src/update/mod.rs index 8826f78dc..635ab59c1 100644 --- a/core/startos/src/update/mod.rs +++ b/core/startos/src/update/mod.rs @@ -3,7 +3,7 @@ use std::path::Path; use std::time::Duration; use clap::{ArgAction, Parser}; -use color_eyre::eyre::{eyre, Result}; +use color_eyre::eyre::{Result, eyre}; use exver::{Version, VersionRange}; use futures::TryStreamExt; use helpers::{AtomicFile, NonDetachingJoinHandle}; @@ -17,32 +17,32 @@ use tokio::process::Command; use tracing::instrument; use ts_rs::TS; +use crate::PLATFORM; use crate::context::{CliContext, RpcContext}; +use crate::disk::mount::filesystem::MountType; use crate::disk::mount::filesystem::bind::Bind; use crate::disk::mount::filesystem::block_dev::BlockDev; use crate::disk::mount::filesystem::efivarfs::EfiVarFs; use crate::disk::mount::filesystem::overlayfs::OverlayGuard; -use crate::disk::mount::filesystem::MountType; use crate::disk::mount::guard::{GenericMountGuard, MountGuard, TmpMountGuard}; -use crate::notifications::{notify, NotificationLevel}; +use crate::notifications::{NotificationLevel, notify}; use crate::prelude::*; use crate::progress::{ FullProgressTracker, PhaseProgressTrackerHandle, PhasedProgressBar, ProgressUnits, }; use crate::registry::asset::RegistryAsset; use crate::registry::context::{RegistryContext, RegistryUrlParams}; -use crate::registry::os::index::OsVersionInfo; use crate::registry::os::SIG_CONTEXT; -use crate::registry::signer::commitment::blake3::Blake3Commitment; -use crate::registry::signer::commitment::Commitment; +use crate::registry::os::index::OsVersionInfo; use crate::rpc_continuations::{Guid, RpcContinuation}; use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; +use crate::sign::commitment::Commitment; +use crate::sign::commitment::blake3::Blake3Commitment; use crate::sound::{ CIRCLE_OF_5THS_SHORT, UPDATE_FAILED_1, UPDATE_FAILED_2, UPDATE_FAILED_3, UPDATE_FAILED_4, }; -use crate::util::net::WebSocketExt; use crate::util::Invoke; -use crate::PLATFORM; +use crate::util::net::WebSocketExt; #[derive(Deserialize, Serialize, Parser, TS)] #[serde(rename_all = "camelCase")] @@ -87,7 +87,12 @@ pub async fn update_system( .into_updated() .de()? { - return Err(Error::new(eyre!("Server was already updated. Please restart your device before attempting to update again."), ErrorKind::InvalidRequest)); + return Err(Error::new( + eyre!( + "Server was already updated. Please restart your device before attempting to update again." + ), + ErrorKind::InvalidRequest, + )); } let target = maybe_do_update(ctx.clone(), registry, target.unwrap_or(VersionRange::Any)).await?; @@ -202,6 +207,7 @@ pub async fn cli_update_system( prev.overall.set_complete(); progress.update(&prev); } + println!("Update complete. Restart your server to apply the update.") } else { println!("Updating to v{v}...") } @@ -297,7 +303,9 @@ async fn maybe_do_update( if status.updated { return Err(Error::new( - eyre!("Server was already updated. Please restart your device before attempting to update again."), + eyre!( + "Server was already updated. Please restart your device before attempting to update again." + ), crate::ErrorKind::InvalidRequest, )); } diff --git a/core/startos/src/upload.rs b/core/startos/src/upload.rs index d7428bb44..ca34f34d1 100644 --- a/core/startos/src/upload.rs +++ b/core/startos/src/upload.rs @@ -8,7 +8,7 @@ use axum::body::Body; use axum::extract::Request; use axum::response::Response; use bytes::Bytes; -use futures::{ready, FutureExt, Stream, StreamExt}; +use futures::{FutureExt, Stream, StreamExt, ready}; use http::header::CONTENT_LENGTH; use http::{HeaderMap, StatusCode}; use imbl_value::InternedString; @@ -20,9 +20,9 @@ use crate::context::RpcContext; use crate::prelude::*; use crate::progress::{PhaseProgressTrackerHandle, ProgressUnits}; use crate::rpc_continuations::{Guid, RpcContinuation}; -use crate::s9pk::merkle_archive::source::multi_cursor_file::{FileCursor, MultiCursorFile}; use crate::s9pk::merkle_archive::source::ArchiveSource; -use crate::util::io::{create_file, TmpDir}; +use crate::s9pk::merkle_archive::source::multi_cursor_file::{FileCursor, MultiCursorFile}; +use crate::util::io::{TmpDir, create_file}; pub async fn upload( ctx: &RpcContext, @@ -359,7 +359,7 @@ impl UploadHandle { }); } } - async fn process_body>>( + async fn process_body>>( &mut self, mut body: impl Stream> + Unpin, ) { diff --git a/core/startos/src/util/actor/background.rs b/core/startos/src/util/actor/background.rs index 7666cbf04..87a535f87 100644 --- a/core/startos/src/util/actor/background.rs +++ b/core/startos/src/util/actor/background.rs @@ -63,3 +63,29 @@ impl Future for BackgroundJobRunner { } } } +impl BackgroundJobRunner { + pub fn run_while( + &mut self, + fut: Fut, + ) -> impl Future + Send { + #[pin_project::pin_project] + struct RunWhile<'a, Fut> { + #[pin] + runner: &'a mut BackgroundJobRunner, + #[pin] + fut: Fut, + } + impl<'a, Fut: Future> Future for RunWhile<'a, Fut> { + type Output = Fut::Output; + fn poll( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + let this = self.project(); + let _ = this.runner.poll(cx); + this.fut.poll(cx) + } + } + RunWhile { runner: self, fut } + } +} diff --git a/core/startos/src/util/actor/concurrent.rs b/core/startos/src/util/actor/concurrent.rs index 5a24970d4..a929a0ab3 100644 --- a/core/startos/src/util/actor/concurrent.rs +++ b/core/startos/src/util/actor/concurrent.rs @@ -2,7 +2,7 @@ use std::any::Any; use std::sync::Arc; use std::time::Duration; -use futures::future::{ready, BoxFuture}; +use futures::future::{BoxFuture, ready}; use futures::{Future, FutureExt, TryFutureExt}; use helpers::NonDetachingJoinHandle; use tokio::sync::{mpsc, oneshot}; @@ -318,8 +318,10 @@ mod test { .await .is_ok() ); - assert!(tokio::time::timeout(Duration::from_secs(1), pending) - .await - .is_err()); + assert!( + tokio::time::timeout(Duration::from_secs(1), pending) + .await + .is_err() + ); } } diff --git a/core/startos/src/util/actor/mod.rs b/core/startos/src/util/actor/mod.rs index 05cdd83c1..0787e61e7 100644 --- a/core/startos/src/util/actor/mod.rs +++ b/core/startos/src/util/actor/mod.rs @@ -146,11 +146,7 @@ impl ConflictBuilder { Arc::new(move |m| { self.base ^ if let Some(entry) = self.except.get(&m.type_id()) { - if let Some(f) = entry { - f(m) - } else { - true - } + if let Some(f) = entry { f(m) } else { true } } else { false } diff --git a/core/startos/src/util/collections/eq_map.rs b/core/startos/src/util/collections/eq_map.rs index 5078866a5..d03667e88 100644 --- a/core/startos/src/util/collections/eq_map.rs +++ b/core/startos/src/util/collections/eq_map.rs @@ -666,13 +666,27 @@ impl IntoIterator for EqMap { impl Extend<(K, V)> for EqMap { fn extend>(&mut self, iter: T) { - self.0.extend(iter) + let iter = iter.into_iter(); + if let (_, Some(len)) = iter.size_hint() { + self.0.reserve(len) + } + for (k, v) in iter { + self.insert(k, v); + } } } impl FromIterator<(K, V)> for EqMap { fn from_iter>(iter: T) -> Self { - Self(Vec::from_iter(iter)) + let mut res = Self(Vec::new()); + let iter = iter.into_iter(); + if let (_, Some(len)) = iter.size_hint() { + res.0.reserve(len) + } + for (k, v) in iter { + res.insert(k, v); + } + res } } @@ -687,7 +701,7 @@ impl From<[(K, V); N]> for EqMap { /// assert_eq!(map1, map2); /// ``` fn from(arr: [(K, V); N]) -> Self { - EqMap(Vec::from(arr)) + Self::from_iter(arr) } } diff --git a/core/startos/src/util/collections/eq_set.rs b/core/startos/src/util/collections/eq_set.rs new file mode 100644 index 000000000..5ff4d3ba9 --- /dev/null +++ b/core/startos/src/util/collections/eq_set.rs @@ -0,0 +1,425 @@ +use std::borrow::Borrow; +use std::fmt; +use std::marker::PhantomData; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Serialize)] +pub struct EqSet(Vec); +impl Default for EqSet { + fn default() -> Self { + Self(Default::default()) + } +} +impl EqSet { + pub fn new() -> Self { + Self::default() + } + + pub fn clear(&mut self) { + self.0.clear() + } + + /// Returns a reference to the element in the set, if any, that is equal to + /// the value. + /// + /// The value may be any borrowed form of the set's element type, + /// but the ordering on the borrowed form *must* match the + /// ordering on the element type. + /// + /// # Examples + /// + /// ``` + /// use startos::util::collections::EqSet; + /// + /// let set = EqSet::from([1, 2, 3]); + /// assert_eq!(set.get(&2), Some(&2)); + /// assert_eq!(set.get(&4), None); + /// ``` + pub fn get(&self, value: &Q) -> Option<&T> + where + T: Borrow, + Q: Eq, + { + self.0.iter().find(|k| (*k).borrow() == value) + } + + /// Removes and returns an element in the set. + /// There is no guarantee about which element this might be + /// + /// # Examples + /// + /// ``` + /// use startos::util::collections::EqSet; + /// + /// let mut set = EqSet::new(); + /// set.insert("a"); + /// set.insert("b"); + /// while let Some(_val) = set.pop() { } + /// assert!(set.is_empty()); + /// ``` + pub fn pop(&mut self) -> Option { + self.0.pop() + } + + /// Returns `true` if the set contains a value for the specified value. + /// + /// The value may be any borrowed form of the set's value type, but the equality + /// on the borrowed form *must* match the equality on the value type. + /// + /// # Examples + /// + /// ``` + /// use startos::util::collections::EqSet; + /// + /// let mut set = EqSet::new(); + /// set.insert("a"); + /// assert_eq!(set.contains("a"), true); + /// assert_eq!(set.contains("b"), false); + /// ``` + pub fn contains(&self, value: &Q) -> bool + where + T: Borrow, + Q: Eq, + { + self.get(value).is_some() + } + + /// Inserts a value into the set. + /// + /// If the set did not have this value present, `None` is returned. + /// + /// If the set did have this value present, the value is updated, and the old + /// value is returned. + /// + /// # Examples + /// + /// ``` + /// use startos::util::collections::EqSet; + /// + /// let mut set = EqSet::new(); + /// assert_eq!(set.insert("a"), None); + /// assert_eq!(set.is_empty(), false); + /// + /// set.insert("b"); + /// assert_eq!(set.insert("b"), Some("b")); + /// assert!(set.contains("a")); + /// ``` + pub fn insert(&mut self, value: T) -> Option { + if let Some(entry) = self.0.iter_mut().find(|a| *a == &value) { + Some(std::mem::replace(entry, value)) + } else { + self.0.push(value); + None + } + } + + /// Tries to insert a value into the set. + /// + /// If the set already had this value present, nothing is updated. + /// + /// Returns whether the value was inserted. + /// + /// # Examples + /// + /// ``` + /// use startos::util::collections::EqSet; + /// + /// let mut set = EqSet::new(); + /// assert!(set.try_insert("a")); + /// assert!(!set.try_insert("a")); + /// ``` + pub fn try_insert(&mut self, value: T) -> bool { + if self.0.iter().find(|a| *a == &value).is_some() { + false + } else { + self.0.push(value); + true + } + } + + /// Removes a value from the set, returning the value if it + /// was previously in the set. + /// + /// The value may be any borrowed form of the set's value type, but the equality + /// on the borrowed form *must* match the equality on the value type. + /// + /// # Examples + /// + /// ``` + /// use startos::util::collections::EqSet; + /// + /// let mut set = EqSet::new(); + /// set.insert("a"); + /// assert_eq!(set.remove("a"), Some("a")); + /// assert_eq!(set.remove("a"), None); + /// ``` + pub fn remove(&mut self, value: &Q) -> Option + where + T: Borrow, + Q: Eq, + { + if let Some((idx, _)) = self + .0 + .iter() + .enumerate() + .find(|(_, v)| (*v).borrow() == value) + { + Some(self.0.swap_remove(idx)) + } else { + None + } + } + + /// Retains only the elements specified by the predicate. + /// + /// In other words, remove all elements `x` for which `f(&x)` returns `false`. + /// The elements are visited in order. + /// + /// # Examples + /// + /// ``` + /// use startos::util::collections::EqSet; + /// + /// let mut set: EqSet = (0..8).collect(); + /// // Keep only the elements with even-numbered values. + /// set.retain(|x| *x % 2 == 0); + /// assert!(set.into_iter().eq(vec![0, 2, 4, 6])); + /// ``` + #[inline] + pub fn retain(&mut self, f: F) + where + F: FnMut(&T) -> bool, + { + self.0.retain(f) + } + + /// Moves all elements from `other` into `self`, leaving `other` empty. + /// + /// If a value from `other` is already present in `self`, the respective + /// value from `self` will be overwritten with the respective value from `other`. + /// + /// # Examples + /// + /// ``` + /// use startos::util::collections::EqSet; + /// + /// let mut a = EqSet::new(); + /// a.insert("a"); + /// a.insert("b"); + /// a.insert("c"); // Note: "c" also present in b. + /// + /// let mut b = EqSet::new(); + /// b.insert("c"); // Note: "c" also present in a. + /// b.insert("d"); + /// b.insert("e"); + /// + /// a.append(&mut b); + /// + /// assert_eq!(a.len(), 5); + /// assert_eq!(b.len(), 0); + /// ``` + pub fn append(&mut self, other: &mut Self) { + other.retain(|v| !self.contains(v)); + self.0.append(&mut other.0) + } + + // /// Creates an iterator that visits all elements (values) and + // /// uses a closure to determine if an element should be removed. If the + // /// closure returns `true`, the element is removed from the set and yielded. + // /// If the closure returns `false`, or panics, the element remains in the set + // /// and will not be yielded. + // /// + // /// The iterator also lets you mutate the value of each element in the + // /// closure, regardless of whether you choose to keep or remove it. + // /// + // /// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating + // /// or the iteration short-circuits, then the remaining elements will be retained. + // /// Use [`retain`] with a negated predicate if you do not need the returned iterator. + // /// + // /// [`retain`]: EqSet::retain + // /// + // /// # Examples + // /// + // /// Splitting a set into even and odd values, reusing the original set: + // /// + // /// ``` + // /// use startos::util::collections::EqSet; + // /// + // /// let mut set: EqSet<(i32, i32)> = (0..8).map(|x| (x, x)).collect(); + // /// let evens: EqSet<_, _> = set.extract_if(|k, _v| k % 2 == 0).collect(); + // /// let odds = set; + // /// assert_eq!(evens.values().copied().collect::>(), [0, 2, 4, 6]); + // /// assert_eq!(odds.values().copied().collect::>(), [1, 3, 5, 7]); + // /// ``` + // pub fn extract_if(&mut self, pred: F) -> ExtractIf<'_, T, F> + // where + // K: Eq, + // F: FnMut(&K, &mut V) -> bool, + // { + // let (inner, alloc) = self.extract_if_inner(); + // ExtractIf { pred, inner, alloc } + // } + + /// Gets an iterator over the entries of the set, in no particular order. + /// + /// # Examples + /// + /// ``` + /// use startos::util::collections::EqSet; + /// + /// let mut set = EqSet::new(); + /// set.insert("c"); + /// set.insert("b"); + /// set.insert("a"); + /// + /// for value in set.iter() { + /// println!("{value}"); + /// } + /// + /// let first_value = set.iter().next().unwrap(); + /// assert_eq!(*first_value, "c"); + /// ``` + pub fn iter(&self) -> std::slice::Iter<'_, T> { + self.0.iter() + } + + /// Returns the number of elements in the set. + /// + /// # Examples + /// + /// ``` + /// use startos::util::collections::EqSet; + /// + /// let mut a = EqSet::new(); + /// assert_eq!(a.len(), 0); + /// a.insert("a"); + /// assert_eq!(a.len(), 1); + /// ``` + #[must_use] + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns `true` if the set contains no elements. + /// + /// # Examples + /// + /// ``` + /// use startos::util::collections::EqSet; + /// + /// let mut a = EqSet::new(); + /// assert!(a.is_empty()); + /// a.insert("a"); + /// assert!(!a.is_empty()); + /// ``` + #[must_use] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl fmt::Debug for EqSet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_set().entries(self.iter()).finish() + } +} + +impl IntoIterator for EqSet { + type IntoIter = std::vec::IntoIter; + type Item = T; + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Extend for EqSet { + fn extend>(&mut self, iter: I) { + let iter = iter.into_iter(); + if let (_, Some(len)) = iter.size_hint() { + self.0.reserve(len) + } + for v in iter { + self.insert(v); + } + } +} + +impl FromIterator for EqSet { + fn from_iter>(iter: I) -> Self { + let mut res = Self(Vec::new()); + let iter = iter.into_iter(); + if let (_, Some(len)) = iter.size_hint() { + res.0.reserve(len) + } + for v in iter { + res.insert(v); + } + res + } +} + +impl From<[T; N]> for EqSet { + /// Converts a `[T; N]` into a `EqSet`. + /// + /// ``` + /// use startos::util::collections::EqSet; + /// + /// let set1 = EqSet::from([(1, 2), (3, 4)]); + /// let set2: EqSet<_> = [(1, 2), (3, 4)].into(); + /// assert_eq!(set1, set2); + /// ``` + fn from(arr: [T; N]) -> Self { + EqSet::from_iter(arr) + } +} + +impl PartialEq for EqSet { + fn eq(&self, other: &Self) -> bool { + self.len() == other.len() && self.iter().all(|v| other.get(v) == Some(v)) + } +} +impl Eq for EqSet {} + +impl<'de, T> Deserialize<'de> for EqSet +where + T: Deserialize<'de> + Eq, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct Visitor { + marker: PhantomData, + } + + impl<'de, T> serde::de::Visitor<'de> for Visitor + where + T: Deserialize<'de> + Eq, + { + type Value = EqSet; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut values = EqSet(Vec::new()); + + while let Some(value) = seq.next_element()? { + values.insert(value); + } + + Ok(values) + } + } + + let visitor = Visitor { + marker: PhantomData, + }; + deserializer.deserialize_seq(visitor) + } +} diff --git a/core/startos/src/util/collections/mod.rs b/core/startos/src/util/collections/mod.rs index aa6e3ddb5..2070343bd 100644 --- a/core/startos/src/util/collections/mod.rs +++ b/core/startos/src/util/collections/mod.rs @@ -1,3 +1,45 @@ pub mod eq_map; +pub mod eq_set; + +use std::marker::PhantomData; +use std::ops::Bound; pub use eq_map::EqMap; +pub use eq_set::EqSet; +use imbl::OrdMap; + +pub struct OrdMapIterMut<'a, K: 'a, V: 'a> { + map: *mut OrdMap, + prev: Option<&'a K>, + _marker: PhantomData<&'a mut (K, V)>, +} +impl<'a, K, V> From<&'a mut OrdMap> for OrdMapIterMut<'a, K, V> { + fn from(value: &'a mut OrdMap) -> Self { + Self { + map: value, + prev: None, + _marker: PhantomData, + } + } +} +impl<'a, K: Ord + Clone, V: Clone> Iterator for OrdMapIterMut<'a, K, V> { + type Item = (&'a K, &'a mut V); + fn next(&mut self) -> Option { + unsafe { + let map: &'a mut OrdMap = self.map.as_mut().unwrap(); + let Some((k, _)) = (if let Some(k) = self.prev.take() { + map.range((Bound::Excluded(k), Bound::Unbounded)).next() + } else { + map.get_min().map(|(k, v)| (k, v)) + }) else { + return None; + }; + let k = k.clone(); // hate that I have to do this but whatev + let res = map.get_key_value_mut(&k); + if let Some((k, _)) = &res { + self.prev = Some(*k); + } + res + } + } +} diff --git a/core/startos/src/util/crypto.rs b/core/startos/src/util/crypto.rs index bb164b6ad..a99e4593a 100644 --- a/core/startos/src/util/crypto.rs +++ b/core/startos/src/util/crypto.rs @@ -1,4 +1,4 @@ -use ed25519_dalek::{SecretKey, EXPANDED_SECRET_KEY_LENGTH}; +use ed25519_dalek::{EXPANDED_SECRET_KEY_LENGTH, SecretKey}; #[inline] pub fn ed25519_expand_key(key: &SecretKey) -> [u8; EXPANDED_SECRET_KEY_LENGTH] { @@ -8,8 +8,8 @@ pub fn ed25519_expand_key(key: &SecretKey) -> [u8; EXPANDED_SECRET_KEY_LENGTH] { .to_bytes() } -use aes::cipher::{CipherKey, NewCipher, Nonce, StreamCipher}; use aes::Aes256Ctr; +use aes::cipher::{CipherKey, NewCipher, Nonce, StreamCipher}; use hmac::Hmac; use josekit::jwk::Jwk; use serde::{Deserialize, Serialize}; diff --git a/core/startos/src/util/future.rs b/core/startos/src/util/future.rs index c690f9754..7ac34ff02 100644 --- a/core/startos/src/util/future.rs +++ b/core/startos/src/util/future.rs @@ -1,7 +1,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; -use futures::future::{abortable, pending, BoxFuture, FusedFuture}; +use futures::future::{BoxFuture, FusedFuture, abortable, pending}; use futures::stream::{AbortHandle, Abortable, BoxStream}; use futures::{Future, FutureExt, Stream, StreamExt}; use tokio::sync::watch; diff --git a/core/startos/src/util/http_reader.rs b/core/startos/src/util/http_reader.rs index 02a9f57ae..688a1be36 100644 --- a/core/startos/src/util/http_reader.rs +++ b/core/startos/src/util/http_reader.rs @@ -122,7 +122,7 @@ impl HttpReader { http_url ), crate::ErrorKind::MissingHeader, - )) + )); } }; @@ -137,7 +137,7 @@ impl HttpReader { return Err(Error::new( eyre!("No content length headers for {}", http_url), crate::ErrorKind::MissingHeader, - )) + )); } }; diff --git a/core/startos/src/util/io.rs b/core/startos/src/util/io.rs index 565971bc7..464666e00 100644 --- a/core/startos/src/util/io.rs +++ b/core/startos/src/util/io.rs @@ -14,8 +14,9 @@ use std::time::Duration; use bytes::{Buf, BytesMut}; use clap::builder::ValueParserFactory; use futures::future::{BoxFuture, Fuse}; -use futures::{AsyncSeek, FutureExt, Stream, TryStreamExt}; +use futures::{AsyncSeek, FutureExt, Stream, StreamExt, TryStreamExt}; use helpers::{AtomicFile, NonDetachingJoinHandle}; +use inotify::{EventMask, EventStream, Inotify, WatchMask}; use models::FromStrParser; use nix::unistd::{Gid, Uid}; use serde::{Deserialize, Serialize}; @@ -390,7 +391,7 @@ impl AsyncRead for BufferedWriteReader { match this.hdl.poll(cx) { Poll::Ready(Ok(Err(e))) => return Poll::Ready(Err(e)), Poll::Ready(Err(e)) => { - return Poll::Ready(Err(std::io::Error::new(std::io::ErrorKind::BrokenPipe, e))) + return Poll::Ready(Err(std::io::Error::new(std::io::ErrorKind::BrokenPipe, e))); } _ => res, } @@ -1526,3 +1527,71 @@ impl ValueParserFactory for TermSize { FromStrParser::new() } } + +pub trait ReadWriter: AsyncRead + AsyncWrite {} +impl ReadWriter for T {} + +#[instrument(skip_all)] +fn wait_for_created<'a>( + stream: &'a mut EventStream<[u8; 1024]>, + path: &'a Path, +) -> BoxFuture<'a, Result<(), Error>> { + async { + let parent_path = path.parent().unwrap_or("/".as_ref()); + let parent; + loop { + match stream.watches().add(parent_path, WatchMask::CREATE) { + Ok(desc) => { + parent = desc; + break; + } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + wait_for_created(stream, parent_path).await?; + } + Err(e) => Err(e)?, + } + } + while let Some(e) = stream.try_next().await? { + if e.mask & EventMask::CREATE != EventMask::empty() + && e.name.as_deref() == path.file_name() + { + break; + } + } + stream.watches().remove(parent)?; + Ok(()) + } + .boxed() +} + +#[instrument(skip_all)] +pub fn file_string_stream( + path: impl Into, +) -> impl Stream, Error>> { + let path = path.into(); + async_stream::try_stream! { + let mut stream = Inotify::init()?.into_event_stream([0; 1024])?; + loop { + loop { + match stream.watches().add( + &path, + WatchMask::MODIFY | WatchMask::MOVE_SELF | WatchMask::MOVED_TO | WatchMask::DELETE_SELF, + ) { + Ok(_) => break, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + yield None; + wait_for_created(&mut stream, &path).await?; + } + Err(e) => Err(e)?, + } + } + yield maybe_read_file_to_string(&path).await?; + while let Some(e) = stream.try_next().await? { + if e.mask & EventMask::DELETE_SELF != EventMask::empty() { + break; + } + yield maybe_read_file_to_string(&path).await?; + } + } + } +} diff --git a/core/startos/src/util/iter.rs b/core/startos/src/util/iter.rs new file mode 100644 index 000000000..99e1f569c --- /dev/null +++ b/core/startos/src/util/iter.rs @@ -0,0 +1,31 @@ +pub trait TransposeResultIterExt: Sized { + type Ok: IntoIterator; + type Err; + fn transpose(self) -> TransposeResult; +} +impl TransposeResultIterExt for Result { + type Ok = T; + type Err = E; + fn transpose(self) -> TransposeResult { + TransposeResult(Some(self.map(|t| t.into_iter()))) + } +} + +pub struct TransposeResult(Option>); +impl Iterator for TransposeResult +where + T: IntoIterator, +{ + type Item = Result; + fn next(&mut self) -> Option { + if self.0.as_ref().map_or(true, |r| r.is_err()) { + self.0 + .take() + .map(|e| Err(e.map_or_else(|e| e, |_| unreachable!()))) + } else if let Some(Ok(res)) = &mut self.0 { + res.next().map(Ok) + } else { + None + } + } +} diff --git a/core/startos/src/util/logger.rs b/core/startos/src/util/logger.rs index 5fc9fb98e..d6bc01418 100644 --- a/core/startos/src/util/logger.rs +++ b/core/startos/src/util/logger.rs @@ -59,7 +59,7 @@ impl StartOSLogger { fn base_subscriber(logfile: LogFile) -> impl Subscriber { use tracing_error::ErrorLayer; use tracing_subscriber::prelude::*; - use tracing_subscriber::{fmt, EnvFilter}; + use tracing_subscriber::{EnvFilter, fmt}; let filter_layer = || { EnvFilter::builder() diff --git a/core/startos/src/util/mod.rs b/core/startos/src/util/mod.rs index a31332efa..45c402031 100644 --- a/core/startos/src/util/mod.rs +++ b/core/startos/src/util/mod.rs @@ -14,10 +14,10 @@ use ::serde::{Deserialize, Serialize}; use async_trait::async_trait; use color_eyre::eyre::{self, eyre}; use fd_lock_rs::FdLock; -use futures::future::BoxFuture; use futures::FutureExt; -use helpers::canonicalize; +use futures::future::BoxFuture; pub use helpers::NonDetachingJoinHandle; +use helpers::canonicalize; use imbl_value::InternedString; use lazy_static::lazy_static; pub use models::VersionString; @@ -25,7 +25,7 @@ use pin_project::pin_project; use sha2::Digest; use tokio::fs::File; use tokio::io::{AsyncRead, AsyncReadExt, BufReader}; -use tokio::sync::{oneshot, Mutex, OwnedMutexGuard, RwLock}; +use tokio::sync::{Mutex, OwnedMutexGuard, RwLock, oneshot}; use tracing::instrument; use ts_rs::TS; use url::Url; @@ -42,6 +42,7 @@ pub mod crypto; pub mod future; pub mod http_reader; pub mod io; +pub mod iter; pub mod logger; pub mod lshw; pub mod net; diff --git a/core/startos/src/util/rpc.rs b/core/startos/src/util/rpc.rs index f7c91eb82..165632c5d 100644 --- a/core/startos/src/util/rpc.rs +++ b/core/startos/src/util/rpc.rs @@ -1,18 +1,18 @@ use std::path::Path; use clap::Parser; -use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; +use crate::CAP_10_MiB; use crate::context::CliContext; use crate::prelude::*; +use crate::s9pk::merkle_archive::source::ArchiveSource; use crate::s9pk::merkle_archive::source::http::HttpSource; use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; -use crate::s9pk::merkle_archive::source::ArchiveSource; -use crate::util::io::{open_file, ParallelBlake3Writer}; +use crate::util::io::{ParallelBlake3Writer, open_file}; use crate::util::serde::Base16; use crate::util::{Apply, PathOrUrl}; -use crate::CAP_10_MiB; pub fn util() -> ParentHandler { ParentHandler::new().subcommand( diff --git a/core/startos/src/util/rpc_client.rs b/core/startos/src/util/rpc_client.rs index 82ce11e20..58998afad 100644 --- a/core/startos/src/util/rpc_client.rs +++ b/core/startos/src/util/rpc_client.rs @@ -10,11 +10,11 @@ use lazy_async_pool::Pool; use models::{Error, ErrorKind, ResultExt}; use rpc_toolkit::yajrc::{self, Id, RpcError, RpcMethod, RpcRequest, RpcResponse}; use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader}; use tokio::net::UnixStream; use tokio::runtime::Handle; -use tokio::sync::{oneshot, Mutex, OnceCell}; +use tokio::sync::{Mutex, OnceCell, oneshot}; use crate::util::io::TmpDir; diff --git a/core/startos/src/util/serde.rs b/core/startos/src/util/serde.rs index b7b08529f..e2d621daf 100644 --- a/core/startos/src/util/serde.rs +++ b/core/startos/src/util/serde.rs @@ -7,7 +7,7 @@ use base64::Engine; use clap::builder::ValueParserFactory; use clap::{ArgMatches, CommandFactory, FromArgMatches}; use color_eyre::eyre::eyre; -use imbl::OrdMap; +use imbl_value::imbl::OrdMap; use models::FromStrParser; use openssl::pkey::{PKey, Private}; use openssl::x509::X509; @@ -686,7 +686,7 @@ impl std::str::FromStr for Duration { return Err(Error::new( eyre!("Invalid units for duration"), crate::ErrorKind::Deserialization, - )) + )); } })) } @@ -1023,6 +1023,11 @@ pub const BASE64: base64::engine::GeneralPurpose = #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, TS)] #[ts(type = "string", concrete(T = Vec))] pub struct Base64(pub T); +impl> Base64 { + pub fn to_padded_string(&self) -> String { + base64::engine::general_purpose::STANDARD.encode(self.0.as_ref()) + } +} impl> std::fmt::Display for Base64 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&BASE64.encode(self.0.as_ref())) diff --git a/core/startos/src/util/sync.rs b/core/startos/src/util/sync.rs index 9bf396629..e9d0251eb 100644 --- a/core/startos/src/util/sync.rs +++ b/core/startos/src/util/sync.rs @@ -1,4 +1,5 @@ -use std::collections::VecDeque; +use std::collections::{BTreeMap, VecDeque}; +use std::ops::Deref; use std::pin::Pin; use std::sync::atomic::AtomicUsize; use std::sync::{Arc, Weak}; @@ -7,17 +8,217 @@ use std::task::{Poll, Waker}; use futures::stream::BoxStream; use futures::Stream; +use crate::prelude::*; + +#[cfg(feature = "unstable")] +lazy_static::lazy_static! { + static ref ID_CTR: AtomicUsize = AtomicUsize::new(0); +} + +#[cfg(not(feature = "unstable"))] +fn annotate_lock(f: F, _: bool) -> T +where + F: FnOnce() -> T, +{ + f() +} + +#[cfg(feature = "unstable")] +fn annotate_lock(f: F, id: usize, write: bool) -> T +where + F: FnOnce() -> T, +{ + std::thread_local! { + static LOCK_CTX: std::cell::RefCell>> = std::cell::RefCell::new(BTreeMap::new()); + } + if LOCK_CTX.with_borrow_mut(|ctx| { + let panic = if write { + ctx.contains_key(&id) + } else { + ctx.get(&id).copied().unwrap_or(Err(0)).is_ok() + }; + if !panic { + if write { + ctx.insert(id, Ok(())); + } else { + if let Err(count) = ctx.entry(id).or_insert(Err(0)) { + *count += 1; + } + } + } + panic + }) { + panic!("lock {id} is already locked on this thread"); + } + let tracer: helpers::NonDetachingJoinHandle<()> = { + let bt = std::backtrace::Backtrace::force_capture(); + tokio::spawn(async move { + use std::time::Duration; + + tokio::time::sleep(Duration::from_secs(10)).await; + tracing::error!("waited on lock {id} more than 10s:\n{bt}"); + }) + .into() + }; + let res = f(); + drop(tracer); + LOCK_CTX.with_borrow_mut(|ctx| { + if write { + ctx.remove(&id); + } else { + if ctx + .get_mut(&id) + .map(|c| { + c.as_mut().map_err(|count| { + *count -= 1; + *count + }) == Err(0) + }) + .unwrap_or(false) + { + ctx.remove(&id); + } + } + }); + res +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_annotate_lock() { + annotate_lock(|| annotate_lock(|| (), 0, true), 0, true) +} + #[derive(Debug, Default)] -pub struct SyncMutex(std::sync::Mutex); +pub struct SyncMutex { + #[cfg(feature = "unstable")] + id: usize, + lock: std::sync::Mutex, +} impl SyncMutex { pub fn new(t: T) -> Self { - Self(std::sync::Mutex::new(t)) + Self { + #[cfg(feature = "unstable")] + id: ID_CTR.fetch_add(1, std::sync::atomic::Ordering::SeqCst), + lock: std::sync::Mutex::new(t), + } } + #[cfg_attr(feature = "unstable", inline(never))] pub fn mutate U, U>(&self, f: F) -> U { - f(&mut *self.0.lock().unwrap()) + annotate_lock( + || f(&mut *self.lock.lock().unwrap()), + #[cfg(feature = "unstable")] + self.id, + true, + ) } + #[cfg_attr(feature = "unstable", inline(never))] pub fn peek U, U>(&self, f: F) -> U { - f(&*self.0.lock().unwrap()) + annotate_lock( + || f(&*self.lock.lock().unwrap()), + #[cfg(feature = "unstable")] + self.id, + true, + ) + } + #[cfg_attr(feature = "unstable", inline(never))] + pub fn replace(&self, value: T) -> T { + annotate_lock( + || std::mem::replace(&mut *self.lock.lock().unwrap(), value), + #[cfg(feature = "unstable")] + self.id, + true, + ) + } +} + +#[derive(Debug, Default)] +pub struct SyncRwLock { + #[cfg(feature = "unstable")] + id: usize, + lock: std::sync::RwLock, +} +impl SyncRwLock { + pub fn new(t: T) -> Self { + Self { + #[cfg(feature = "unstable")] + id: ID_CTR.fetch_add(1, std::sync::atomic::Ordering::SeqCst), + lock: std::sync::RwLock::new(t), + } + } + #[cfg_attr(feature = "unstable", inline(never))] + pub fn mutate U, U>(&self, f: F) -> U { + annotate_lock( + || f(&mut *self.lock.write().unwrap()), + #[cfg(feature = "unstable")] + self.id, + true, + ) + } + #[cfg_attr(feature = "unstable", inline(never))] + pub fn peek U, U>(&self, f: F) -> U { + annotate_lock( + || f(&*self.lock.read().unwrap()), + #[cfg(feature = "unstable")] + self.id, + false, + ) + } + #[cfg_attr(feature = "unstable", inline(never))] + pub fn replace(&self, value: T) -> T { + annotate_lock( + || std::mem::replace(&mut *self.lock.write().unwrap(), value), + #[cfg(feature = "unstable")] + self.id, + true, + ) + } +} + +#[derive(Debug, Default)] +pub struct AsyncMutex(tokio::sync::Mutex); +impl AsyncMutex { + pub fn new(t: T) -> Self { + Self(tokio::sync::Mutex::new(t)) + } + pub async fn mutate U, U>(&self, f: F) -> U { + f(&mut *self.0.lock().await) + } + pub async fn peek U, U>(&self, f: F) -> U { + f(&*self.0.lock().await) + } + pub async fn replace(&self, value: T) -> T { + std::mem::replace(&mut *self.lock().await, value) + } +} +impl Deref for AsyncMutex { + type Target = tokio::sync::Mutex; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Default)] +pub struct AsyncRwLock(tokio::sync::RwLock); +impl AsyncRwLock { + pub fn new(t: T) -> Self { + Self(tokio::sync::RwLock::new(t)) + } + pub async fn mutate U, U>(&self, f: F) -> U { + f(&mut *self.0.write().await) + } + pub async fn peek U, U>(&self, f: F) -> U { + f(&*self.0.read().await) + } + pub async fn replace(&self, value: T) -> T { + std::mem::replace(&mut *self.0.write().await, value) + } +} +impl Deref for AsyncRwLock { + type Target = tokio::sync::RwLock; + fn deref(&self) -> &Self::Target { + &self.0 } } @@ -37,7 +238,7 @@ impl WatchShared { #[pin_project::pin_project] pub struct Watch { - shared: Arc>>, + shared: Arc>>, version: u64, } impl Clone for Watch { @@ -51,7 +252,7 @@ impl Clone for Watch { impl Watch { pub fn new(init: T) -> Self { Self { - shared: Arc::new(SyncMutex::new(WatchShared { + shared: Arc::new(SyncRwLock::new(WatchShared { version: 1, data: init, wakers: Vec::new(), @@ -65,6 +266,7 @@ impl Watch { version: 0, } } + #[cfg_attr(feature = "unstable", inline(never))] pub fn poll_changed(&mut self, cx: &mut std::task::Context<'_>) -> Poll<()> { self.shared.mutate(|shared| { if shared.version != self.version { @@ -79,9 +281,11 @@ impl Watch { } }) } + #[cfg_attr(feature = "unstable", inline(never))] pub async fn changed(&mut self) { futures::future::poll_fn(|cx| self.poll_changed(cx)).await } + #[cfg_attr(feature = "unstable", inline(never))] pub async fn wait_for bool>(&mut self, mut f: F) { loop { if self.peek(&mut f) { @@ -90,6 +294,7 @@ impl Watch { self.changed().await; } } + #[cfg_attr(feature = "unstable", inline(never))] pub fn send_if_modified bool>(&self, modify: F) -> bool { self.shared.mutate(|shared| { let changed = modify(&mut shared.data); @@ -99,6 +304,7 @@ impl Watch { changed }) } + #[cfg_attr(feature = "unstable", inline(never))] pub fn send_modify U>(&self, modify: F) -> U { self.shared.mutate(|shared| { let res = modify(&mut shared.data); @@ -106,40 +312,52 @@ impl Watch { res }) } + #[cfg_attr(feature = "unstable", inline(never))] pub fn send_replace(&self, new: T) -> T { self.send_modify(|a| std::mem::replace(a, new)) } + #[cfg_attr(feature = "unstable", inline(never))] pub fn send(&self, new: T) { self.send_replace(new); } + #[cfg_attr(feature = "unstable", inline(never))] pub fn mark_changed(&self) { self.shared.mutate(|shared| shared.modified()) } pub fn mark_unseen(&mut self) { self.version = 0; } + #[cfg_attr(feature = "unstable", inline(never))] pub fn mark_seen(&mut self) { self.shared.peek(|shared| { self.version = shared.version; }) } + #[cfg_attr(feature = "unstable", inline(never))] pub fn peek U>(&self, f: F) -> U { self.shared.peek(|shared| f(&shared.data)) } + #[cfg_attr(feature = "unstable", inline(never))] pub fn peek_and_mark_seen U>(&mut self, f: F) -> U { self.shared.peek(|shared| { self.version = shared.version; f(&shared.data) }) } + #[cfg_attr(feature = "unstable", inline(never))] pub fn peek_mut U>(&self, f: F) -> U { self.shared.mutate(|shared| f(&mut shared.data)) } } impl Watch { + #[cfg_attr(feature = "unstable", inline(never))] pub fn read(&self) -> T { self.peek(|a| a.clone()) } + #[cfg_attr(feature = "unstable", inline(never))] + pub fn read_and_mark_seen(&mut self) -> T { + self.peek_and_mark_seen(|a| a.clone()) + } } impl futures::Stream for Watch { type Item = T; diff --git a/core/startos/src/version/mod.rs b/core/startos/src/version/mod.rs index d134b35fc..a63a51a6f 100644 --- a/core/startos/src/version/mod.rs +++ b/core/startos/src/version/mod.rs @@ -5,14 +5,14 @@ use std::panic::{RefUnwindSafe, UnwindSafe}; use color_eyre::eyre::eyre; use futures::future::BoxFuture; use futures::{Future, FutureExt}; -use imbl_value::{to_value, InternedString}; +use imbl_value::{InternedString, to_value}; use patch_db::json_ptr::ROOT; +use crate::Error; use crate::context::RpcContext; use crate::db::model::Database; use crate::prelude::*; use crate::progress::PhaseProgressTrackerHandle; -use crate::Error; mod v0_3_5; mod v0_3_5_1; @@ -49,7 +49,9 @@ mod v0_4_0_alpha_7; mod v0_4_0_alpha_8; mod v0_4_0_alpha_9; -pub type Current = v0_4_0_alpha_9::Version; // VERSION_BUMP +mod v0_4_0_alpha_10; + +pub type Current = v0_4_0_alpha_10::Version; // VERSION_BUMP impl Current { #[instrument(skip(self, db))] @@ -161,7 +163,8 @@ enum Version { V0_4_0_alpha_6(Wrapper), V0_4_0_alpha_7(Wrapper), V0_4_0_alpha_8(Wrapper), - V0_4_0_alpha_9(Wrapper), // VERSION_BUMP + V0_4_0_alpha_9(Wrapper), + V0_4_0_alpha_10(Wrapper), // VERSION_BUMP Other(exver::Version), } @@ -180,7 +183,7 @@ impl Version { return Err(Error::new( eyre!("cannot migrate from versions before 0.3.5"), ErrorKind::MigrationFailed, - )) + )); } Self::V0_3_5(v) => DynVersion(Box::new(v.0)), Self::V0_3_5_1(v) => DynVersion(Box::new(v.0)), @@ -213,12 +216,13 @@ impl Version { Self::V0_4_0_alpha_6(v) => DynVersion(Box::new(v.0)), Self::V0_4_0_alpha_7(v) => DynVersion(Box::new(v.0)), Self::V0_4_0_alpha_8(v) => DynVersion(Box::new(v.0)), - Self::V0_4_0_alpha_9(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP + Self::V0_4_0_alpha_9(v) => DynVersion(Box::new(v.0)), + Self::V0_4_0_alpha_10(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP Self::Other(v) => { return Err(Error::new( eyre!("unknown version {v}"), ErrorKind::MigrationFailed, - )) + )); } }) } @@ -257,7 +261,8 @@ impl Version { Version::V0_4_0_alpha_6(Wrapper(x)) => x.semver(), Version::V0_4_0_alpha_7(Wrapper(x)) => x.semver(), Version::V0_4_0_alpha_8(Wrapper(x)) => x.semver(), - Version::V0_4_0_alpha_9(Wrapper(x)) => x.semver(), // VERSION_BUMP + Version::V0_4_0_alpha_9(Wrapper(x)) => x.semver(), + Version::V0_4_0_alpha_10(Wrapper(x)) => x.semver(), // VERSION_BUMP Version::Other(x) => x.clone(), } } @@ -334,7 +339,7 @@ impl PreUps { from.semver() ), crate::ErrorKind::MigrationFailed, - )) + )); } Ordering::Equal => None, }; diff --git a/core/startos/src/version/update_details/v0_4_0.md b/core/startos/src/version/update_details/v0_4_0.md index b13c367db..e546704ee 100644 --- a/core/startos/src/version/update_details/v0_4_0.md +++ b/core/startos/src/version/update_details/v0_4_0.md @@ -16,25 +16,7 @@ v0.4.0 is a complete rewrite of StartOS, almost nothing survived. After nearly s ## Changelog -- [Improve user interface](#user-interface) -- [Add translations](#translations) -- [Switch to lxc-based container runtime](#lxc) -- [Update s9pk archive format](#s9pk-archive-format) -- [Improve Actions](#actions) -- [Use squashfs images for OS updates](#squashfs-updates) -- [Introduce Typescript package API and SDK](#typescript-sdk) -- [Remove Postgresql](#remove-postgressql) -- [Enable sending emails via SMTP](#smtp) -- [Support SSH password auth](#ssh-password-auth) -- [Allow managing Tor addresses](#tor-addresses) -- [Implement detailed progress reporting](#progress-reporting) -- [Improve registry protocol](#registry-protocol) -- [Replace unique .local URLs with unique ports](#lan-port-forwarding) -- [Use start-fs Fuse module for improved backups](#improved-backups) -- [Switch to Exver for versioning](#exver) -- [Add clearnet hosting](#clearnet) - -### User interface +### Improved User interface We re-wrote the StartOS UI to be more performant, more intuitive, and better looking on both mobile and desktop. Enjoy. @@ -42,31 +24,31 @@ We re-wrote the StartOS UI to be more performant, more intuitive, and better loo StartOS v0.4.0 supports multiple languages and also makes it easy to add more later on. -### LXC +### LXC Container Runtime -Replacing both Docker and Podman, StartOS v0.4.0 uses a nested container paradigm based on LXC for the outer container and linux namespaces for sub containers. This architecture naturally support multi container setups. +Neither Docker nor Podman offer the reliability and flexibility needed for StartOS. Instead, v0.4.0 uses a nested container paradigm based on LXC for the outer container and Linux namespaces for sub containers. This architecture naturally supports multi container setups. -### S9PK archive format +### New S9PK archive format The S9PK archive format has been overhauled to allow for signature verification of partial downloads, and allow direct mounting of container images without unpacking the s9pk. -### Actions +### Improved Actions -Actions take arbitrary form input and return arbitrary responses, thus satisfying the needs of both Config and Properties, which have been removed. The new actions API gives packages developers the ability to break up Config and Properties into smaller, more specific formats, or to exclude them entirely without polluting the UI. Improved form design and new input types round out the actions experience. +Actions take arbitrary form input and return arbitrary responses, thus satisfying the needs of both "Config" and "Properties", which have now been removed. The new actions API gives package developers the ability to break up Config and Properties into smaller, more specific formats, or to exclude them entirely without polluting the UI. Improved form design and new input types round out the new actions experience. -### Squashfs updates +### Squashfs Images for OS Updates -StartOS now uses squashfs images to represent OS updates. This allows for better update verification, and improved reliability over rsync updates. +StartOS now uses squashfs images instead of rsync for OS updates. This allows for better update verification and improved reliability. -### Typescript SDK +### Typescript Package API and SDK -Package developers can now take advantage of StartOS APIs using the new start-sdk, available in Typescript. A barebones StartOS package (s9pk) can be produced in minutes with minimal knowledge or skill. More advanced developers can use the SDK to create highly customized user experiences with their service. +Package developers can now take advantage of StartOS APIs using the new start-sdk, available in Typescript. A barebones StartOS package (s9pk) can be produced in minutes with minimal knowledge or skill. More advanced developers can use the SDK to create highly customized user experiences for their service. -### Remove PostgresSQL +### Removed PostgresSQL StartOS itself has miniscule data persistence needs. PostgresSQL was overkill and has been removed in favor of lightweight PatchDB. -### SMTP +### Sending Emails via SMTP You can now add your Gmail, SES, or other SMTP credentials to StartOS in order to send deliver email notifications from StartOS and from installed services that support SMTP. @@ -74,17 +56,17 @@ You can now add your Gmail, SES, or other SMTP credentials to StartOS in order t You can now SSH into your server using your master password. SSH public key authentication is still supported as well. -### Tor addresses +### Tor Address Management StartOS v0.4.0 supports adding and removing Tor addresses for StartOS and all service interfaces. You can even provide your own private key instead of using one auto-generated by StartOS. This has the added benefit of permitting vanity addresses. -### Progress reporting +### Progress Reporting A new progress reporting API enabled package developers to create unique phases and provide real-time progress reporting for actions such as installing, updating, or backing up a service. -### Registry protocol +### Registry Protocol -The new registry protocol bifurcates package indexing (listing/validating) and package hosting (downloading). Registries are now simple indexes of packages that reference binaries hosted in arbitrary locations, locally or externally. For example, when someone visits the Start9 Registry, the currated list of packages comes from Start9. But when someone installs a listed service, the package binary is being downloaded from Github. The registry also valides the binary. This makes it much easier to host a custom registry, since it is just a currated list of services tat reference package binaries hosted on Github or elsewhere. +The new registry protocol bifurcates package indexing (listing/validating) and package hosting (downloading). Registries are now simple indexes of packages that reference binaries hosted in arbitrary locations, locally or externally. For example, when someone visits the Start9 Registry, the curated list of packages comes from Start9. But when someone installs a listed service, the package binary is being downloaded from Github. The registry also validates the binary. This makes it much easier to host a custom registry, since it is just a curated list of services tat reference package binaries hosted on Github or elsewhere. ### LAN port forwarding @@ -96,12 +78,34 @@ The new start-fs fuse module unifies file system expectations for various platfo ### Exver -StartOS now uses Extended Versioning (Exver), which consists of three parts: (1) a Semver-compliant upstream version, (2) a Semver-compliant wrapper version, and (3) an optional "flavor" prefix. Flavors can be thought of as alternative implementations of services, where a user would only want one or the other installed, and data can feasibly be migrating between the two. Another common characteristic of flavors is that they satisfy the same API requirement of dependents, though this is not strictly necessary. A valid Exver looks something like this: `#knots:28.0.:1.0-beta.1`. This would translate to "the first beta release of StartOS wrapper version 1.0 of Bitcoin Knots version 27.0". +StartOS now uses Extended Versioning (Exver), which consists of three parts: (1) a Semver-compliant upstream version, (2) a Semver-compliant wrapper version, and (3) an optional "flavor" prefix. Flavors can be thought of as alternative implementations of services, where a user would only want one or the other installed, and data can feasibly be migrating between the two. Another common characteristic of flavors is that they satisfy the same API requirement of dependents, though this is not strictly necessary. A valid Exver looks something like this: `#knots:29.0:1.0-beta.1`. This would translate to "the first beta release of StartOS wrapper version 1.0 of Bitcoin Knots version 29.0". -### Clearnet +### ACME -It is now possible (and easy) to expose service interfaces to the public Internet on standard domains. There are two options, both of which are easy to accomplish: +StartOS now supports using ACME protocol to automatically obtain SSL/TLS certificates from widely trusted certificate authorities, such as Let's Encrypt, for your public domains. This means people visiting your public websites and APIs will not need to download and trust your server's Root CA. -1. Open ports on your router. This option is free and supported by all routers. The drawback is that your home IP address is revealed to anyone accessing an exposed interface. For example, hosting a blog in this way would reveal your home IP address, and therefore your approximate location, to readers. +### Gateways -2. Use a Wireguard VPN to proxy web traffic. This option requires provisioning a $5-$10/month VPS and running a one-line script. The result is the successful obfuscation of the users home IP address. +Gateways connect your server to the Internet. They process outbound traffic, and under certain conditions, they also permit inbound traffic. For example, your router is a gateway. It is now possible add gateways to StartOS, such as StartTunnel, in order to more granularly control how your installed services are exposed to the Internet. + +### Static DNS Servers + +By default, StartOS uses the DNS servers it receives via DHCP from its gateway(s). It is now possible to override these DNS servers with custom, static ones. + +### Internal DNS Server + +StartOS runs its own DNS server and automatically adds records for your private domains. You can update your router or other gateway to use StartOS DNS server in order to resolve these domains locally. + +### Private Domains + +A private domain is like to your server's .local, except it also works for VPN connectivity, and it can be _anything_. It can be a real domain you control, a made up domain, or even a domain controlled by someone else. + +Similar to your local domain, private domains can only be accessed when connected to the same LAN as your server, either physically or via VPN, and they require trusting your server's Root CA. + +### Public Domains (Clearnet) + +It is now easy to expose service interfaces to the public Internet on a public domain you control. There are two options, both of which are easy to accomplish: + +1. Open ports on your router. This option is free and supported by all routers. The drawback is that your home IP address is revealed to anyone accessing an exposed interface. + +2. Use a Wireguard reverse tunnel, such as [StartTunnel](#start-tunnel) to proxy web traffic. This option requires renting a $5-$10/month VPS and installing StartTunnel (or similar). The result is a new gateway, a virtual router in the cloud, that you can use to expose service interfaces instead of your real router, thereby hiding your IP address from visitors. diff --git a/core/startos/src/version/v0_3_5_1.rs b/core/startos/src/version/v0_3_5_1.rs index 808eae3bc..c228913b8 100644 --- a/core/startos/src/version/v0_3_5_1.rs +++ b/core/startos/src/version/v0_3_5_1.rs @@ -1,7 +1,7 @@ use exver::VersionRange; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_5, VersionT}; +use super::{VersionT, v0_3_5}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_5_2.rs b/core/startos/src/version/v0_3_5_2.rs index 9bbe38f1c..b2cbecf12 100644 --- a/core/startos/src/version/v0_3_5_2.rs +++ b/core/startos/src/version/v0_3_5_2.rs @@ -1,7 +1,7 @@ use exver::VersionRange; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_5_1, VersionT}; +use super::{VersionT, v0_3_5_1}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_6_alpha_0.rs b/core/startos/src/version/v0_3_6_alpha_0.rs index 80390432f..0edc98677 100644 --- a/core/startos/src/version/v0_3_6_alpha_0.rs +++ b/core/startos/src/version/v0_3_6_alpha_0.rs @@ -6,17 +6,16 @@ use chrono::{DateTime, Utc}; use const_format::formatcp; use ed25519_dalek::SigningKey; use exver::{PreReleaseSegment, VersionRange}; -use imbl_value::{json, InternedString}; +use imbl_value::{InternedString, json}; use models::{PackageId, ReplayId}; use openssl::pkey::PKey; use openssl::x509::X509; use sqlx::postgres::PgConnectOptions; use sqlx::{PgPool, Row}; use tokio::process::Command; -use torut::onion::TorSecretKeyV3; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_5_2, VersionT}; +use super::{VersionT, v0_3_5_2}; use crate::account::AccountInfo; use crate::auth::Sessions; use crate::backup::target::cifs::CifsTargets; @@ -26,13 +25,14 @@ use crate::disk::mount::util::unmount; use crate::hostname::Hostname; use crate::net::forward::AvailablePorts; use crate::net::keys::KeyStore; +use crate::net::tor::TorSecretKey; use crate::notifications::Notifications; use crate::prelude::*; use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; use crate::ssh::{SshKeys, SshPubKey}; +use crate::util::Invoke; use crate::util::crypto::ed25519_expand_key; use crate::util::serde::Pem; -use crate::util::Invoke; use crate::{DATA_DIR, PACKAGE_DATA}; lazy_static::lazy_static! { @@ -198,12 +198,9 @@ async fn init_postgres(datadir: impl AsRef) -> Result { .port(5433) .socket("/var/run/postgresql"), ) - .await? - }; - sqlx::migrate!() - .run(&secret_store) .await - .with_kind(crate::ErrorKind::Database)?; + .with_kind(ErrorKind::Database)? + }; Ok(secret_store) } @@ -262,10 +259,12 @@ impl VersionT for Version { let tor_address: String = from_value(db["server-info"]["tor-address"].clone())?; // Maybe we do this like the Public::init does server_info["torAddress"] = json!(tor_address); - server_info["onionAddress"] = json!(tor_address - .replace("https://", "") - .replace("http://", "") - .replace(".onion/", "")); + server_info["onionAddress"] = json!( + tor_address + .replace("https://", "") + .replace("http://", "") + .replace(".onion/", "") + ); server_info["networkInterfaces"] = json!({}); server_info["statusInfo"] = status_info; server_info["wifi"] = wifi; @@ -293,7 +292,8 @@ impl VersionT for Version { let mut value = json!({}); value["keyStore"] = to_value(&KeyStore::new(&account)?)?; value["password"] = to_value(&account.password)?; - value["compatS9pkKey"] = to_value(&crate::db::model::private::generate_compat_key())?; + value["compatS9pkKey"] = + to_value(&crate::db::model::private::generate_developer_key())?; value["sshPrivkey"] = to_value(Pem::new_ref(&account.ssh_key))?; value["sshPubkeys"] = to_value(&ssh_keys)?; value["availablePorts"] = to_value(&AvailablePorts::new())?; @@ -377,7 +377,7 @@ impl VersionT for Version { let package_s9pk = tokio::fs::File::open(path).await?; let file = MultiCursorFile::open(&package_s9pk).await?; - let key = ctx.db.peek().await.into_private().into_compat_s9pk_key(); + let key = ctx.db.peek().await.into_private().into_developer_key(); ctx.services .install( ctx.clone(), @@ -421,10 +421,11 @@ impl VersionT for Version { async fn previous_cifs(pg: &sqlx::Pool) -> Result { let cifs = sqlx::query(r#"SELECT * FROM cifs_shares"#) .fetch_all(pg) - .await? + .await + .with_kind(ErrorKind::Database)? .into_iter() .map(|row| { - let id: i32 = row.try_get("id")?; + let id: i32 = row.try_get("id").with_kind(ErrorKind::Database)?; Ok::<_, Error>(( id, Cifs { @@ -457,13 +458,14 @@ async fn previous_cifs(pg: &sqlx::Pool) -> Result) -> Result { let account_query = sqlx::query(r#"SELECT * FROM account"#) .fetch_one(pg) - .await?; + .await + .with_kind(ErrorKind::Database)?; let account = { AccountInfo { password: account_query .try_get("password") .with_ctx(|_| (ErrorKind::Database, "password"))?, - tor_keys: vec![TorSecretKeyV3::try_from( + tor_keys: vec![TorSecretKey::from_bytes( if let Some(bytes) = account_query .try_get::>, _>("tor_key") .with_ctx(|_| (ErrorKind::Database, "tor_key"))? @@ -478,14 +480,18 @@ async fn previous_account_info(pg: &sqlx::Pool) -> Result::try_from(account_query.try_get::, _>("network_key")?) - .map_err(|e| { - Error::new( - eyre!("expected vec of len 32, got len {}", e.len()), - ErrorKind::ParseDbField, - ) - }) - .with_ctx(|_| (ErrorKind::Database, "password.u8 32"))?, + &<[u8; 32]>::try_from( + account_query + .try_get::, _>("network_key") + .with_kind(ErrorKind::Database)?, + ) + .map_err(|e| { + Error::new( + eyre!("expected vec of len 32, got len {}", e.len()), + ErrorKind::ParseDbField, + ) + }) + .with_ctx(|_| (ErrorKind::Database, "password.u8 32"))?, ) }, )?], @@ -512,7 +518,7 @@ async fn previous_account_info(pg: &sqlx::Pool) -> Result) -> Result) -> Result { let ssh_query = sqlx::query(r#"SELECT * FROM ssh_keys"#) .fetch_all(pg) - .await?; + .await + .with_kind(ErrorKind::Database)?; let ssh_keys: SshKeys = { let keys = ssh_query.into_iter().fold( Ok::<_, Error>(BTreeMap::>::new()), @@ -534,12 +541,12 @@ async fn previous_ssh_keys(pg: &sqlx::Pool) -> Result("created_at") - .map_err(Error::from) + .with_kind(ErrorKind::Database) .and_then(|x| x.parse::>().with_kind(ErrorKind::Database)) .with_ctx(|_| (ErrorKind::Database, "openssh_pubkey::created_at"))?; let value: SshPubKey = row .try_get::("openssh_pubkey") - .map_err(Error::from) + .with_kind(ErrorKind::Database) .and_then(|x| x.parse().map(SshPubKey).with_kind(ErrorKind::Database)) .with_ctx(|_| (ErrorKind::Database, "openssh_pubkey"))?; let data = WithTimeData { diff --git a/core/startos/src/version/v0_3_6_alpha_1.rs b/core/startos/src/version/v0_3_6_alpha_1.rs index 2cec81140..aeca4ada6 100644 --- a/core/startos/src/version/v0_3_6_alpha_1.rs +++ b/core/startos/src/version/v0_3_6_alpha_1.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_0, VersionT}; +use super::{VersionT, v0_3_6_alpha_0}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_6_alpha_10.rs b/core/startos/src/version/v0_3_6_alpha_10.rs index 6b7f1d73e..655b0e1f3 100644 --- a/core/startos/src/version/v0_3_6_alpha_10.rs +++ b/core/startos/src/version/v0_3_6_alpha_10.rs @@ -2,12 +2,13 @@ use std::collections::{BTreeMap, BTreeSet}; use exver::{PreReleaseSegment, VersionRange}; use imbl_value::InternedString; +use models::GatewayId; use serde::{Deserialize, Serialize}; -use torut::onion::OnionAddressV3; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_9, VersionT}; -use crate::net::host::address::DomainConfig; +use super::{VersionT, v0_3_6_alpha_9}; +use crate::net::host::address::PublicDomainConfig; +use crate::net::tor::OnionAddress; use crate::prelude::*; lazy_static::lazy_static! { @@ -21,7 +22,7 @@ lazy_static::lazy_static! { #[serde(rename_all = "camelCase")] #[serde(tag = "kind")] enum HostAddress { - Onion { address: OnionAddressV3 }, + Onion { address: OnionAddress }, Domain { address: InternedString }, } @@ -72,9 +73,9 @@ impl VersionT for Version { } HostAddress::Domain { address } => { domains.insert( - address, - DomainConfig { - public: true, + address.clone(), + PublicDomainConfig { + gateway: GatewayId::from("lo"), acme: None, }, ); diff --git a/core/startos/src/version/v0_3_6_alpha_11.rs b/core/startos/src/version/v0_3_6_alpha_11.rs index 0d99cb5be..ce1bc6a03 100644 --- a/core/startos/src/version/v0_3_6_alpha_11.rs +++ b/core/startos/src/version/v0_3_6_alpha_11.rs @@ -2,7 +2,7 @@ use exver::{PreReleaseSegment, VersionRange}; use imbl_value::json; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_10, VersionT}; +use super::{VersionT, v0_3_6_alpha_10}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_6_alpha_12.rs b/core/startos/src/version/v0_3_6_alpha_12.rs index cf86500d1..e2cec380a 100644 --- a/core/startos/src/version/v0_3_6_alpha_12.rs +++ b/core/startos/src/version/v0_3_6_alpha_12.rs @@ -4,7 +4,7 @@ use exver::{PreReleaseSegment, VersionRange}; use imbl_value::json; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_11, VersionT}; +use super::{VersionT, v0_3_6_alpha_11}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_6_alpha_13.rs b/core/startos/src/version/v0_3_6_alpha_13.rs index 965b9c04d..54476f325 100644 --- a/core/startos/src/version/v0_3_6_alpha_13.rs +++ b/core/startos/src/version/v0_3_6_alpha_13.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_12, VersionT}; +use super::{VersionT, v0_3_6_alpha_12}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_6_alpha_14.rs b/core/startos/src/version/v0_3_6_alpha_14.rs index 4370239e6..22086e124 100644 --- a/core/startos/src/version/v0_3_6_alpha_14.rs +++ b/core/startos/src/version/v0_3_6_alpha_14.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_13, VersionT}; +use super::{VersionT, v0_3_6_alpha_13}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_6_alpha_15.rs b/core/startos/src/version/v0_3_6_alpha_15.rs index f7e050cce..a670a4ad0 100644 --- a/core/startos/src/version/v0_3_6_alpha_15.rs +++ b/core/startos/src/version/v0_3_6_alpha_15.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_14, VersionT}; +use super::{VersionT, v0_3_6_alpha_14}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_6_alpha_16.rs b/core/startos/src/version/v0_3_6_alpha_16.rs index 177fd01d2..22c54ff06 100644 --- a/core/startos/src/version/v0_3_6_alpha_16.rs +++ b/core/startos/src/version/v0_3_6_alpha_16.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_15, VersionT}; +use super::{VersionT, v0_3_6_alpha_15}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_6_alpha_17.rs b/core/startos/src/version/v0_3_6_alpha_17.rs index 92207b9e7..da361675e 100644 --- a/core/startos/src/version/v0_3_6_alpha_17.rs +++ b/core/startos/src/version/v0_3_6_alpha_17.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_16, VersionT}; +use super::{VersionT, v0_3_6_alpha_16}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_6_alpha_18.rs b/core/startos/src/version/v0_3_6_alpha_18.rs index b5d27eef7..4cfa8a61d 100644 --- a/core/startos/src/version/v0_3_6_alpha_18.rs +++ b/core/startos/src/version/v0_3_6_alpha_18.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_17, VersionT}; +use super::{VersionT, v0_3_6_alpha_17}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_6_alpha_2.rs b/core/startos/src/version/v0_3_6_alpha_2.rs index 7787f7e89..2788f84cf 100644 --- a/core/startos/src/version/v0_3_6_alpha_2.rs +++ b/core/startos/src/version/v0_3_6_alpha_2.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_1, VersionT}; +use super::{VersionT, v0_3_6_alpha_1}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_6_alpha_3.rs b/core/startos/src/version/v0_3_6_alpha_3.rs index b2284c909..aa7db98f7 100644 --- a/core/startos/src/version/v0_3_6_alpha_3.rs +++ b/core/startos/src/version/v0_3_6_alpha_3.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_2, VersionT}; +use super::{VersionT, v0_3_6_alpha_2}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_6_alpha_4.rs b/core/startos/src/version/v0_3_6_alpha_4.rs index 3890a25a3..c1c6cd3bf 100644 --- a/core/startos/src/version/v0_3_6_alpha_4.rs +++ b/core/startos/src/version/v0_3_6_alpha_4.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_3, VersionT}; +use super::{VersionT, v0_3_6_alpha_3}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_6_alpha_5.rs b/core/startos/src/version/v0_3_6_alpha_5.rs index b565abdca..9d707a425 100644 --- a/core/startos/src/version/v0_3_6_alpha_5.rs +++ b/core/startos/src/version/v0_3_6_alpha_5.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_4, VersionT}; +use super::{VersionT, v0_3_6_alpha_4}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_6_alpha_6.rs b/core/startos/src/version/v0_3_6_alpha_6.rs index 0a95a19a5..e3622e80a 100644 --- a/core/startos/src/version/v0_3_6_alpha_6.rs +++ b/core/startos/src/version/v0_3_6_alpha_6.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_5, VersionT}; +use super::{VersionT, v0_3_6_alpha_5}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_3_6_alpha_7.rs b/core/startos/src/version/v0_3_6_alpha_7.rs index 2f19dbb15..241404fd2 100644 --- a/core/startos/src/version/v0_3_6_alpha_7.rs +++ b/core/startos/src/version/v0_3_6_alpha_7.rs @@ -3,7 +3,7 @@ use imbl_value::json; use tokio::process::Command; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_6, VersionT}; +use super::{VersionT, v0_3_6_alpha_6}; use crate::context::RpcContext; use crate::prelude::*; use crate::util::Invoke; diff --git a/core/startos/src/version/v0_3_6_alpha_8.rs b/core/startos/src/version/v0_3_6_alpha_8.rs index b76cb6d6a..92f96b4c4 100644 --- a/core/startos/src/version/v0_3_6_alpha_8.rs +++ b/core/startos/src/version/v0_3_6_alpha_8.rs @@ -4,18 +4,18 @@ use exver::{PreReleaseSegment, VersionRange}; use tokio::fs::File; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_7, VersionT}; +use super::{VersionT, v0_3_6_alpha_7}; +use crate::DATA_DIR; use crate::context::RpcContext; use crate::install::PKG_ARCHIVE_DIR; use crate::prelude::*; -use crate::s9pk::manifest::{DeviceFilter, Manifest}; -use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; -use crate::s9pk::merkle_archive::MerkleArchive; -use crate::s9pk::v2::SIG_CONTEXT; use crate::s9pk::S9pk; +use crate::s9pk::manifest::{DeviceFilter, Manifest}; +use crate::s9pk::merkle_archive::MerkleArchive; +use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; +use crate::s9pk::v2::SIG_CONTEXT; use crate::service::LoadDisposition; use crate::util::io::create_file; -use crate::DATA_DIR; lazy_static::lazy_static! { static ref V0_3_6_alpha_8: exver::Version = exver::Version::new( @@ -115,7 +115,7 @@ impl VersionT for Version { let manifest: Manifest = from_value(manifest.clone())?; let id = manifest.id.clone(); let mut s9pk: S9pk<_> = S9pk::new_with_manifest(archive, None, manifest); - let s9pk_compat_key = ctx.account.read().await.compat_s9pk_key.clone(); + let s9pk_compat_key = ctx.account.read().await.developer_key.clone(); s9pk.as_archive_mut() .set_signer(s9pk_compat_key, SIG_CONTEXT); s9pk.serialize(&mut tmp_file, true).await?; diff --git a/core/startos/src/version/v0_3_6_alpha_9.rs b/core/startos/src/version/v0_3_6_alpha_9.rs index 4ccecd43c..a31b8fb86 100644 --- a/core/startos/src/version/v0_3_6_alpha_9.rs +++ b/core/startos/src/version/v0_3_6_alpha_9.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_8, VersionT}; +use super::{VersionT, v0_3_6_alpha_8}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_4_0_alpha_0.rs b/core/startos/src/version/v0_4_0_alpha_0.rs index d095fbd65..39cb530bf 100644 --- a/core/startos/src/version/v0_4_0_alpha_0.rs +++ b/core/startos/src/version/v0_4_0_alpha_0.rs @@ -2,9 +2,9 @@ use exver::{PreReleaseSegment, VersionRange}; use imbl_value::json; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_3_6_alpha_18, VersionT}; +use super::{VersionT, v0_3_6_alpha_18}; use crate::context::RpcContext; -use crate::notifications::{notify, NotificationLevel}; +use crate::notifications::{NotificationLevel, notify}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_4_0_alpha_1.rs b/core/startos/src/version/v0_4_0_alpha_1.rs index 3e17db8ac..a917b88a0 100644 --- a/core/startos/src/version/v0_4_0_alpha_1.rs +++ b/core/startos/src/version/v0_4_0_alpha_1.rs @@ -2,7 +2,7 @@ use exver::{PreReleaseSegment, VersionRange}; use imbl_value::json; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_4_0_alpha_0, VersionT}; +use super::{VersionT, v0_4_0_alpha_0}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_4_0_alpha_10.rs b/core/startos/src/version/v0_4_0_alpha_10.rs new file mode 100644 index 000000000..ece6fcefc --- /dev/null +++ b/core/startos/src/version/v0_4_0_alpha_10.rs @@ -0,0 +1,111 @@ +use std::collections::{BTreeMap, BTreeSet}; +use std::sync::Arc; + +use exver::{PreReleaseSegment, VersionRange}; +use imbl_value::json; + +use super::v0_3_5::V0_3_0_COMPAT; +use super::{VersionT, v0_4_0_alpha_9}; +use crate::prelude::*; + +lazy_static::lazy_static! { + static ref V0_4_0_alpha_10: exver::Version = exver::Version::new( + [0, 4, 0], + [PreReleaseSegment::String("alpha".into()), 10.into()] + ); +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct Version; + +impl VersionT for Version { + type Previous = v0_4_0_alpha_9::Version; + type PreUpRes = (); + + async fn pre_up(self) -> Result { + Ok(()) + } + fn semver(self) -> exver::Version { + V0_4_0_alpha_10.clone() + } + fn compat(self) -> &'static VersionRange { + &V0_3_0_COMPAT + } + #[instrument] + fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result { + let default_gateway = db["public"]["serverInfo"]["network"]["networkInterfaces"] + .as_object() + .into_iter() + .flatten() + .find(|(_, i)| i["ipInfo"]["wanIp"].is_string()) + .map(|(g, _)| g.clone()); + let fix_host = |host: &mut Value| { + let mut public = BTreeMap::new(); + let mut private = BTreeSet::new(); + for (domain, info) in host["domains"] + .as_object_mut() + .ok_or_else(|| { + Error::new( + eyre!("expected public.packageData[id].hosts[id].domains to be an object"), + ErrorKind::Database, + ) + })? + .iter_mut() + { + let Some(info) = info.as_object_mut() else { + continue; + }; + if info["public"].as_bool().unwrap_or_default() + && let Some(gateway) = &default_gateway + { + info.insert( + "gateway".into(), + Value::String(Arc::new((&**gateway).to_owned())), + ); + public.insert(domain.clone(), info.clone()); + } else { + private.insert(domain.clone()); + } + } + host["hostnameInfo"] = json!({}); + host["publicDomains"] = to_value(&public)?; + host["privateDomains"] = to_value(&private)?; + Ok::<_, Error>(()) + }; + for (_, package) in db["public"]["packageData"] + .as_object_mut() + .ok_or_else(|| { + Error::new( + eyre!("expected public.packageData to be an object"), + ErrorKind::Database, + ) + })? + .iter_mut() + { + for (_, host) in package["hosts"] + .as_object_mut() + .ok_or_else(|| { + Error::new( + eyre!("expected public.packageData[id].hosts to be an object"), + ErrorKind::Database, + ) + })? + .iter_mut() + { + fix_host(host)?; + } + } + fix_host(&mut db["public"]["serverInfo"]["network"]["host"])?; + let network = &mut db["public"]["serverInfo"]["network"]; + network["gateways"] = json!({}); + network["dns"] = json!({ + "dhcpServers": [], + }); + db["private"]["authPubkeys"] = json!([]); + + Ok(Value::Null) + } + fn down(self, _db: &mut Value) -> Result<(), Error> { + Ok(()) + } +} diff --git a/core/startos/src/version/v0_4_0_alpha_2.rs b/core/startos/src/version/v0_4_0_alpha_2.rs index 541cc2b20..f18866099 100644 --- a/core/startos/src/version/v0_4_0_alpha_2.rs +++ b/core/startos/src/version/v0_4_0_alpha_2.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_4_0_alpha_1, VersionT}; +use super::{VersionT, v0_4_0_alpha_1}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_4_0_alpha_3.rs b/core/startos/src/version/v0_4_0_alpha_3.rs index b5aaa188c..46ab9745d 100644 --- a/core/startos/src/version/v0_4_0_alpha_3.rs +++ b/core/startos/src/version/v0_4_0_alpha_3.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_4_0_alpha_2, VersionT}; +use super::{VersionT, v0_4_0_alpha_2}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_4_0_alpha_4.rs b/core/startos/src/version/v0_4_0_alpha_4.rs index 420e1a596..ffc83fe65 100644 --- a/core/startos/src/version/v0_4_0_alpha_4.rs +++ b/core/startos/src/version/v0_4_0_alpha_4.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_4_0_alpha_3, VersionT}; +use super::{VersionT, v0_4_0_alpha_3}; use crate::context::RpcContext; use crate::prelude::*; use crate::util::io::create_file_mod; diff --git a/core/startos/src/version/v0_4_0_alpha_5.rs b/core/startos/src/version/v0_4_0_alpha_5.rs index de7a49c58..4ff950e5e 100644 --- a/core/startos/src/version/v0_4_0_alpha_5.rs +++ b/core/startos/src/version/v0_4_0_alpha_5.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_4_0_alpha_4, VersionT}; +use super::{VersionT, v0_4_0_alpha_4}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_4_0_alpha_6.rs b/core/startos/src/version/v0_4_0_alpha_6.rs index ce208e6f4..d75aceb19 100644 --- a/core/startos/src/version/v0_4_0_alpha_6.rs +++ b/core/startos/src/version/v0_4_0_alpha_6.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_4_0_alpha_5, VersionT}; +use super::{VersionT, v0_4_0_alpha_5}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_4_0_alpha_7.rs b/core/startos/src/version/v0_4_0_alpha_7.rs index 851073362..bd8a226e1 100644 --- a/core/startos/src/version/v0_4_0_alpha_7.rs +++ b/core/startos/src/version/v0_4_0_alpha_7.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_4_0_alpha_6, VersionT}; +use super::{VersionT, v0_4_0_alpha_6}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_4_0_alpha_8.rs b/core/startos/src/version/v0_4_0_alpha_8.rs index d03dc357d..fa2c87a0a 100644 --- a/core/startos/src/version/v0_4_0_alpha_8.rs +++ b/core/startos/src/version/v0_4_0_alpha_8.rs @@ -1,7 +1,7 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_4_0_alpha_7, VersionT}; +use super::{VersionT, v0_4_0_alpha_7}; use crate::prelude::*; lazy_static::lazy_static! { diff --git a/core/startos/src/version/v0_4_0_alpha_9.rs b/core/startos/src/version/v0_4_0_alpha_9.rs index 6a3499d28..ffb5ad5be 100644 --- a/core/startos/src/version/v0_4_0_alpha_9.rs +++ b/core/startos/src/version/v0_4_0_alpha_9.rs @@ -7,13 +7,13 @@ use imbl_value::{InOMap, InternedString}; use models::PackageId; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_4_0_alpha_8, VersionT}; +use super::{VersionT, v0_4_0_alpha_8}; +use crate::DATA_DIR; use crate::context::RpcContext; use crate::install::PKG_ARCHIVE_DIR; use crate::prelude::*; use crate::util::io::write_file_atomic; use crate::volume::PKG_VOLUME_DIR; -use crate::DATA_DIR; lazy_static::lazy_static! { static ref V0_4_0_alpha_9: exver::Version = exver::Version::new( diff --git a/debian/postinst b/debian/postinst index 9124f03bc..a8d4ee729 100755 --- a/debian/postinst +++ b/debian/postinst @@ -58,17 +58,12 @@ EOF rm -f /etc/localtime ln -s /usr/share/zoneinfo/Etc/UTC /etc/localtime -# switch to systemd-resolved & network-manager +rm /etc/resolv.conf +echo "nameserver 127.0.0.1" > /etc/resolv.conf +echo "nameserver 1.1.1.1" >> /etc/resolv.conf # Cloudflare DNS Fallback + +# switch to network-manager echo "#" > /etc/network/interfaces -if ! [ -f /etc/resolv.conf ]; then - rm -f /etc/resolv.conf - echo "nameserver 1.1.1.1" > /etc/resolv.conf # Cloudflare DNS Fallback -fi -if ! [ -f /run/systemd/resolve/stub-resolv.conf ]; then - mkdir -p /run/systemd/resolve - cp /etc/resolv.conf /run/systemd/resolve/stub-resolv.conf -fi -ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf cat << EOF > /etc/NetworkManager/NetworkManager.conf [main] plugins=ifupdown,keyfile @@ -83,7 +78,6 @@ $SYSTEMCTL enable ssh.service $SYSTEMCTL disable wpa_supplicant.service $SYSTEMCTL mask systemd-networkd-wait-online.service # currently use `NetworkManager-wait-online.service` -$SYSTEMCTL disable docker.service $SYSTEMCTL disable postgresql.service $SYSTEMCTL disable tor.service $SYSTEMCTL disable bluetooth.service @@ -111,6 +105,9 @@ sed -i '/^\s*#\?\s*issue_discards\s*=\s*/c\issue_discards = 1' /etc/lvm/lvm.conf sed -i '/\(^\|#\)\s*unqualified-search-registries\s*=\s*/c\unqualified-search-registries = ["docker.io"]' /etc/containers/registries.conf sed -i 's/\(#\|\^\)\s*\([^=]\+\)=\(suspend\|hibernate\)\s*$/\2=ignore/g' /etc/systemd/logind.conf sed -i '/\(^\|#\)MulticastDNS=/c\MulticastDNS=no' /etc/systemd/resolved.conf +sed -i '/\(^\|#\)DNSStubListener=/c\DNSStubListener=no' /etc/systemd/resolved.conf +sed -i '/\(^\|#\)LXC_DHCP_CONFILE=/c\LXC_DHCP_CONFILE=/etc/dnsmasq.conf' /etc/default/lxc-net +echo 'port=0' > /etc/dnsmasq.conf sed -i 's/\[Service\]/[Service]\nEnvironment=SYSTEMD_LOG_LEVEL=debug/' /lib/systemd/system/systemd-timesyncd.service sed -i "s/\.debian\./\./g;s/#FallbackNTP=/FallbackNTP=/" /etc/systemd/timesyncd.conf sed -i '/\(^\|#\)RootDistanceMaxSec=/c\RootDistanceMaxSec=10' /etc/systemd/timesyncd.conf diff --git a/image-recipe/build.sh b/image-recipe/build.sh index 3d97f7e5c..2a69ab712 100755 --- a/image-recipe/build.sh +++ b/image-recipe/build.sh @@ -161,12 +161,6 @@ if [ "${IB_TARGET_PLATFORM}" = "rockchip64" ]; then echo "deb https://apt.armbian.com/ ${IB_SUITE} main" > config/archives/armbian.list fi -curl -fsSL https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc > config/archives/tor.key -echo "deb [arch=${IB_TARGET_ARCH} signed-by=/etc/apt/trusted.gpg.d/tor.key.gpg] https://deb.torproject.org/torproject.org ${IB_SUITE} main" > config/archives/tor.list - -curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o config/archives/docker.key -echo "deb [arch=${IB_TARGET_ARCH} signed-by=/etc/apt/trusted.gpg.d/docker.key.gpg] https://download.docker.com/linux/debian ${IB_SUITE} stable" > config/archives/docker.list - # Dependencies ## Base dependencies diff --git a/patch-db b/patch-db index f43ee1958..5b23a1eac 160000 --- a/patch-db +++ b/patch-db @@ -1 +1 @@ -Subproject commit f43ee195877364fb8aa34bb769515855ab995933 +Subproject commit 5b23a1eac6439fff930c9b0eafae7680339dcd4b diff --git a/sdk/base/lib/Effects.ts b/sdk/base/lib/Effects.ts index 91231f19c..24aafd310 100644 --- a/sdk/base/lib/Effects.ts +++ b/sdk/base/lib/Effects.ts @@ -27,6 +27,7 @@ import { /** Used to reach out from the pure js runtime */ export type Effects = { + readonly eventId: string | null child: (name: string) => Effects constRetry?: () => void isInContext: boolean diff --git a/sdk/base/lib/actions/input/builder/value.ts b/sdk/base/lib/actions/input/builder/value.ts index 5bfaadf0b..7e13b6cce 100644 --- a/sdk/base/lib/actions/input/builder/value.ts +++ b/sdk/base/lib/actions/input/builder/value.ts @@ -1,8 +1,7 @@ -import { ExtractInputSpecType, InputSpec, LazyBuild } from "./inputSpec" +import { InputSpec, LazyBuild } from "./inputSpec" import { List } from "./list" import { UnionRes, UnionResStaticValidatedAs, Variants } from "./variants" import { - FilePath, Pattern, RandomString, ValueSpec, @@ -27,6 +26,12 @@ import { } from "ts-matches" import { DeepPartial } from "../../../types" +export const fileInfoParser = object({ + path: string, + commitment: object({ hash: string, size: number }), +}) +export type FileInfo = typeof fileInfoParser._TYPE + type AsRequired = Required extends true ? T : T | null @@ -891,47 +896,54 @@ export class Value { } }, spec.validator) } - // static file(a: { - // name: string - // description?: string | null - // extensions: string[] - // required: Required - // }) { - // const buildValue = { - // type: "file" as const, - // description: null, - // warning: null, - // ...a, - // } - // return new Value, Store>( - // () => ({ - // ...buildValue, - // }), - // asRequiredParser(object({ filePath: string }), a), - // ) - // } - // static dynamicFile( - // a: LazyBuild< - // Store, - // { - // name: string - // description?: string | null - // warning?: string | null - // extensions: string[] - // required: boolean - // } - // >, - // ) { - // return new Value( - // async (options) => ({ - // type: "file" as const, - // description: null, - // warning: null, - // ...(await a(options)), - // }), - // object({ filePath: string }).nullable(), - // ) - // } + static file(a: { + name: string + description?: string | null + warning?: string | null + extensions: string[] + required: Required + }) { + const buildValue = { + type: "file" as const, + description: null, + warning: null, + ...a, + } + return new Value>( + () => ({ + spec: { + ...buildValue, + }, + validator: asRequiredParser(fileInfoParser, a), + }), + asRequiredParser(fileInfoParser, a), + ) + } + static dynamicFile( + a: LazyBuild<{ + name: string + description?: string | null + warning?: string | null + extensions: string[] + required: Required + }>, + ) { + return new Value, FileInfo | null>( + async (options) => { + const spec = { + type: "file" as const, + description: null, + warning: null, + ...(await a(options)), + } + return { + spec, + validator: asRequiredParser(fileInfoParser, spec), + } + }, + fileInfoParser.nullable(), + ) + } /** * @description Displays a dropdown, allowing for a single selection. Depending on the selection, a different object ("sub form") is presented. * @example diff --git a/sdk/base/lib/actions/input/inputSpecTypes.ts b/sdk/base/lib/actions/input/inputSpecTypes.ts index 362a56ea1..3e2dfabb1 100644 --- a/sdk/base/lib/actions/input/inputSpecTypes.ts +++ b/sdk/base/lib/actions/input/inputSpecTypes.ts @@ -66,9 +66,6 @@ export type ValueSpecTextarea = { immutable: boolean } -export type FilePath = { - filePath: string -} export type ValueSpecNumber = { type: "number" min: number | null diff --git a/sdk/base/lib/actions/setupActions.ts b/sdk/base/lib/actions/setupActions.ts index 91f93f688..26a72a117 100644 --- a/sdk/base/lib/actions/setupActions.ts +++ b/sdk/base/lib/actions/setupActions.ts @@ -5,9 +5,11 @@ import { once } from "../util" import { InitScript } from "../inits" import { Parser } from "ts-matches" +type MaybeInputSpec = {} extends Type ? null : InputSpec export type Run> = (options: { effects: T.Effects input: A + spec: T.inputSpecTypes.InputSpec }) => Promise<(T.ActionResult & { version: "1" }) | null | void | undefined> export type GetInput> = (options: { effects: T.Effects @@ -47,11 +49,14 @@ export class Action> implements ActionInfo { readonly _INPUT: Type = null as any as Type - private cachedParser?: Parser + private prevInputSpec: Record< + string, + { spec: T.inputSpecTypes.InputSpec; validator: Parser } + > = {} private constructor( readonly id: Id, private readonly metadataFn: MaybeFn, - private readonly inputSpec: InputSpec, + private readonly inputSpec: MaybeInputSpec, private readonly getInputFn: GetInput, private readonly runFn: Run, ) {} @@ -81,7 +86,7 @@ export class Action> return new Action( id, mapMaybeFn(metadata, (m) => ({ ...m, hasInput: false })), - InputSpec.of({}), + null, async () => null, run, ) @@ -100,10 +105,15 @@ export class Action> return metadata } async getInput(options: { effects: T.Effects }): Promise { - const built = await this.inputSpec.build(options) - this.cachedParser = built.validator + let spec = {} + if (this.inputSpec) { + const built = await this.inputSpec.build(options) + this.prevInputSpec[options.effects.eventId!] = built + spec = built.spec + } return { - spec: built.spec, + eventId: options.effects.eventId!, + spec, value: ((await this.getInputFn(options)) as | Record @@ -115,15 +125,23 @@ export class Action> effects: T.Effects input: Type }): Promise { - const parser = - this.cachedParser ?? (await this.inputSpec.build(options)).validator + let spec = {} + if (this.inputSpec) { + const prevInputSpec = this.prevInputSpec[options.effects.eventId!] + if (!prevInputSpec) { + throw new Error( + `getActionInput has not been called for EventID ${options.effects.eventId}`, + ) + } + options.input = prevInputSpec.validator.unsafeCast(options.input) + spec = prevInputSpec.spec + } return ( (await this.runFn({ effects: options.effects, - input: this.cachedParser - ? this.cachedParser.unsafeCast(options.input) - : options.input, - })) || null + input: options.input, + spec, + })) ?? null ) } } diff --git a/sdk/base/lib/dependencies/dependencies.ts b/sdk/base/lib/dependencies/dependencies.ts index 7002f54de..23bd1f1c8 100644 --- a/sdk/base/lib/dependencies/dependencies.ts +++ b/sdk/base/lib/dependencies/dependencies.ts @@ -77,7 +77,7 @@ export async function checkDependencies< } const tasksSatisfied = (packageId: DependencyId) => Object.entries(infoFor(packageId).result.tasks).filter( - ([_, t]) => t.active && t.task.severity === "critical", + ([_, t]) => t?.active && t.task.severity === "critical", ).length === 0 const healthCheckSatisfied = ( packageId: DependencyId, @@ -146,7 +146,7 @@ export async function checkDependencies< const throwIfTasksNotSatisfied = (packageId: DependencyId) => { const dep = infoFor(packageId) const reqs = Object.entries(dep.result.tasks) - .filter(([_, t]) => t.active && t.task.severity === "critical") + .filter(([_, t]) => t?.active && t.task.severity === "critical") .map(([id, _]) => id) if (reqs.length) { throw new Error( diff --git a/sdk/base/lib/dependencies/setupDependencies.ts b/sdk/base/lib/dependencies/setupDependencies.ts index 321dc1981..85b88fa66 100644 --- a/sdk/base/lib/dependencies/setupDependencies.ts +++ b/sdk/base/lib/dependencies/setupDependencies.ts @@ -2,7 +2,10 @@ import * as T from "../types" import { once } from "../util" export type RequiredDependenciesOf = { - [K in keyof Manifest["dependencies"]]: Manifest["dependencies"][K]["optional"] extends false + [K in keyof Manifest["dependencies"]]: Exclude< + Manifest["dependencies"][K], + undefined + >["optional"] extends false ? K : never }[keyof Manifest["dependencies"]] diff --git a/sdk/base/lib/osBindings/ActionInput.ts b/sdk/base/lib/osBindings/ActionInput.ts index a19a5f1a4..c81ce7547 100644 --- a/sdk/base/lib/osBindings/ActionInput.ts +++ b/sdk/base/lib/osBindings/ActionInput.ts @@ -1,6 +1,8 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Guid } from "./Guid" export type ActionInput = { + eventId: Guid spec: Record value: Record | null } diff --git a/sdk/base/lib/osBindings/ForgetInterfaceParams.ts b/sdk/base/lib/osBindings/AddTunnelParams.ts similarity index 57% rename from sdk/base/lib/osBindings/ForgetInterfaceParams.ts rename to sdk/base/lib/osBindings/AddTunnelParams.ts index b3532602c..018b22bb5 100644 --- a/sdk/base/lib/osBindings/ForgetInterfaceParams.ts +++ b/sdk/base/lib/osBindings/AddTunnelParams.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type ForgetInterfaceParams = { interface: string } +export type AddTunnelParams = { name: string; config: string; public: boolean } diff --git a/sdk/base/lib/osBindings/BindingGatewaySetEnabledParams.ts b/sdk/base/lib/osBindings/BindingGatewaySetEnabledParams.ts new file mode 100644 index 000000000..f38b5d095 --- /dev/null +++ b/sdk/base/lib/osBindings/BindingGatewaySetEnabledParams.ts @@ -0,0 +1,8 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { GatewayId } from "./GatewayId" + +export type BindingGatewaySetEnabledParams = { + internalPort: number + gateway: GatewayId + enabled: boolean | null +} diff --git a/sdk/base/lib/osBindings/DepInfo.ts b/sdk/base/lib/osBindings/DepInfo.ts index d635cca3b..47e2c9b2e 100644 --- a/sdk/base/lib/osBindings/DepInfo.ts +++ b/sdk/base/lib/osBindings/DepInfo.ts @@ -1,8 +1,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { PathOrUrl } from "./PathOrUrl" +import type { MetadataSrc } from "./MetadataSrc" export type DepInfo = { description: string | null optional: boolean - s9pk: PathOrUrl | null -} +} & MetadataSrc diff --git a/sdk/base/lib/osBindings/NetworkInterfaceSetInboundParams.ts b/sdk/base/lib/osBindings/DnsSettings.ts similarity index 54% rename from sdk/base/lib/osBindings/NetworkInterfaceSetInboundParams.ts rename to sdk/base/lib/osBindings/DnsSettings.ts index 30a5804bf..37412159f 100644 --- a/sdk/base/lib/osBindings/NetworkInterfaceSetInboundParams.ts +++ b/sdk/base/lib/osBindings/DnsSettings.ts @@ -1,6 +1,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type NetworkInterfaceSetInboundParams = { - interface: string - inbound: boolean | null +export type DnsSettings = { + dhcpServers: Array + staticServers: Array | null } diff --git a/sdk/base/lib/osBindings/DomainSettings.ts b/sdk/base/lib/osBindings/DomainSettings.ts new file mode 100644 index 000000000..276135926 --- /dev/null +++ b/sdk/base/lib/osBindings/DomainSettings.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { GatewayId } from "./GatewayId" + +export type DomainSettings = { gateway: GatewayId } diff --git a/sdk/base/lib/osBindings/ProcedureId.ts b/sdk/base/lib/osBindings/EventId.ts similarity index 75% rename from sdk/base/lib/osBindings/ProcedureId.ts rename to sdk/base/lib/osBindings/EventId.ts index 4d9a0debd..4449fb8a3 100644 --- a/sdk/base/lib/osBindings/ProcedureId.ts +++ b/sdk/base/lib/osBindings/EventId.ts @@ -1,4 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { Guid } from "./Guid" -export type ProcedureId = { procedureId: Guid } +export type EventId = { eventId: Guid } diff --git a/sdk/base/lib/osBindings/ForgetGatewayParams.ts b/sdk/base/lib/osBindings/ForgetGatewayParams.ts new file mode 100644 index 000000000..8d43c3a3b --- /dev/null +++ b/sdk/base/lib/osBindings/ForgetGatewayParams.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { GatewayId } from "./GatewayId" + +export type ForgetGatewayParams = { gateway: GatewayId } diff --git a/sdk/base/lib/osBindings/UnsetInboundParams.ts b/sdk/base/lib/osBindings/GatewayId.ts similarity index 66% rename from sdk/base/lib/osBindings/UnsetInboundParams.ts rename to sdk/base/lib/osBindings/GatewayId.ts index df519e752..1b0cc9b38 100644 --- a/sdk/base/lib/osBindings/UnsetInboundParams.ts +++ b/sdk/base/lib/osBindings/GatewayId.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type UnsetInboundParams = { interface: string } +export type GatewayId = string diff --git a/sdk/base/lib/osBindings/Host.ts b/sdk/base/lib/osBindings/Host.ts index 041e1c9bc..33352f9d6 100644 --- a/sdk/base/lib/osBindings/Host.ts +++ b/sdk/base/lib/osBindings/Host.ts @@ -1,12 +1,13 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { BindInfo } from "./BindInfo" -import type { DomainConfig } from "./DomainConfig" import type { HostnameInfo } from "./HostnameInfo" +import type { PublicDomainConfig } from "./PublicDomainConfig" export type Host = { bindings: { [key: number]: BindInfo } onions: string[] - domains: { [key: string]: DomainConfig } + publicDomains: { [key: string]: PublicDomainConfig } + privateDomains: Array /** * COMPUTED: NetService::update */ diff --git a/sdk/base/lib/osBindings/HostAddress.ts b/sdk/base/lib/osBindings/HostAddress.ts deleted file mode 100644 index fe16c89d7..000000000 --- a/sdk/base/lib/osBindings/HostAddress.ts +++ /dev/null @@ -1,11 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AcmeProvider } from "./AcmeProvider" - -export type HostAddress = - | { kind: "onion"; address: string } - | { - kind: "domain" - address: string - public: boolean - acme: AcmeProvider | null - } diff --git a/sdk/base/lib/osBindings/HostnameInfo.ts b/sdk/base/lib/osBindings/HostnameInfo.ts index ef8bafac0..7819eec54 100644 --- a/sdk/base/lib/osBindings/HostnameInfo.ts +++ b/sdk/base/lib/osBindings/HostnameInfo.ts @@ -3,10 +3,5 @@ import type { IpHostname } from "./IpHostname" import type { OnionHostname } from "./OnionHostname" export type HostnameInfo = - | { - kind: "ip" - networkInterfaceId: string - public: boolean - hostname: IpHostname - } + | { kind: "ip"; gatewayId: string; public: boolean; hostname: IpHostname } | { kind: "onion"; hostname: OnionHostname } diff --git a/sdk/base/lib/osBindings/IpHostname.ts b/sdk/base/lib/osBindings/IpHostname.ts index 9b3ddd6d1..0fb7c3f3c 100644 --- a/sdk/base/lib/osBindings/IpHostname.ts +++ b/sdk/base/lib/osBindings/IpHostname.ts @@ -17,8 +17,7 @@ export type IpHostname = } | { kind: "domain" - domain: string - subdomain: string | null + value: string port: number | null sslPort: number | null } diff --git a/sdk/base/lib/osBindings/IpInfo.ts b/sdk/base/lib/osBindings/IpInfo.ts index 28d052048..4fc21f163 100644 --- a/sdk/base/lib/osBindings/IpInfo.ts +++ b/sdk/base/lib/osBindings/IpInfo.ts @@ -6,6 +6,8 @@ export type IpInfo = { scopeId: number deviceType: NetworkInterfaceType | null subnets: string[] + lanIp: string[] wanIp: string | null ntpServers: string[] + dnsServers: string[] } diff --git a/sdk/base/lib/osBindings/LoginParams.ts b/sdk/base/lib/osBindings/LoginParams.ts index a569e6c8e..2a907ad6f 100644 --- a/sdk/base/lib/osBindings/LoginParams.ts +++ b/sdk/base/lib/osBindings/LoginParams.ts @@ -1,4 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { PasswordType } from "./PasswordType" -export type LoginParams = { password: PasswordType | null; ephemeral: boolean } +export type LoginParams = { password: string; ephemeral: boolean } diff --git a/sdk/base/lib/osBindings/Metadata.ts b/sdk/base/lib/osBindings/Metadata.ts new file mode 100644 index 000000000..0ea43923e --- /dev/null +++ b/sdk/base/lib/osBindings/Metadata.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { PathOrUrl } from "./PathOrUrl" + +export type Metadata = { title: string; icon: PathOrUrl } diff --git a/sdk/base/lib/osBindings/MetadataSrc.ts b/sdk/base/lib/osBindings/MetadataSrc.ts new file mode 100644 index 000000000..7a386636d --- /dev/null +++ b/sdk/base/lib/osBindings/MetadataSrc.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Metadata } from "./Metadata" +import type { PathOrUrl } from "./PathOrUrl" + +export type MetadataSrc = { metadata: Metadata } | { s9pk: PathOrUrl | null } diff --git a/sdk/base/lib/osBindings/NetInfo.ts b/sdk/base/lib/osBindings/NetInfo.ts index e790cadaa..6ac8c4204 100644 --- a/sdk/base/lib/osBindings/NetInfo.ts +++ b/sdk/base/lib/osBindings/NetInfo.ts @@ -1,7 +1,9 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { GatewayId } from "./GatewayId" export type NetInfo = { - public: boolean + privateDisabled: Array + publicEnabled: Array assignedPort: number | null assignedSslPort: number | null } diff --git a/sdk/base/lib/osBindings/NetworkInfo.ts b/sdk/base/lib/osBindings/NetworkInfo.ts index 1933332e4..514e2931f 100644 --- a/sdk/base/lib/osBindings/NetworkInfo.ts +++ b/sdk/base/lib/osBindings/NetworkInfo.ts @@ -1,6 +1,8 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { AcmeProvider } from "./AcmeProvider" import type { AcmeSettings } from "./AcmeSettings" +import type { DnsSettings } from "./DnsSettings" +import type { GatewayId } from "./GatewayId" import type { Host } from "./Host" import type { NetworkInterfaceInfo } from "./NetworkInterfaceInfo" import type { WifiInfo } from "./WifiInfo" @@ -8,6 +10,7 @@ import type { WifiInfo } from "./WifiInfo" export type NetworkInfo = { wifi: WifiInfo host: Host - networkInterfaces: { [key: string]: NetworkInterfaceInfo } + gateways: { [key: GatewayId]: NetworkInterfaceInfo } acme: { [key: AcmeProvider]: AcmeSettings } + dns: DnsSettings } diff --git a/sdk/base/lib/osBindings/NetworkInterfaceInfo.ts b/sdk/base/lib/osBindings/NetworkInterfaceInfo.ts index 324b3df78..8c3f5dca5 100644 --- a/sdk/base/lib/osBindings/NetworkInterfaceInfo.ts +++ b/sdk/base/lib/osBindings/NetworkInterfaceInfo.ts @@ -2,7 +2,8 @@ import type { IpInfo } from "./IpInfo" export type NetworkInterfaceInfo = { - inbound: boolean | null - outbound: boolean | null + name: string | null + public: boolean | null + secure: boolean | null ipInfo: IpInfo | null } diff --git a/sdk/base/lib/osBindings/BindingSetPublicParams.ts b/sdk/base/lib/osBindings/NetworkInterfaceSetPublicParams.ts similarity index 54% rename from sdk/base/lib/osBindings/BindingSetPublicParams.ts rename to sdk/base/lib/osBindings/NetworkInterfaceSetPublicParams.ts index 077cf8510..c81719031 100644 --- a/sdk/base/lib/osBindings/BindingSetPublicParams.ts +++ b/sdk/base/lib/osBindings/NetworkInterfaceSetPublicParams.ts @@ -1,6 +1,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { GatewayId } from "./GatewayId" -export type BindingSetPublicParams = { - internalPort: number +export type NetworkInterfaceSetPublicParams = { + gateway: GatewayId public: boolean | null } diff --git a/sdk/base/lib/osBindings/PackageDataEntry.ts b/sdk/base/lib/osBindings/PackageDataEntry.ts index 52e55dd75..cb65500a2 100644 --- a/sdk/base/lib/osBindings/PackageDataEntry.ts +++ b/sdk/base/lib/osBindings/PackageDataEntry.ts @@ -6,6 +6,7 @@ import type { DataUrl } from "./DataUrl" import type { Hosts } from "./Hosts" import type { MainStatus } from "./MainStatus" import type { PackageState } from "./PackageState" +import type { ReplayId } from "./ReplayId" import type { ServiceInterface } from "./ServiceInterface" import type { ServiceInterfaceId } from "./ServiceInterfaceId" import type { TaskEntry } from "./TaskEntry" @@ -20,7 +21,7 @@ export type PackageDataEntry = { lastBackup: string | null currentDependencies: CurrentDependencies actions: { [key: ActionId]: ActionMetadata } - tasks: { [key: string]: TaskEntry } + tasks: { [key: ReplayId]: TaskEntry } serviceInterfaces: { [key: ServiceInterfaceId]: ServiceInterface } hosts: Hosts storeExposedDependents: string[] diff --git a/sdk/base/lib/osBindings/DomainConfig.ts b/sdk/base/lib/osBindings/PublicDomainConfig.ts similarity index 55% rename from sdk/base/lib/osBindings/DomainConfig.ts rename to sdk/base/lib/osBindings/PublicDomainConfig.ts index 433bc65f5..d4058eccd 100644 --- a/sdk/base/lib/osBindings/DomainConfig.ts +++ b/sdk/base/lib/osBindings/PublicDomainConfig.ts @@ -1,4 +1,8 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { AcmeProvider } from "./AcmeProvider" +import type { GatewayId } from "./GatewayId" -export type DomainConfig = { public: boolean; acme: AcmeProvider | null } +export type PublicDomainConfig = { + gateway: GatewayId + acme: AcmeProvider | null +} diff --git a/sdk/base/lib/osBindings/RemoveTunnelParams.ts b/sdk/base/lib/osBindings/RemoveTunnelParams.ts new file mode 100644 index 000000000..67c7b08c5 --- /dev/null +++ b/sdk/base/lib/osBindings/RemoveTunnelParams.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { GatewayId } from "./GatewayId" + +export type RemoveTunnelParams = { id: GatewayId } diff --git a/sdk/base/lib/osBindings/RenameGatewayParams.ts b/sdk/base/lib/osBindings/RenameGatewayParams.ts new file mode 100644 index 000000000..5c282e683 --- /dev/null +++ b/sdk/base/lib/osBindings/RenameGatewayParams.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { GatewayId } from "./GatewayId" + +export type RenameGatewayParams = { id: GatewayId; name: string } diff --git a/sdk/base/lib/osBindings/Sessions.ts b/sdk/base/lib/osBindings/Sessions.ts index 0f43f1d01..6a15449e2 100644 --- a/sdk/base/lib/osBindings/Sessions.ts +++ b/sdk/base/lib/osBindings/Sessions.ts @@ -1,9 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Session } from "./Session" -export type Sessions = { - [key: string]: { - loggedIn: string - lastActive: string - userAgent: string | null - } -} +export type Sessions = { [key: string]: Session } diff --git a/sdk/base/lib/osBindings/UnsetPublicParams.ts b/sdk/base/lib/osBindings/UnsetPublicParams.ts new file mode 100644 index 000000000..1e0673fd4 --- /dev/null +++ b/sdk/base/lib/osBindings/UnsetPublicParams.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { GatewayId } from "./GatewayId" + +export type UnsetPublicParams = { gateway: GatewayId } diff --git a/sdk/base/lib/osBindings/index.ts b/sdk/base/lib/osBindings/index.ts index e6e3a7bff..724895888 100644 --- a/sdk/base/lib/osBindings/index.ts +++ b/sdk/base/lib/osBindings/index.ts @@ -17,6 +17,7 @@ export { AddPackageParams } from "./AddPackageParams" export { AddPackageToCategoryParams } from "./AddPackageToCategoryParams" export { AddressInfo } from "./AddressInfo" export { AddSslOptions } from "./AddSslOptions" +export { AddTunnelParams } from "./AddTunnelParams" export { AddVersionParams } from "./AddVersionParams" export { Alerts } from "./Alerts" export { Algorithm } from "./Algorithm" @@ -33,7 +34,7 @@ export { BackupTargetFS } from "./BackupTargetFS" export { Base64 } from "./Base64" export { BindId } from "./BindId" export { BindInfo } from "./BindInfo" -export { BindingSetPublicParams } from "./BindingSetPublicParams" +export { BindingGatewaySetEnabledParams } from "./BindingGatewaySetEnabledParams" export { BindOptions } from "./BindOptions" export { BindParams } from "./BindParams" export { Blake3Commitment } from "./Blake3Commitment" @@ -65,17 +66,20 @@ export { DepInfo } from "./DepInfo" export { Description } from "./Description" export { DestroySubcontainerFsParams } from "./DestroySubcontainerFsParams" export { DeviceFilter } from "./DeviceFilter" -export { DomainConfig } from "./DomainConfig" +export { DnsSettings } from "./DnsSettings" +export { DomainSettings } from "./DomainSettings" export { Duration } from "./Duration" export { EchoParams } from "./EchoParams" export { EditSignerParams } from "./EditSignerParams" export { EncryptedWire } from "./EncryptedWire" +export { EventId } from "./EventId" export { ExportActionParams } from "./ExportActionParams" export { ExportServiceInterfaceParams } from "./ExportServiceInterfaceParams" export { FileType } from "./FileType" -export { ForgetInterfaceParams } from "./ForgetInterfaceParams" +export { ForgetGatewayParams } from "./ForgetGatewayParams" export { FullIndex } from "./FullIndex" export { FullProgress } from "./FullProgress" +export { GatewayId } from "./GatewayId" export { GetActionInputParams } from "./GetActionInputParams" export { GetContainerIpParams } from "./GetContainerIpParams" export { GetHostInfoParams } from "./GetHostInfoParams" @@ -96,7 +100,6 @@ export { Governor } from "./Governor" export { Guid } from "./Guid" export { HardwareRequirements } from "./HardwareRequirements" export { HealthCheckId } from "./HealthCheckId" -export { HostAddress } from "./HostAddress" export { HostId } from "./HostId" export { HostnameInfo } from "./HostnameInfo" export { Hosts } from "./Hosts" @@ -125,6 +128,8 @@ export { Manifest } from "./Manifest" export { MaybeUtf8String } from "./MaybeUtf8String" export { MebiBytes } from "./MebiBytes" export { MerkleArchiveCommitment } from "./MerkleArchiveCommitment" +export { MetadataSrc } from "./MetadataSrc" +export { Metadata } from "./Metadata" export { MetricsCpu } from "./MetricsCpu" export { MetricsDisk } from "./MetricsDisk" export { MetricsGeneral } from "./MetricsGeneral" @@ -137,7 +142,7 @@ export { NamedProgress } from "./NamedProgress" export { NetInfo } from "./NetInfo" export { NetworkInfo } from "./NetworkInfo" export { NetworkInterfaceInfo } from "./NetworkInterfaceInfo" -export { NetworkInterfaceSetInboundParams } from "./NetworkInterfaceSetInboundParams" +export { NetworkInterfaceSetPublicParams } from "./NetworkInterfaceSetPublicParams" export { NetworkInterfaceType } from "./NetworkInterfaceType" export { OnionHostname } from "./OnionHostname" export { OsIndex } from "./OsIndex" @@ -155,9 +160,9 @@ export { PackageVersionInfo } from "./PackageVersionInfo" export { PasswordType } from "./PasswordType" export { PathOrUrl } from "./PathOrUrl" export { Percentage } from "./Percentage" -export { ProcedureId } from "./ProcedureId" export { Progress } from "./Progress" export { ProgressUnits } from "./ProgressUnits" +export { PublicDomainConfig } from "./PublicDomainConfig" export { Public } from "./Public" export { RecoverySource } from "./RecoverySource" export { RegistryAsset } from "./RegistryAsset" @@ -167,7 +172,9 @@ export { RemoveAssetParams } from "./RemoveAssetParams" export { RemoveCategoryParams } from "./RemoveCategoryParams" export { RemovePackageFromCategoryParams } from "./RemovePackageFromCategoryParams" export { RemovePackageParams } from "./RemovePackageParams" +export { RemoveTunnelParams } from "./RemoveTunnelParams" export { RemoveVersionParams } from "./RemoveVersionParams" +export { RenameGatewayParams } from "./RenameGatewayParams" export { ReplayId } from "./ReplayId" export { RequestCommitment } from "./RequestCommitment" export { RunActionParams } from "./RunActionParams" @@ -203,7 +210,7 @@ export { TaskSeverity } from "./TaskSeverity" export { TaskTrigger } from "./TaskTrigger" export { Task } from "./Task" export { TestSmtpParams } from "./TestSmtpParams" -export { UnsetInboundParams } from "./UnsetInboundParams" +export { UnsetPublicParams } from "./UnsetPublicParams" export { UpdatingState } from "./UpdatingState" export { VerifyCifsParams } from "./VerifyCifsParams" export { VersionSignerParams } from "./VersionSignerParams" diff --git a/sdk/base/lib/s9pk/index.ts b/sdk/base/lib/s9pk/index.ts index 8d6a2c6ed..bb928384c 100644 --- a/sdk/base/lib/s9pk/index.ts +++ b/sdk/base/lib/s9pk/index.ts @@ -101,18 +101,20 @@ export class S9pk { ) } - async dependencyMetadata(): Promise> { + async dependencyMetadata() { return Object.fromEntries( await Promise.all( - Object.entries(this.manifest.dependencies).map(async ([id, info]) => [ - id, - { - ...(await this.dependencyMetadataFor(id)), - icon: await this.dependencyIconFor(id), - description: info.description, - optional: info.optional, - }, - ]), + Object.entries(this.manifest.dependencies) + .filter(([_, info]) => !!info) + .map(async ([id, info]) => [ + id, + { + ...(await this.dependencyMetadataFor(id)), + icon: await this.dependencyIconFor(id), + description: info!.description, + optional: info!.optional, + }, + ]), ), ) } diff --git a/sdk/base/lib/test/startosTypeValidation.test.ts b/sdk/base/lib/test/startosTypeValidation.test.ts index b6a29fc00..e07db88b4 100644 --- a/sdk/base/lib/test/startosTypeValidation.test.ts +++ b/sdk/base/lib/test/startosTypeValidation.test.ts @@ -46,6 +46,7 @@ type EffectsTypeChecker = { describe("startosTypeValidation ", () => { test(`checking the params match`, () => { typeEquality({ + eventId: {} as never, child: "", isInContext: {} as never, onLeaveContext: () => {}, diff --git a/sdk/base/lib/util/Hostname.ts b/sdk/base/lib/util/Hostname.ts deleted file mode 100644 index ee68abe4f..000000000 --- a/sdk/base/lib/util/Hostname.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { HostnameInfo } from "../types" - -export function hostnameInfoToAddress(hostInfo: HostnameInfo): string { - if (hostInfo.kind === "onion") { - return `${hostInfo.hostname.value}` - } - if (hostInfo.kind !== "ip") { - throw Error("Expecting that the kind is ip.") - } - const hostname = hostInfo.hostname - if (hostname.kind === "domain") { - return `${hostname.subdomain ? `${hostname.subdomain}.` : ""}${hostname.domain}` - } - const port = hostname.sslPort || hostname.port - const portString = port ? `:${port}` : "" - if ("ipv4" === hostname.kind || "ipv6" === hostname.kind) { - return `${hostname.value}${portString}` - } - if ("local" === hostname.kind) { - return `${hostname.value}${portString}` - } - throw Error( - "Expecting to have a valid hostname kind." + JSON.stringify(hostname), - ) -} diff --git a/sdk/base/lib/util/getServiceInterface.ts b/sdk/base/lib/util/getServiceInterface.ts index d51fb8880..430c975c0 100644 --- a/sdk/base/lib/util/getServiceInterface.ts +++ b/sdk/base/lib/util/getServiceInterface.ts @@ -3,6 +3,7 @@ import { knownProtocols } from "../interfaces/Host" import { AddressInfo, Host, Hostname, HostnameInfo } from "../types" import { Effects } from "../Effects" import { DropGenerator, DropPromise } from "./Drop" +import { IPV6_LINK_LOCAL } from "./ip" export type UrlString = string export type HostId = string @@ -95,9 +96,9 @@ export const addressHostToUrl = ( hostname = host.hostname.value } else if (host.kind === "ip") { if (host.hostname.kind === "domain") { - hostname = `${host.hostname.subdomain ? `${host.hostname.subdomain}.` : ""}${host.hostname.domain}` + hostname = host.hostname.value } else if (host.hostname.kind === "ipv6") { - hostname = host.hostname.value.startsWith("fe80::") + hostname = IPV6_LINK_LOCAL.contains(host.hostname.value) ? `[${host.hostname.value}%${host.hostname.scopeId}]` : `[${host.hostname.value}]` } else { @@ -164,7 +165,7 @@ export const filledAddress = ( addressInfo: AddressInfo, ): FilledAddressInfo => { const toUrl = addressHostToUrl.bind(null, addressInfo) - const hostnames = host.hostnameInfo[addressInfo.internalPort] + const hostnames = host.hostnameInfo[addressInfo.internalPort] ?? [] return { ...addressInfo, diff --git a/sdk/base/lib/util/index.ts b/sdk/base/lib/util/index.ts index 2f3e981e6..c4c900192 100644 --- a/sdk/base/lib/util/index.ts +++ b/sdk/base/lib/util/index.ts @@ -1,6 +1,7 @@ /// Currently being used export { addressHostToUrl } from "./getServiceInterface" export { getDefaultString } from "./getDefaultString" +export * from "./ip" /// Not being used, but known to be browser compatible export { GetServiceInterface, getServiceInterface } from "./getServiceInterface" @@ -16,6 +17,5 @@ export { splitCommand } from "./splitCommand" export { nullIfEmpty } from "./nullIfEmpty" export { deepMerge, partialDiff } from "./deepMerge" export { deepEqual } from "./deepEqual" -export { hostnameInfoToAddress } from "./Hostname" export * as regexes from "./regexes" export { stringFromStdErrOut } from "./stringFromStdErrOut" diff --git a/sdk/base/lib/util/ip.ts b/sdk/base/lib/util/ip.ts new file mode 100644 index 000000000..a631b6b23 --- /dev/null +++ b/sdk/base/lib/util/ip.ts @@ -0,0 +1,85 @@ +export class IpAddress { + readonly octets: number[] + constructor(readonly address: string) { + if (address.includes(":")) { + this.octets = new Array(16).fill(0) + const segs = address.split(":") + let idx = 0 + let octIdx = 0 + while (segs[idx]) { + const num = parseInt(segs[idx], 16) + this.octets[octIdx++] = num >> 8 + this.octets[octIdx++] = num & 255 + idx += 1 + } + const lastSegIdx = segs.length - 1 + if (idx < lastSegIdx) { + idx = lastSegIdx + octIdx = 15 + while (segs[idx]) { + const num = parseInt(segs[idx], 16) + this.octets[octIdx--] = num & 255 + this.octets[octIdx--] = num >> 8 + idx -= 1 + } + } + } else { + this.octets = address.split(".").map(Number) + if (this.octets.length !== 4) throw new Error("invalid ipv4 address") + } + if (this.octets.some((o) => o >= 256)) { + throw new Error("invalid ip address") + } + } + static parse(address: string): IpAddress { + return new IpAddress(address) + } + isIpv4(): boolean { + return this.octets.length === 4 + } + isIpv6(): boolean { + return this.octets.length === 16 + } + isPublic(): boolean { + return this.isIpv4() && !PRIVATE_IPV4_RANGES.some((r) => r.contains(this)) + } +} + +export class IpNet extends IpAddress { + readonly prefix + constructor(readonly ipnet: string) { + const [address, prefixStr] = ipnet.split("/", 2) + super(address) + this.prefix = Number(prefixStr) + } + static parse(ipnet: string): IpNet { + return new IpNet(ipnet) + } + contains(address: string | IpAddress): boolean { + if (typeof address === "string") address = new IpAddress(address) + if (this.octets.length !== address.octets.length) return false + let prefix = this.prefix + let idx = 0 + while (idx < this.octets.length && prefix >= 8) { + if (this.octets[idx] !== address.octets[idx]) { + return false + } + idx += 1 + prefix -= 8 + } + if (prefix === 0 || idx >= this.octets.length) return true + const mask = 255 << prefix + return (this.octets[idx] & mask) === (address.octets[idx] & mask) + } +} + +export const PRIVATE_IPV4_RANGES = [ + new IpNet("127.0.0.0/8"), + new IpNet("10.0.0.0/8"), + new IpNet("172.16.0.0/12"), + new IpNet("192.168.0.0/16"), +] + +export const IPV6_LINK_LOCAL = new IpNet("fe80::/10") + +export const CGNAT = new IpNet("100.64.0.0/10") diff --git a/sdk/package/lib/StartSdk.ts b/sdk/package/lib/StartSdk.ts index 3f74b172a..9cb32df4a 100644 --- a/sdk/package/lib/StartSdk.ts +++ b/sdk/package/lib/StartSdk.ts @@ -61,7 +61,7 @@ import { } from "../../base/lib/inits" import { DropGenerator } from "../../base/lib/util/Drop" -export const OSVersion = testTypeVersion("0.4.0-alpha.9") +export const OSVersion = testTypeVersion("0.4.0-alpha.10") // prettier-ignore type AnyNeverCond = @@ -104,7 +104,7 @@ export class StartSdk { // prettier-ignore type StartSdkEffectWrapper = { - [K in keyof Omit]: (effects: Effects, ...args: Parameters) => ReturnType + [K in keyof Omit]: (effects: Effects, ...args: Parameters) => ReturnType } const startSdkEffectWrapper: StartSdkEffectWrapper = { restart: (effects, ...args) => effects.restart(...args), diff --git a/sdk/package/lib/backup/Backups.ts b/sdk/package/lib/backup/Backups.ts index 6784ad51c..b7348e01d 100644 --- a/sdk/package/lib/backup/Backups.ts +++ b/sdk/package/lib/backup/Backups.ts @@ -197,7 +197,7 @@ async function runRsync(rsyncOptions: { for (const exclude of options.exclude) { args.push(`--exclude=${exclude}`) } - args.push("-actAXH") + args.push("-rlptgocAXH") args.push("--info=progress2") args.push("--no-inc-recursive") args.push(srcPath) diff --git a/sdk/package/lib/util/SubContainer.ts b/sdk/package/lib/util/SubContainer.ts index 9f89d6115..31a691baa 100644 --- a/sdk/package/lib/util/SubContainer.ts +++ b/sdk/package/lib/util/SubContainer.ts @@ -157,10 +157,14 @@ export class SubContainerOwned< ) { super() this.leaderExited = false - this.leader = cp.spawn("start-cli", ["subcontainer", "launch", rootfs], { - killSignal: "SIGKILL", - stdio: "inherit", - }) + this.leader = cp.spawn( + "start-container", + ["subcontainer", "launch", rootfs], + { + killSignal: "SIGKILL", + stdio: "inherit", + }, + ) this.leader.on("exit", () => { this.leaderExited = true }) @@ -407,7 +411,7 @@ export class SubContainerOwned< delete options.cwd } const child = cp.spawn( - "start-cli", + "start-container", [ "subcontainer", "exec", @@ -529,7 +533,7 @@ export class SubContainerOwned< await this.killLeader() this.leaderExited = false this.leader = cp.spawn( - "start-cli", + "start-container", [ "subcontainer", "launch", @@ -571,7 +575,7 @@ export class SubContainerOwned< delete options.cwd } return cp.spawn( - "start-cli", + "start-container", [ "subcontainer", "exec", diff --git a/sdk/package/lib/util/fileHelper.ts b/sdk/package/lib/util/fileHelper.ts index d6f83a68a..25c4bc12e 100644 --- a/sdk/package/lib/util/fileHelper.ts +++ b/sdk/package/lib/util/fileHelper.ts @@ -269,7 +269,7 @@ export class FileHelper { eq: (left: B | null | undefined, right: B | null) => boolean, abort?: AbortSignal, ) { - let res + let prev: { value: B | null } | null = null while (effects.isInContext && !abort?.aborted) { if (await exists(this.path)) { const ctrl = new AbortController() @@ -287,8 +287,10 @@ export class FileHelper { } }) .catch((e) => console.error(asError(e))) - if (!eq(res, newRes)) yield newRes - res = newRes + if (!prev || !eq(prev.value, newRes)) { + yield newRes + } + prev = { value: newRes } await listen } else { yield null diff --git a/sdk/package/lib/util/index.ts b/sdk/package/lib/util/index.ts index 879a512d5..77cd8f850 100644 --- a/sdk/package/lib/util/index.ts +++ b/sdk/package/lib/util/index.ts @@ -1,5 +1,4 @@ export * from "../../../base/lib/util" export { GetSslCertificate } from "./GetSslCertificate" -export { hostnameInfoToAddress } from "../../../base/lib/util/Hostname" export { Drop } from "../../../base/lib/util/Drop" diff --git a/sdk/package/package-lock.json b/sdk/package/package-lock.json index 6be167d70..a70719329 100644 --- a/sdk/package/package-lock.json +++ b/sdk/package/package-lock.json @@ -1,12 +1,12 @@ { "name": "@start9labs/start-sdk", - "version": "0.4.0-beta.36", + "version": "0.4.0-beta.37", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@start9labs/start-sdk", - "version": "0.4.0-beta.36", + "version": "0.4.0-beta.37", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", diff --git a/sdk/package/package.json b/sdk/package/package.json index 9b1fc87fc..7ebc2eb98 100644 --- a/sdk/package/package.json +++ b/sdk/package/package.json @@ -1,6 +1,6 @@ { "name": "@start9labs/start-sdk", - "version": "0.4.0-beta.36", + "version": "0.4.0-beta.37", "description": "Software development kit to facilitate packaging services for StartOS", "main": "./package/lib/index.js", "types": "./package/lib/index.d.ts", diff --git a/system-images/README.md b/system-images/README.md deleted file mode 100644 index 45dde2b2a..000000000 --- a/system-images/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# System Images - -There once was a time that I served to provide backwards compatibility for service configuration and properties rendering. Utilizing me for this functionality has since been deprecated in favor of [JS procedures](https://start9.com/latest/developer-docs/specification/js-procedure). Now I simply serve to provide service backup functionality. This will soon be converted to using JS procedure scripts as well. \ No newline at end of file diff --git a/system-images/binfmt/.gitignore b/system-images/binfmt/.gitignore deleted file mode 100644 index 9ea8ef8d2..000000000 --- a/system-images/binfmt/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/docker-images \ No newline at end of file diff --git a/system-images/binfmt/Dockerfile b/system-images/binfmt/Dockerfile deleted file mode 100644 index e1dd5d65b..000000000 --- a/system-images/binfmt/Dockerfile +++ /dev/null @@ -1 +0,0 @@ -FROM tonistiigi/binfmt diff --git a/system-images/binfmt/Makefile b/system-images/binfmt/Makefile deleted file mode 100644 index 141b50fc1..000000000 --- a/system-images/binfmt/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -.DELETE_ON_ERROR: - -all: docker-images/aarch64.tar docker-images/x86_64.tar - -clean: - rm -rf docker-images - -docker-images: - mkdir docker-images - -docker-images/aarch64.tar: Dockerfile docker-images - docker buildx build --tag start9/x_system/binfmt --platform=linux/arm64 -o type=docker,dest=docker-images/aarch64.tar . - -docker-images/x86_64.tar: Dockerfile docker-images - docker buildx build --tag start9/x_system/binfmt --platform=linux/amd64 -o type=docker,dest=docker-images/x86_64.tar . \ No newline at end of file diff --git a/system-images/binfmt/manifest.json b/system-images/binfmt/manifest.json deleted file mode 100644 index ebdfc259d..000000000 --- a/system-images/binfmt/manifest.json +++ /dev/null @@ -1 +0,0 @@ -[{"Config":"3f7fd8db05afd7a5fa804ebfb0e40038068af52b45d8f9a04c1c9fa8d316e511.json","RepoTags":["multitest/binfmt:latest"],"Layers":["753827173463aa2b7471a5be8ac2323a1cf78ccedfdfcc7bb21831d9e0141732/layer.tar","e0747212aee318099c729893bb4cca2ff164f6bd154841d5777636f82b7f18d4/layer.tar"]},{"Config":"143329dfbcc6abb472070a2f0bae5cf9865f178e5391a15592e75b349c803c69.json","RepoTags":["multitest/binfmt:latest"],"Layers":["de6e72d83667be34f782cf1f0a376e0507d189872142e1934f14d3be4813715a/layer.tar","12670876b666af35f388c3d2160436257b390a163a4c7178863badd05394577a/layer.tar"]}] diff --git a/system-images/compat/.gitignore b/system-images/compat/.gitignore deleted file mode 100644 index 0a8fcff98..000000000 --- a/system-images/compat/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -/target -**/*.rs.bk -.DS_Store -.vscode -/docker-images \ No newline at end of file diff --git a/system-images/compat/Cargo.lock b/system-images/compat/Cargo.lock deleted file mode 100644 index c5cf4db11..000000000 --- a/system-images/compat/Cargo.lock +++ /dev/null @@ -1,6120 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if", - "cipher 0.3.0", - "cpufeatures", - "ctr", - "opaque-debug", -] - -[[package]] -name = "ahash" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" -dependencies = [ - "getrandom 0.2.12", - "once_cell", - "version_check", -] - -[[package]] -name = "ahash" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" -dependencies = [ - "cfg-if", - "getrandom 0.2.12", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "allocator-api2" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - -[[package]] -name = "anstream" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" - -[[package]] -name = "anstyle-parse" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" -dependencies = [ - "anstyle", - "windows-sys 0.52.0", -] - -[[package]] -name = "anyhow" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" -dependencies = [ - "backtrace", -] - -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "ascii-canvas" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" -dependencies = [ - "term", -] - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-compression" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" -dependencies = [ - "brotli", - "flate2", - "futures-core", - "memchr", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "async-stream" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "async-trait" -version = "0.1.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - -[[package]] -name = "atomic-write-file" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" -dependencies = [ - "nix 0.27.1", - "rand 0.8.5", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "axum" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e" -dependencies = [ - "async-trait", - "axum-core", - "base64 0.21.7", - "bytes", - "futures-util", - "http 1.0.0", - "http-body 1.0.0", - "http-body-util", - "hyper 1.1.0", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sha1", - "sync_wrapper", - "tokio", - "tokio-tungstenite", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 1.0.0", - "http-body 1.0.0", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-server" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad46c3ec4e12f4a4b6835e173ba21c25e484c9d02b49770bf006ce5367c036" -dependencies = [ - "bytes", - "futures-util", - "http 1.0.0", - "http-body 1.0.0", - "http-body-util", - "hyper 1.1.0", - "hyper-util", - "pin-project-lite", - "tokio", - "tower", - "tower-service", -] - -[[package]] -name = "backtrace" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base32" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "basic-cookies" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67bd8fd42c16bdb08688243dc5f0cc117a3ca9efeeaba3a345a18a6159ad96f7" -dependencies = [ - "lalrpop", - "lalrpop-util", - "regex", -] - -[[package]] -name = "beau_collector" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a143066d3cbd3d32c15b51c39449e50e32a68293d31589fb941d6a9df0df4f" -dependencies = [ - "anyhow", - "itertools 0.9.0", -] - -[[package]] -name = "bimap" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b" -dependencies = [ - "serde", -] - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" -dependencies = [ - "serde", -] - -[[package]] -name = "bitmaps" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703642b98a00b3b90513279a8ede3fcfa479c126c5fb46e78f3051522f021403" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "blake2b_simd" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" -dependencies = [ - "arrayref", - "arrayvec", - "constant_time_eq", -] - -[[package]] -name = "blake3" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", -] - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "block-padding", - "generic-array", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - -[[package]] -name = "brotli" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bumpalo" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" - -[[package]] -name = "cc" -version = "1.0.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-targets 0.52.0", -] - -[[package]] -name = "chumsky" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" -dependencies = [ - "hashbrown 0.14.3", -] - -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half 2.3.1", -] - -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "ansi_term", - "atty", - "bitflags 1.3.2", - "strsim 0.8.0", - "textwrap 0.11.0", - "unicode-width", - "vec_map", -] - -[[package]] -name = "clap" -version = "3.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" -dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_lex 0.2.4", - "indexmap 1.9.3", - "strsim 0.10.0", - "termcolor", - "textwrap 0.16.0", -] - -[[package]] -name = "clap" -version = "4.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" -dependencies = [ - "anstream", - "anstyle", - "clap_lex 0.6.0", - "strsim 0.10.0", -] - -[[package]] -name = "clap_derive" -version = "4.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "clap_lex" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" - -[[package]] -name = "color-eyre" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" -dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", -] - -[[package]] -name = "colorchoice" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" - -[[package]] -name = "compat" -version = "0.1.0" -dependencies = [ - "anyhow", - "beau_collector", - "clap 2.34.0", - "dashmap", - "emver", - "failure", - "imbl-value", - "indexmap 1.9.3", - "itertools 0.10.5", - "lazy_static", - "linear-map", - "log", - "nix 0.25.1", - "pest", - "pest_derive", - "rand 0.8.5", - "regex", - "rust-argon2 1.0.1", - "serde", - "serde_json", - "serde_yaml 0.8.26", - "start-os", -] - -[[package]] -name = "concurrent-queue" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "console" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" -dependencies = [ - "encode_unicode 0.3.6", - "lazy_static", - "libc", - "unicode-width", - "windows-sys 0.52.0", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "const_format" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "constant_time_eq" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cookie" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - -[[package]] -name = "cookie" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - -[[package]] -name = "cookie" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd91cf61412820176e137621345ee43b3f4423e589e7ae4e50d601d93e35ef8" -dependencies = [ - "time", - "version_check", -] - -[[package]] -name = "cookie_store" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d606d0fba62e13cf04db20536c05cb7f13673c161cb47a47a82b9b9e7d3f1daa" -dependencies = [ - "cookie 0.16.2", - "idna 0.2.3", - "log", - "publicsuffix", - "serde", - "serde_derive", - "serde_json", - "time", - "url", -] - -[[package]] -name = "cookie_store" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387461abbc748185c3a6e1673d826918b450b87ff22639429c694619a83b6cf6" -dependencies = [ - "cookie 0.17.0", - "idna 0.3.0", - "log", - "publicsuffix", - "serde", - "serde_derive", - "serde_json", - "time", - "url", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" - -[[package]] -name = "cpufeatures" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" -dependencies = [ - "libc", -] - -[[package]] -name = "crc" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" - -[[package]] -name = "crossterm" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" -dependencies = [ - "bitflags 2.4.2", - "crossterm_winapi", - "futures-core", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "crypto-mac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" -dependencies = [ - "generic-array", - "subtle", -] - -[[package]] -name = "csv" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" -dependencies = [ - "csv-core", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" -dependencies = [ - "memchr", -] - -[[package]] -name = "ctr" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" -dependencies = [ - "cipher 0.3.0", -] - -[[package]] -name = "current_platform" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a74858bcfe44b22016cb49337d7b6f04618c58e5dbfdef61b06b8c434324a0bc" - -[[package]] -name = "curve25519-dalek" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek" -version = "4.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" -dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "digest 0.10.7", - "fiat-crypto", - "platforms", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "darling" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 2.0.48", -] - -[[package]] -name = "darling_macro" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.3", - "lock_api", - "once_cell", - "parking_lot_core", -] - -[[package]] -name = "data-encoding" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" - -[[package]] -name = "der" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", - "serde", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case 0.4.0", - "proc-macro2", - "quote", - "rustc_version", - "syn 1.0.109", -] - -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer 0.10.4", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "divrem" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69dde51e8fef5e12c1d65e0929b03d66e4c0c18282bc30ed2ca050ad6f44dd82" - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "drain" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f1a0abf3fcefad9b4dd0e414207a7408e12b68414a01e6bb19b897d5bd7632d" -dependencies = [ - "tokio", -] - -[[package]] -name = "dyn-clone" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest 0.10.7", - "elliptic-curve", - "rfc6979", - "signature 2.2.0", - "spki", -] - -[[package]] -name = "ed25519" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" -dependencies = [ - "signature 1.6.4", -] - -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "serde", - "signature 2.2.0", -] - -[[package]] -name = "ed25519-dalek" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" -dependencies = [ - "curve25519-dalek 3.2.0", - "ed25519 1.5.3", - "rand 0.7.3", - "serde", - "sha2 0.9.9", - "zeroize", -] - -[[package]] -name = "ed25519-dalek" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" -dependencies = [ - "curve25519-dalek 4.1.1", - "ed25519 2.2.3", - "rand_core 0.6.4", - "serde", - "sha2 0.10.8", - "signature 2.2.0", - "subtle", - "zeroize", -] - -[[package]] -name = "either" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" -dependencies = [ - "serde", -] - -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest 0.10.7", - "ff", - "generic-array", - "group", - "pem-rfc7468", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "emver" -version = "0.1.7" -source = "git+https://github.com/Start9Labs/emver-rs.git#61cf0bc96711b4d6f3f30df8efef025e0cc02bad" -dependencies = [ - "either", - "fp-core", - "nom", - "serde", -] - -[[package]] -name = "ena" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" -dependencies = [ - "log", -] - -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - -[[package]] -name = "encoding_rs" -version = "0.8.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "enum-as-inner" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "eyre" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "failure" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" -dependencies = [ - "backtrace", - "failure_derive", -] - -[[package]] -name = "failure_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "synstructure", -] - -[[package]] -name = "fastrand" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" - -[[package]] -name = "fd-lock-rs" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef0f547e1d79e058664f2ea7d3a6d82b2ddd5fea4a6650b97b70c38979f34db3" -dependencies = [ - "nix 0.24.3", -] - -[[package]] -name = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "fiat-crypto" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" - -[[package]] -name = "filetime" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.4.1", - "windows-sys 0.52.0", -] - -[[package]] -name = "finl_unicode" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "flate2" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "flume" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" -dependencies = [ - "futures-core", - "futures-sink", - "spin 0.9.8", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fp-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "338a5feb6c7248603dfa3da758da4e99abb65e792a157fe1d657e7c2f5fbcd0b" -dependencies = [ - "itertools 0.8.2", -] - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-executor" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - -[[package]] -name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-macro" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "futures-sink" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" - -[[package]] -name = "futures-task" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" - -[[package]] -name = "futures-util" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" - -[[package]] -name = "gpt" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8283e7331b8c93b9756e0cfdbcfb90312852f953c6faf9bf741e684cc3b6ad69" -dependencies = [ - "bitflags 2.4.2", - "crc", - "log", - "uuid", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "h2" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.11", - "indexmap 2.1.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h2" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 1.0.0", - "indexmap 2.1.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "half" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - -[[package]] -name = "half" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" -dependencies = [ - "cfg-if", - "crunchy", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash 0.8.7", -] - -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" -dependencies = [ - "ahash 0.8.7", - "allocator-api2", -] - -[[package]] -name = "hashlink" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" -dependencies = [ - "hashbrown 0.14.3", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "helpers" -version = "0.1.0" -dependencies = [ - "async-trait", - "color-eyre", - "futures", - "lazy_async_pool", - "models", - "pin-project", - "rpc-toolkit 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde", - "serde_json", - "tokio", - "tokio-stream", - "tracing", -] - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hifijson" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ef6b41c333e6dd2a4aaa59125a19b633cd17e7aaf372b2260809777bcdef4a" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac 0.12.1", -] - -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac", - "digest 0.9.0", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "http" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.11", - "pin-project-lite", -] - -[[package]] -name = "http-body" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" -dependencies = [ - "bytes", - "http 1.0.0", -] - -[[package]] -name = "http-body-util" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" -dependencies = [ - "bytes", - "futures-util", - "http 1.0.0", - "http-body 1.0.0", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "0.14.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.24", - "http 0.2.11", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2 0.4.2", - "http 1.0.0", - "http-body 1.0.0", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper 0.14.28", - "native-tls", - "tokio", - "tokio-native-tls", -] - -[[package]] -name = "hyper-util" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdea9aac0dbe5a9240d68cfd9501e2db94222c6dc06843e06640b9e07f0fdc67" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http 1.0.0", - "http-body 1.0.0", - "hyper 1.1.0", - "pin-project-lite", - "socket2", - "tokio", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "imbl" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978d142c8028edf52095703af2fad11d6f611af1246685725d6b850634647085" -dependencies = [ - "bitmaps", - "imbl-sized-chunks", - "rand_core 0.6.4", - "rand_xoshiro", - "serde", - "version_check", -] - -[[package]] -name = "imbl-sized-chunks" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144006fb58ed787dcae3f54575ff4349755b00ccc99f4b4873860b654be1ed63" -dependencies = [ - "bitmaps", -] - -[[package]] -name = "imbl-value" -version = "0.1.0" -source = "git+https://github.com/Start9Labs/imbl-value.git#48dc39a762a3b4f9300d3b9f850cbd394e777ae0" -dependencies = [ - "imbl", - "serde", - "serde_json", - "treediff", - "yasi", -] - -[[package]] -name = "include_dir" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" -dependencies = [ - "include_dir_macros", -] - -[[package]] -name = "include_dir_macros" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" -dependencies = [ - "equivalent", - "hashbrown 0.14.3", - "serde", -] - -[[package]] -name = "indicatif" -version = "0.17.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" -dependencies = [ - "console", - "instant", - "number_prefix", - "portable-atomic", - "tokio", - "unicode-width", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "integer-encoding" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924df4f0e24e2e7f9cdd90babb0b96f93b20f3ecfa949ea9e6613756b8c8e1bf" -dependencies = [ - "async-trait", - "tokio", -] - -[[package]] -name = "ipnet" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" -dependencies = [ - "serde", -] - -[[package]] -name = "iprange" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37209be0ad225457e63814401415e748e2453a5297f9b637338f5fb8afa4ec00" -dependencies = [ - "ipnet", - "serde", -] - -[[package]] -name = "is-terminal" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" -dependencies = [ - "hermit-abi 0.3.4", - "rustix", - "windows-sys 0.52.0", -] - -[[package]] -name = "isocountry" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea1dc4bf0fb4904ba83ffdb98af3d9c325274e92e6e295e4151e86c96363e04" -dependencies = [ - "serde", - "thiserror", -] - -[[package]] -name = "itertools" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" - -[[package]] -name = "jaq-core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb52eeac20f256459e909bd4a03bb8c4fab6a1fdbb8ed52d00f644152df48ece" -dependencies = [ - "ahash 0.7.7", - "dyn-clone", - "hifijson", - "indexmap 1.9.3", - "itertools 0.10.5", - "jaq-parse", - "log", - "once_cell", - "regex", - "serde_json", -] - -[[package]] -name = "jaq-parse" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0f97f01eb9e87af3cbcc843b0dfe693fc6b0a2b9093dc8980dd9fc682826b0" -dependencies = [ - "chumsky", - "serde", -] - -[[package]] -name = "jaq-std" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b261109851c8687bc55eab26e6d81e96f3fdab367e2d3d5706947c218ddaf22" -dependencies = [ - "bincode", - "jaq-parse", -] - -[[package]] -name = "josekit" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd20997283339a19226445db97d632c8dc7adb6b8172537fe0e9e540fb141df2" -dependencies = [ - "anyhow", - "base64 0.21.7", - "flate2", - "once_cell", - "openssl", - "regex", - "serde", - "serde_json", - "thiserror", - "time", -] - -[[package]] -name = "js-sys" -version = "0.3.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "json-patch" -version = "0.2.7-alpha.0" -dependencies = [ - "imbl-value", - "json-ptr", - "serde", - "treediff", -] - -[[package]] -name = "json-ptr" -version = "0.1.0" -dependencies = [ - "imbl", - "imbl-value", - "serde", - "thiserror", -] - -[[package]] -name = "jsonpath_lib" -version = "0.3.0" -source = "git+https://github.com/Start9Labs/jsonpath.git#1cacbd64afa2e1941a21fef06bad14317ba92f30" -dependencies = [ - "imbl-value", - "log", - "serde", - "serde_json", -] - -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "lalrpop" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" -dependencies = [ - "ascii-canvas", - "bit-set", - "diff", - "ena", - "is-terminal", - "itertools 0.10.5", - "lalrpop-util", - "petgraph", - "pico-args", - "regex", - "regex-syntax 0.7.5", - "string_cache", - "term", - "tiny-keccak", - "unicode-xid", -] - -[[package]] -name = "lalrpop-util" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" -dependencies = [ - "regex", -] - -[[package]] -name = "lazy_async_pool" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06cf485d4867e0714e35c1652e736bcf892d28fceecca01036764575db64ba84" -dependencies = [ - "async-channel", - "futures", -] - -[[package]] -name = "lazy_format" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e479e99b287d578ed5f6cd4c92cdf48db219088adb9c5b14f7c155b71dfba792" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -dependencies = [ - "spin 0.5.2", -] - -[[package]] -name = "libc" -version = "0.2.152" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" - -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "libredox" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" -dependencies = [ - "bitflags 2.4.2", - "libc", - "redox_syscall 0.4.1", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linear-map" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" -dependencies = [ - "serde", - "serde_test", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - -[[package]] -name = "linux-raw-sys" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" - -[[package]] -name = "lock_api" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - -[[package]] -name = "mbrman" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c487024623ae38584610237dd1be8932bb2b324474b23c37a25f9fbe6bf5e9e" -dependencies = [ - "bincode", - "bitvec", - "serde", - "serde-big-array", - "thiserror", -] - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest 0.10.7", -] - -[[package]] -name = "memchr" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" - -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", -] - -[[package]] -name = "models" -version = "0.1.0" -dependencies = [ - "base64 0.21.7", - "color-eyre", - "ed25519-dalek 2.1.0", - "emver", - "ipnet", - "lazy_static", - "mbrman", - "num_enum", - "openssl", - "patch-db", - "rand 0.8.5", - "regex", - "reqwest", - "rpc-toolkit 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde", - "serde_json", - "sqlx", - "ssh-key", - "thiserror", - "tokio", - "torut", - "tracing", - "yasi", -] - -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - -[[package]] -name = "new_mime_guess" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d684d1b59e0dc07b37e2203ef576987473288f530082512aff850585c61b1f" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "nix" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.6.5", -] - -[[package]] -name = "nix" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.6.5", - "pin-utils", -] - -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.7.1", - "pin-utils", -] - -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.4.2", - "cfg-if", - "libc", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "num" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint-dig" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-complex" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.4", - "libc", -] - -[[package]] -name = "num_enum" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "openssh-keys" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75a0ec2d1b302412fb503224289325fcc0e44600176864804c7211b055cfd58" -dependencies = [ - "base64 0.21.7", - "byteorder", - "md-5", - "sha2 0.10.8", - "thiserror", -] - -[[package]] -name = "openssl" -version = "0.10.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" -dependencies = [ - "bitflags 2.4.2", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-src" -version = "300.2.1+3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fe476c29791a5ca0d1273c697e96085bbabbbea2ef7afd5617e78a4b40332d3" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2 0.10.8", -] - -[[package]] -name = "p384" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2 0.10.8", -] - -[[package]] -name = "p521" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" -dependencies = [ - "base16ct", - "ecdsa", - "elliptic-curve", - "primeorder", - "rand_core 0.6.4", - "sha2 0.10.8", -] - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.4.1", - "smallvec", - "windows-targets 0.48.5", -] - -[[package]] -name = "paste" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" - -[[package]] -name = "patch-db" -version = "0.1.0" -dependencies = [ - "async-trait", - "fd-lock-rs", - "futures", - "imbl", - "imbl-value", - "json-patch", - "json-ptr", - "lazy_static", - "nix 0.26.4", - "patch-db-macro", - "serde", - "serde_cbor 0.11.1", - "thiserror", - "tokio", - "tracing", - "tracing-error", -] - -[[package]] -name = "patch-db-macro" -version = "0.1.0" -dependencies = [ - "patch-db-macro-internals", - "proc-macro2", - "syn 1.0.109", -] - -[[package]] -name = "patch-db-macro-internals" -version = "0.1.0" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest 0.10.7", - "hmac 0.12.1", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pest" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" -dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "pest_meta" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d" -dependencies = [ - "once_cell", - "pest", - "sha2 0.10.8", -] - -[[package]] -name = "petgraph" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" -dependencies = [ - "fixedbitset", - "indexmap 2.1.0", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - -[[package]] -name = "pin-project" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "pkg-config" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" - -[[package]] -name = "platforms" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" - -[[package]] -name = "portable-atomic" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "prettytable-rs" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a" -dependencies = [ - "csv", - "encode_unicode 1.0.0", - "is-terminal", - "lazy_static", - "term", - "unicode-width", -] - -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - -[[package]] -name = "proc-macro-crate" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" -dependencies = [ - "toml_edit 0.21.0", -] - -[[package]] -name = "proc-macro2" -version = "1.0.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proptest" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" -dependencies = [ - "bit-set", - "bit-vec", - "bitflags 2.4.2", - "lazy_static", - "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_xorshift", - "regex-syntax 0.8.2", - "rusty-fork", - "tempfile", - "unarray", -] - -[[package]] -name = "proptest-derive" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf16337405ca084e9c78985114633b6827711d22b9e6ef6c6c0d665eb3f0b6e" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "psl-types" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" - -[[package]] -name = "publicsuffix" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" -dependencies = [ - "idna 0.3.0", - "psl-types", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quote" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.12", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "rand_xoshiro" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "redox_syscall" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_users" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" -dependencies = [ - "getrandom 0.2.12", - "libredox", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.5", - "regex-syntax 0.8.2", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.8.2", -] - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" - -[[package]] -name = "regex-syntax" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" - -[[package]] -name = "reqwest" -version = "0.11.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" -dependencies = [ - "base64 0.21.7", - "bytes", - "cookie 0.16.2", - "cookie_store 0.16.2", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.24", - "http 0.2.11", - "http-body 0.4.6", - "hyper 0.14.28", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "system-configuration", - "tokio", - "tokio-native-tls", - "tokio-socks", - "tokio-util", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "winreg", -] - -[[package]] -name = "reqwest_cookie_store" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba529055ea150e42e4eb9c11dcd380a41025ad4d594b0cb4904ef28b037e1061" -dependencies = [ - "bytes", - "cookie_store 0.20.0", - "reqwest", - "url", -] - -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac 0.12.1", - "subtle", -] - -[[package]] -name = "ring" -version = "0.17.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" -dependencies = [ - "cc", - "getrandom 0.2.12", - "libc", - "spin 0.9.8", - "untrusted", - "windows-sys 0.48.0", -] - -[[package]] -name = "rpassword" -version = "7.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" -dependencies = [ - "libc", - "rtoolbox", - "windows-sys 0.48.0", -] - -[[package]] -name = "rpc-toolkit" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48252a30abb9426a3239fa8dfd2c8dd2647bb24db0b6145db2df04ae53fe647" -dependencies = [ - "clap 3.2.25", - "futures", - "hyper 0.14.28", - "lazy_static", - "openssl", - "reqwest", - "rpc-toolkit-macro 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde", - "serde_cbor 0.11.2", - "serde_json", - "thiserror", - "tokio", - "url", - "yajrc", -] - -[[package]] -name = "rpc-toolkit" -version = "0.2.3" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits#9e989e23adb440bc72faa585b28e5aa2667a0a0d" -dependencies = [ - "async-stream", - "async-trait", - "axum", - "clap 4.4.18", - "futures", - "http 1.0.0", - "http-body-util", - "imbl-value", - "itertools 0.12.0", - "lazy_format", - "lazy_static", - "openssl", - "pin-project", - "reqwest", - "rpc-toolkit-macro 0.2.2 (git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits)", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-stream", - "url", - "yajrc", -] - -[[package]] -name = "rpc-toolkit-macro" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8e4b9cb00baf2d61bcd35e98d67dcb760382a3b4540df7e63b38d053c8a7b8b" -dependencies = [ - "proc-macro2", - "rpc-toolkit-macro-internals 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.109", -] - -[[package]] -name = "rpc-toolkit-macro" -version = "0.2.2" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits#9e989e23adb440bc72faa585b28e5aa2667a0a0d" -dependencies = [ - "proc-macro2", - "rpc-toolkit-macro-internals 0.2.2 (git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits)", - "syn 1.0.109", -] - -[[package]] -name = "rpc-toolkit-macro-internals" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e2ce21b936feaecdab9c9a8e75b9dca64374ccc11951a58045ad6559b75f42" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "rpc-toolkit-macro-internals" -version = "0.2.2" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits#9e989e23adb440bc72faa585b28e5aa2667a0a0d" -dependencies = [ - "itertools 0.12.0", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "rsa" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" -dependencies = [ - "const-oid", - "digest 0.10.7", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "sha2 0.10.8", - "signature 2.2.0", - "spki", - "subtle", - "zeroize", -] - -[[package]] -name = "rtoolbox" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "rust-argon2" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5885493fdf0be6cdff808d1533ce878d21cfa49c7086fa00c66355cd9141bfc" -dependencies = [ - "base64 0.21.7", - "blake2b_simd", - "constant_time_eq", - "crossbeam-utils", -] - -[[package]] -name = "rust-argon2" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d9848531d60c9cbbcf9d166c885316c24bc0e2a9d3eba0956bb6cbbd79bc6e8" -dependencies = [ - "base64 0.21.7", - "blake2b_simd", - "constant_time_eq", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "0.38.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" -dependencies = [ - "bitflags 2.4.2", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.21.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" -dependencies = [ - "ring", - "rustls-webpki 0.101.7", - "sct", -] - -[[package]] -name = "rustls" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" -dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki 0.102.1", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - -[[package]] -name = "rustls-pki-types" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e9d979b3ce68192e42760c7810125eb6cf2ea10efae545a156063e61f314e2a" - -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "rustls-webpki" -version = "0.102.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4ca26037c909dedb327b48c3327d0ba91d3dd3c4e05dad328f210ffb68e95b" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - -[[package]] -name = "rusty-fork" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" -dependencies = [ - "fnv", - "quick-error", - "tempfile", - "wait-timeout", -] - -[[package]] -name = "rustyline-async" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca4447465ceb8c01c253cc81660b242547c58e4a59c85b13294a6e70de8b9e" -dependencies = [ - "crossterm", - "futures-channel", - "futures-util", - "pin-project", - "thingbuf", - "thiserror", - "unicode-segmentation", - "unicode-width", -] - -[[package]] -name = "ryu" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" - -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - -[[package]] -name = "security-framework" -version = "2.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" -dependencies = [ - "serde", -] - -[[package]] -name = "serde" -version = "1.0.195" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-big-array" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3323f09a748af288c3dc2474ea6803ee81f118321775bffa3ac8f7e65c5e90e7" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_cbor" -version = "0.11.1" -dependencies = [ - "half 1.8.2", - "serde", -] - -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half 1.8.2", - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.195" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "serde_json" -version = "1.0.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" -dependencies = [ - "indexmap 2.1.0", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" -dependencies = [ - "itoa", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_test" -version = "1.0.176" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a2f49ace1498612d14f7e0b8245519584db8299541dfe31a06374a828d620ab" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5c9fdb6b00a489875b22efd4b78fe2b363b72265cc5f6eb2e2b9ee270e6140c" -dependencies = [ - "base64 0.21.7", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.1.0", - "serde", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff351eb4b33600a2e138dfa0b10b65a238ea8ff8fb2387c422c5022a3e8298" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "serde_yaml" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" -dependencies = [ - "indexmap 1.9.3", - "ryu", - "serde", - "yaml-rust", -] - -[[package]] -name = "serde_yaml" -version = "0.9.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38" -dependencies = [ - "indexmap 2.1.0", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sha3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "keccak", - "opaque-debug", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shell-words" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" - -[[package]] -name = "signal-hook" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - -[[package]] -name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - -[[package]] -name = "simple-logging" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00d48e85675326bb182a2286ea7c1a0b264333ae10f27a937a72be08628b542" -dependencies = [ - "lazy_static", - "log", - "thread-id", -] - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" - -[[package]] -name = "socket2" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sqlformat" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" -dependencies = [ - "itertools 0.12.0", - "nom", - "unicode_categories", -] - -[[package]] -name = "sqlx" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" -dependencies = [ - "ahash 0.8.7", - "atoi", - "byteorder", - "bytes", - "chrono", - "crc", - "crossbeam-queue", - "dotenvy", - "either", - "event-listener", - "futures-channel", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashlink", - "hex", - "indexmap 2.1.0", - "log", - "memchr", - "once_cell", - "paste", - "percent-encoding", - "rustls 0.21.10", - "rustls-pemfile", - "serde", - "serde_json", - "sha2 0.10.8", - "smallvec", - "sqlformat", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "url", - "webpki-roots", -] - -[[package]] -name = "sqlx-macros" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 1.0.109", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" -dependencies = [ - "atomic-write-file", - "dotenvy", - "either", - "heck", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2 0.10.8", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn 1.0.109", - "tempfile", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" -dependencies = [ - "atoi", - "base64 0.21.7", - "bitflags 2.4.2", - "byteorder", - "bytes", - "chrono", - "crc", - "digest 0.10.7", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac 0.12.1", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand 0.8.5", - "rsa", - "serde", - "sha1", - "sha2 0.10.8", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" -dependencies = [ - "atoi", - "base64 0.21.7", - "bitflags 2.4.2", - "byteorder", - "chrono", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "hex", - "hkdf", - "hmac 0.12.1", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand 0.8.5", - "serde", - "serde_json", - "sha1", - "sha2 0.10.8", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" -dependencies = [ - "atoi", - "chrono", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "sqlx-core", - "tracing", - "url", - "urlencoding", -] - -[[package]] -name = "sscanf" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c713ebd15ce561dd4a13ed62bc2a0368e16806fc30dcaf66ecf1256b2a3fdde6" -dependencies = [ - "const_format", - "lazy_static", - "regex", - "sscanf_macro", -] - -[[package]] -name = "sscanf_macro" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84955aa74a157e5834d58a07be11af7f0ab923f0194a0bb2ea6b3db8b5d1611d" -dependencies = [ - "convert_case 0.6.0", - "proc-macro2", - "quote", - "regex-syntax 0.6.29", - "strsim 0.10.0", - "syn 2.0.48", - "unicode-width", -] - -[[package]] -name = "ssh-cipher" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" -dependencies = [ - "cipher 0.4.4", - "ssh-encoding", -] - -[[package]] -name = "ssh-encoding" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" -dependencies = [ - "base64ct", - "pem-rfc7468", - "sha2 0.10.8", -] - -[[package]] -name = "ssh-key" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01f8f4ea73476c0aa5d5e6a75ce1e8634e2c3f82005ef3bbed21547ac57f2bf7" -dependencies = [ - "ed25519-dalek 2.1.0", - "p256", - "p384", - "p521", - "rand_core 0.6.4", - "rsa", - "sec1", - "sha2 0.10.8", - "signature 2.2.0", - "ssh-cipher", - "ssh-encoding", - "subtle", - "zeroize", -] - -[[package]] -name = "start-os" -version = "0.3.5-rev.1" -dependencies = [ - "aes", - "async-compression", - "async-stream", - "async-trait", - "axum", - "axum-server", - "base32", - "base64 0.21.7", - "base64ct", - "basic-cookies", - "blake3", - "bytes", - "chrono", - "ciborium", - "clap 4.4.18", - "color-eyre", - "console", - "cookie 0.18.0", - "cookie_store 0.20.0", - "current_platform", - "digest 0.10.7", - "divrem", - "ed25519 2.2.3", - "ed25519-dalek 1.0.1", - "ed25519-dalek 2.1.0", - "emver", - "fd-lock-rs", - "futures", - "gpt", - "helpers", - "hex", - "hmac 0.12.1", - "http 1.0.0", - "imbl", - "imbl-value", - "include_dir", - "indexmap 2.1.0", - "indicatif", - "integer-encoding", - "ipnet", - "iprange", - "isocountry", - "itertools 0.12.0", - "jaq-core", - "jaq-std", - "josekit", - "jsonpath_lib", - "lazy_async_pool", - "lazy_format", - "lazy_static", - "libc", - "log", - "mbrman", - "models", - "new_mime_guess", - "nix 0.27.1", - "nom", - "num", - "num_enum", - "once_cell", - "openssh-keys", - "openssl", - "p256", - "patch-db", - "pbkdf2", - "pin-project", - "pkcs8", - "prettytable-rs", - "proptest", - "proptest-derive", - "rand 0.8.5", - "regex", - "reqwest", - "reqwest_cookie_store", - "rpassword", - "rpc-toolkit 0.2.3 (git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits)", - "rust-argon2 2.1.0", - "rustyline-async", - "semver", - "serde", - "serde_json", - "serde_with", - "serde_yaml 0.9.30", - "sha2 0.10.8", - "shell-words", - "simple-logging", - "sqlx", - "sscanf", - "ssh-key", - "stderrlog", - "tar", - "thiserror", - "tokio", - "tokio-rustls", - "tokio-socks", - "tokio-stream", - "tokio-tar", - "tokio-tungstenite", - "tokio-util", - "toml 0.8.8", - "torut", - "tracing", - "tracing-error", - "tracing-futures", - "tracing-journald", - "tracing-subscriber", - "trust-dns-server", - "typed-builder", - "url", - "urlencoding", - "uuid", - "zeroize", -] - -[[package]] -name = "stderrlog" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69a26bbf6de627d389164afa9783739b56746c6c72c4ed16539f4ff54170327b" -dependencies = [ - "atty", - "chrono", - "log", - "termcolor", - "thread_local", -] - -[[package]] -name = "string_cache" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" -dependencies = [ - "new_debug_unreachable", - "once_cell", - "parking_lot", - "phf_shared", - "precomputed-hash", -] - -[[package]] -name = "stringprep" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" -dependencies = [ - "finl_unicode", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-xid", -] - -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tar" -version = "0.4.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" -dependencies = [ - "filetime", - "libc", - "xattr 1.3.1", -] - -[[package]] -name = "tempfile" -version = "3.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall 0.4.1", - "rustix", - "windows-sys 0.52.0", -] - -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - -[[package]] -name = "thingbuf" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4706f1bfb859af03f099ada2de3cea3e515843c2d3e93b7893f16d94a37f9415" -dependencies = [ - "parking_lot", - "pin-project", -] - -[[package]] -name = "thiserror" -version = "1.0.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "thread-id" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1" -dependencies = [ - "libc", - "redox_syscall 0.1.57", - "winapi", -] - -[[package]] -name = "thread_local" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "time" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" -dependencies = [ - "deranged", - "itoa", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" -dependencies = [ - "time-core", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.35.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "num_cpus", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-macros" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" -dependencies = [ - "rustls 0.22.2", - "rustls-pki-types", - "tokio", -] - -[[package]] -name = "tokio-socks" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" -dependencies = [ - "either", - "futures-util", - "thiserror", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", - "tokio-util", -] - -[[package]] -name = "tokio-tar" -version = "0.3.0" -source = "git+https://github.com/dr-bonez/tokio-tar.git#071db00962a66f8552d418d60be7bdf1c8ed8216" -dependencies = [ - "filetime", - "futures-core", - "libc", - "redox_syscall 0.2.16", - "tokio", - "tokio-stream", - "xattr 0.2.3", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" -dependencies = [ - "futures-util", - "log", - "native-tls", - "tokio", - "tokio-native-tls", - "tungstenite", -] - -[[package]] -name = "tokio-util" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - -[[package]] -name = "toml" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", -] - -[[package]] -name = "toml" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.21.0", -] - -[[package]] -name = "toml_datetime" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.1.0", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "toml_edit" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" -dependencies = [ - "indexmap 2.1.0", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "torut" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99febc413f26cf855b3a309c5872edff5c31e0ffe9c2fce5681868761df36f69" -dependencies = [ - "base32", - "base64 0.13.1", - "derive_more", - "ed25519-dalek 1.0.1", - "hex", - "hmac 0.11.0", - "rand 0.7.3", - "serde", - "serde_derive", - "sha2 0.9.9", - "sha3", - "tokio", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-error" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" -dependencies = [ - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - -[[package]] -name = "tracing-journald" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba316a74e8fc3c3896a850dba2375928a9fa171b085ecddfc7c054d39970f3fd" -dependencies = [ - "libc", - "tracing-core", - "tracing-subscriber", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "treediff" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" -dependencies = [ - "serde_json", -] - -[[package]] -name = "trust-dns-proto" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.4.0", - "ipnet", - "once_cell", - "rand 0.8.5", - "smallvec", - "thiserror", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "trust-dns-server" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c540f73c2b2ec2f6c54eabd0900e7aafb747a820224b742f556e8faabb461bc7" -dependencies = [ - "async-trait", - "bytes", - "cfg-if", - "drain", - "enum-as-inner", - "futures-executor", - "futures-util", - "serde", - "thiserror", - "time", - "tokio", - "toml 0.7.8", - "tracing", - "trust-dns-proto", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.0.0", - "httparse", - "log", - "native-tls", - "rand 0.8.5", - "sha1", - "thiserror", - "url", - "utf-8", -] - -[[package]] -name = "typed-builder" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444d8748011b93cb168770e8092458cb0f8854f931ff82fdf6ddfbd72a9c933e" -dependencies = [ - "typed-builder-macro", -] - -[[package]] -name = "typed-builder-macro" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "563b3b88238ec95680aef36bdece66896eaa7ce3c0f1b4f39d38fb2435261352" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - -[[package]] -name = "unarray" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" - -[[package]] -name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" - -[[package]] -name = "unicode-width" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - -[[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" - -[[package]] -name = "unsafe-libyaml" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" -dependencies = [ - "form_urlencoded", - "idna 0.5.0", - "percent-encoding", - "serde", -] - -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - -[[package]] -name = "uuid" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" -dependencies = [ - "getrandom 0.2.12", -] - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wait-timeout" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" -dependencies = [ - "libc", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.48", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" - -[[package]] -name = "wasm-streams" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" - -[[package]] -name = "whoami" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.0", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.0", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" -dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" - -[[package]] -name = "winnow" -version = "0.5.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "xattr" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" -dependencies = [ - "libc", -] - -[[package]] -name = "xattr" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" -dependencies = [ - "libc", - "linux-raw-sys", - "rustix", -] - -[[package]] -name = "yajrc" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce7af47ad983c2f8357333ef87d859e66deb7eef4bf6f9e1ae7b5e99044a48bf" -dependencies = [ - "anyhow", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - -[[package]] -name = "yasi" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f355ab62ebe30b758c1f4ab096a306722c4b7dbfb9d8c07d18c70d71a945588" -dependencies = [ - "ahash 0.8.7", - "hashbrown 0.13.2", - "lazy_static", - "serde", -] - -[[package]] -name = "zerocopy" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "zeroize" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] diff --git a/system-images/compat/Cargo.toml b/system-images/compat/Cargo.toml deleted file mode 100644 index d5fb24161..000000000 --- a/system-images/compat/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -authors = ["Aiden McClelland "] -edition = "2018" -name = "compat" -version = "0.1.0" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -anyhow = { version = "1.0.40", features = ["backtrace"] } -beau_collector = "0.2.1" -clap = "2.33.3" -dashmap = "5.3.2" -start-os = { path = "../../core/startos", default-features = false } -emver = { version = "0.1.7", git = "https://github.com/Start9Labs/emver-rs.git", features = [ - "serde", -] } -failure = "0.1.8" -indexmap = { version = "1.6.2", features = ["serde"] } -imbl-value = "0.1.2" -itertools = "0.10.0" -lazy_static = "1.4" -linear-map = { version = "1.2", features = ["serde_impl"] } -log = "0.4.11" -nix = "0.25.0" -pest = "2.1" -pest_derive = "2.1" -rand = "0.8.5" -regex = "1.4.2" -rust-argon2 = "1.0.0" -serde = { version = "1.0.118", features = ["derive", "rc"] } -serde_json = "1.0.67" -serde_yaml = "0.8.17" diff --git a/system-images/compat/Dockerfile b/system-images/compat/Dockerfile deleted file mode 100644 index c01248b04..000000000 --- a/system-images/compat/Dockerfile +++ /dev/null @@ -1 +0,0 @@ -FROM start9/compat \ No newline at end of file diff --git a/system-images/compat/Makefile b/system-images/compat/Makefile deleted file mode 100644 index b6cd1bfec..000000000 --- a/system-images/compat/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -COMPAT_SRC := $(shell find ./src) Cargo.toml Cargo.lock - -.DELETE_ON_ERROR: - -all: docker-images/aarch64.tar docker-images/x86_64.tar - -clean: - cargo clean - rm -rf docker-images - -docker-images: - mkdir docker-images - -docker-images/aarch64.tar: Dockerfile docker-images - docker buildx build --build-arg ARCH=aarch64 --tag start9/x_system/compat --platform=linux/arm64 -o type=docker,dest=docker-images/aarch64.tar . - -docker-images/x86_64.tar: Dockerfile docker-images - docker buildx build --build-arg ARCH=x86_64 --tag start9/x_system/compat --platform=linux/amd64 -o type=docker,dest=docker-images/x86_64.tar . - -target/aarch64-unknown-linux-musl/release/compat: $(COMPAT_SRC) ../../core/Cargo.lock - ARCH=aarch64 ./build.sh - -target/x86_64-unknown-linux-musl/release/compat: $(COMPAT_SRC) ../../core/Cargo.lock - ARCH=x86_64 ./build.sh diff --git a/system-images/compat/build.sh b/system-images/compat/build.sh deleted file mode 100755 index d84f2657b..000000000 --- a/system-images/compat/build.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -set -e -shopt -s expand_aliases - -if [ -z "$ARCH" ]; then - ARCH=$(uname -m) -fi - -if [ "$0" != "./build.sh" ]; then - >&2 echo "Must be run from compat directory" - exit 1 -fi - -USE_TTY= -if tty -s; then - USE_TTY="-it" -fi - -alias 'rust-musl-builder'='docker run $USE_TTY --rm -v "$HOME"/.cargo/registry:/root/.cargo/registry -v "$(pwd)":/home/rust/src messense/rust-musl-cross:${ARCH}-musl' - -cd ../.. -rust-musl-builder sh -c "(git config --global --add safe.directory '*'; cd system-images/compat && cargo build --release --target=${ARCH}-unknown-linux-musl --no-default-features)" -cd system-images/compat - -sudo chown -R $USER target -sudo chown -R $USER ~/.cargo \ No newline at end of file diff --git a/system-images/compat/src/backup.rs b/system-images/compat/src/backup.rs deleted file mode 100644 index 9e54100f8..000000000 --- a/system-images/compat/src/backup.rs +++ /dev/null @@ -1,79 +0,0 @@ -use std::{path::Path, process::Stdio}; - -use startos::disk::main::DEFAULT_PASSWORD; - -pub fn create_backup( - mountpoint: impl AsRef, - data_path: impl AsRef, -) -> Result<(), anyhow::Error> { - let mountpoint = std::fs::canonicalize(mountpoint)?; - let data_path = std::fs::canonicalize(data_path)?; - - let ignore_path = data_path.join(".backupignore"); - let exclude = if ignore_path.is_file() { - std::fs::read_to_string(ignore_path)? - } else { - String::new() - }; - - let mut data_cmd = std::process::Command::new("duplicity"); - for exclude in exclude.lines().map(|s| s.trim()).filter(|s| !s.is_empty()) { - if exclude.to_string().starts_with('!') { - data_cmd - .arg(format!( - "--include={}", - data_path - .join(exclude.to_string().trim_start_matches('!')) - .display() - )) - .arg("--allow-source-mismatch"); - } else { - data_cmd - .arg(format!( - "--exclude={}", - data_path.join(exclude.to_string()).display() - )) - .arg("--allow-source-mismatch"); - } - } - let data_output = data_cmd - .env("PASSPHRASE", DEFAULT_PASSWORD) - .arg(data_path) - .arg(format!("file://{}", mountpoint.display().to_string())) - .arg("--allow-source-mismatch") - .stderr(Stdio::piped()) - .output()?; - if !data_output.status.success() { - return Err(anyhow::anyhow!( - "duplicity error: {}", - String::from_utf8(data_output.stderr).unwrap() - )); - } - - Ok(()) -} - -pub fn restore_backup( - mountpoint: impl AsRef, - data_path: impl AsRef, -) -> Result<(), anyhow::Error> { - let mountpoint = std::fs::canonicalize(mountpoint)?; - let data_path = std::fs::canonicalize(data_path)?; - - let data_output = std::process::Command::new("duplicity") - .arg("--allow-source-mismatch") - .env("PASSPHRASE", DEFAULT_PASSWORD) - .arg("--force") - .arg(format!("file://{}", mountpoint.display().to_string())) - .arg(&data_path) - .stderr(Stdio::piped()) - .output()?; - if !data_output.status.success() { - return Err(anyhow::anyhow!( - "duplicity error: {}", - String::from_utf8(data_output.stderr).unwrap() - )); - } - - Ok(()) -} diff --git a/system-images/compat/src/config/mod.rs b/system-images/compat/src/config/mod.rs deleted file mode 100644 index c677dd3d5..000000000 --- a/system-images/compat/src/config/mod.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::borrow::Cow; -use std::collections::{BTreeMap, BTreeSet, HashMap}; -use std::path::Path; - -use beau_collector::BeauCollector; -use linear_map::LinearMap; -use startos::config::action::SetResult; -use startos::config::{spec, Config}; -use startos::s9pk::manifest::PackageId; -use startos::status::health_check::HealthCheckId; - -pub mod rules; - -use anyhow::anyhow; -pub use rules::{ConfigRuleEntry, ConfigRuleEntryWithSuggestions}; - -use self::rules::ConfigRule; -pub type DepInfo = HashMap; -#[derive(Clone, Debug, serde::Deserialize)] -pub struct DepRuleInfo { - condition: ConfigRule, - health_checks: BTreeSet, -} - -pub fn validate_configuration( - name: &str, - config: Config, - rules_path: &Path, - config_path: &Path, - maybe_deps_path: Option<&str>, -) -> Result { - let rules: Vec = serde_yaml::from_reader(std::fs::File::open(rules_path)?)?; - let mut cfgs = LinearMap::new(); - cfgs.insert(name, Cow::Borrowed(&config)); - - let mut depends_on = BTreeMap::new(); - if let Some(deps_path) = maybe_deps_path.map(Path::new) { - if deps_path.exists() { - let deps: DepInfo = serde_yaml::from_reader(std::fs::File::open(deps_path)?)?; - // check if new config is set to depend on any optional dependencies - depends_on.extend( - deps.into_iter() - .filter(|(_, data)| (data.condition.compiled)(&config, &cfgs)) - .map(|(pkg_id, data)| (pkg_id, data.health_checks)), - ); - }; - } - - // check that all configuration rules - let rule_check = rules - .into_iter() - .map(|r| r.check(&config, &cfgs)) - .bcollect::>(); - match rule_check { - Ok(_) => { - // create temp config file - serde_yaml::to_writer( - std::fs::create_file(config_path.with_extension("tmp"))?, - &config, - )?; - std::fs::rename(config_path.with_extension("tmp"), config_path)?; - // return set result - Ok(SetResult { depends_on }) - } - Err(e) => Err(anyhow!("{}", e)), - } -} - -pub fn validate_dependency_configuration( - name: &str, - config: &Option, - parent_name: &str, - parent_config: Config, - rules_path: &Path, -) -> Result<(), anyhow::Error> { - let rules: Vec = serde_yaml::from_reader(std::fs::File::open(rules_path)?)?; - let mut cfgs = LinearMap::new(); - cfgs.insert(parent_name, Cow::Borrowed(&parent_config)); - if let Some(config) = config { - cfgs.insert(name, Cow::Borrowed(&config)) - } else { - cfgs.insert(name, Cow::Owned(imbl_value::InOMap::new())) - }; - let rule_check = rules - .into_iter() - .map(|r| r.check(&parent_config, &cfgs)) - .bcollect::>(); - match rule_check { - Ok(_) => Ok(()), - Err(e) => Err(anyhow!("{}", e)), - } -} - -pub fn apply_dependency_configuration( - package_id: &str, - config: Option, - dependency_id: &str, - mut dep_config: Config, - rules_path: &Path, -) -> Result { - let rules: Vec = - serde_yaml::from_reader(std::fs::File::open(rules_path)?)?; - let mut cfgs = LinearMap::new(); - cfgs.insert(dependency_id, Cow::Owned(dep_config.clone())); - match config { - Some(config) => cfgs.insert(package_id, Cow::Owned(config.clone())), - None => cfgs.insert(package_id, Cow::Owned(imbl_value::InOMap::new())), - }; - let rule_check = rules - .into_iter() - .map(|r| r.apply(dependency_id, &mut dep_config, &mut cfgs)) - .bcollect::>(); - match rule_check { - Ok(_) => Ok(dep_config), - Err(e) => Err(anyhow!("{}", e)), - } -} diff --git a/system-images/compat/src/config/rule_parser.pest b/system-images/compat/src/config/rule_parser.pest deleted file mode 100644 index 53be53252..000000000 --- a/system-images/compat/src/config/rule_parser.pest +++ /dev/null @@ -1,76 +0,0 @@ -num = @{ int ~ ("." ~ ASCII_DIGIT*)? ~ (^"e" ~ int)? } - int = @{ ("+" | "-")? ~ ASCII_DIGIT+ } - -raw_string = @{ (!("\\" | "\"") ~ ANY)+ } -predefined = @{ "n" | "r" | "t" | "\\" | "0" | "\"" | "'" } -escape = @{ "\\" ~ predefined } -str = @{ "\"" ~ (raw_string | escape)* ~ "\"" } - -ident_char = @{ ASCII_ALPHANUMERIC | "-" } -sub_ident = _{ sub_ident_regular | sub_ident_index | sub_ident_any | sub_ident_all | sub_ident_fn } - sub_ident_regular = { sub_ident_regular_base | sub_ident_regular_expr } - sub_ident_regular_base = @{ ASCII_ALPHA ~ ident_char* } - sub_ident_regular_expr = ${ "[" ~ str_expr ~ "]" } - sub_ident_index = { sub_ident_index_base | sub_ident_index_expr } - sub_ident_index_base = @{ ASCII_DIGIT+ } - sub_ident_index_expr = ${ "[" ~ num_expr ~ "]" } - sub_ident_any = @{ "*" } - sub_ident_all = @{ "&" } - sub_ident_fn = ${ "[" ~ list_access_function ~ "]"} - list_access_function = _{ list_access_function_first | list_access_function_last | list_access_function_any | list_access_function_all } - list_access_function_first = !{ "first" ~ "(" ~ sub_ident_regular ~ "=>" ~ bool_expr ~ ")" } - list_access_function_last = !{ "last" ~ "(" ~ sub_ident_regular ~ "=>" ~ bool_expr ~ ")" } - list_access_function_any = !{ "any" ~ "(" ~ sub_ident_regular ~ "=>" ~ bool_expr ~ ")" } - list_access_function_all = !{ "all" ~ "(" ~ sub_ident_regular ~ "=>" ~ bool_expr ~ ")" } - -app_id = ${ "[" ~ sub_ident_regular ~ "]" } -ident = _{ (app_id ~ ".")? ~ sub_ident_regular ~ ("." ~ sub_ident)* } -bool_var = ${ ident ~ "?" } -num_var = ${ "#" ~ ident } -str_var = ${ "'" ~ ident } -any_var = ${ ident } - -bool_op = _{ and | or | xor } - and = { "AND" } - or = { "OR" } - xor = { "XOR" } - -num_cmp_op = _{ lt | lte | eq | neq | gt | gte } -str_cmp_op = _{ lt | lte | eq | neq | gt | gte } - lt = { "<" } - lte = { "<=" } - eq = { "=" } - neq = { "!=" } - gt = { ">" } - gte = { ">=" } - -num_op = _{ add | sub | mul | div | pow } -str_op = _{ add } - add = { "+" } - sub = { "-" } - mul = { "*" } - div = { "/" } - pow = { "^" } - -num_expr = !{ num_term ~ (num_op ~ num_term)* } -num_term = _{ num | num_var | "(" ~ num_expr ~ ")" } - -str_expr = !{ str_term ~ (str_op ~ str_term)* } -str_term = _{ str | str_var | "(" ~ str_expr ~ ")" } - -num_cmp_expr = { num_expr ~ num_cmp_op ~ num_expr } -str_cmp_expr = { str_expr ~ str_cmp_op ~ str_expr } - -bool_expr = !{ bool_term ~ (bool_op ~ bool_term)* } -inv_bool_expr = { "!(" ~ bool_expr ~ ")" } -bool_term = _{ bool_var | "(" ~ bool_expr ~ ")" | inv_bool_expr | num_cmp_expr | str_cmp_expr } - -val_expr = _{ any_var | str_expr | num_expr | bool_expr } - -rule = _{ SOI ~ bool_expr ~ EOI } -reference = _{ SOI ~ any_var ~ EOI } -value = _{ SOI ~ val_expr ~ EOI } -del_action = _{ SOI ~ "FROM" ~ any_var ~ "AS" ~ sub_ident_regular ~ "WHERE" ~ bool_expr ~ EOI } -obj_key = _{ SOI ~ sub_ident_regular ~ EOI } - -WHITESPACE = _{ " " | "\t" } \ No newline at end of file diff --git a/system-images/compat/src/config/rules.rs b/system-images/compat/src/config/rules.rs deleted file mode 100644 index a74ae195c..000000000 --- a/system-images/compat/src/config/rules.rs +++ /dev/null @@ -1,1261 +0,0 @@ -use std::borrow::Cow; -use std::sync::Arc; - -use imbl_value::{InternedString, Value}; -use linear_map::LinearMap; -use pest::iterators::Pairs; -use pest::Parser; -use rand::SeedableRng; - -use startos::config::util::STATIC_NULL; -use startos::config::Config; - -#[derive(Parser)] -#[grammar = "config/rule_parser.pest"] -struct RuleParser; - -lazy_static::lazy_static! { - static ref NUM_PREC_CLIMBER: pest::prec_climber::PrecClimber = { - use pest::prec_climber::*; - use Rule::*; - use Assoc::*; - - PrecClimber::new(vec![ - Operator::new(add, Left) | Operator::new(sub, Left), - Operator::new(mul, Left) | Operator::new(div, Left), - Operator::new(pow, Right) - ]) - }; - - static ref STR_PREC_CLIMBER: pest::prec_climber::PrecClimber = { - use pest::prec_climber::*; - use Rule::*; - use Assoc::*; - - PrecClimber::new(vec![ - Operator::new(add, Left) - ]) - }; - - static ref BOOL_PREC_CLIMBER: pest::prec_climber::PrecClimber = { - use pest::prec_climber::*; - use Rule::*; - use Assoc::*; - - PrecClimber::new(vec![ - Operator::new(or, Left), - Operator::new(xor, Left), - Operator::new(and, Left) - ]) - }; -} - -pub type Accessor = Box< - dyn for<'a> Fn(&'a Value, &LinearMap<&str, Cow>) -> VarRes<&'a Value> + Send + Sync, ->; -pub type AccessorMut = Box< - dyn for<'a> Fn(&'a mut Value, &LinearMap<&str, Cow>) -> Option<&'a mut Value> - + Send - + Sync, ->; -pub type CompiledExpr = Box>) -> T + Send + Sync>; -pub type CompiledReference = Box< - dyn for<'a> Fn(&'a mut Config, &LinearMap<&str, Cow>) -> Option<&'a mut Value> - + Send - + Sync, ->; -pub type Mutator = Box>) + Send + Sync>; -pub type CompiledRule = Box>) -> bool + Send + Sync>; -pub type CompiledRuleRes = Result; - -#[derive(Clone)] -pub struct ConfigRule { - pub src: String, - pub compiled: Arc, -} -impl std::fmt::Debug for ConfigRule { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ConfigRule") - .field("src", &self.src) - .field("compiled", &"Fn(&Config, &Config) -> bool") - .finish() - } -} -impl<'de> serde::de::Deserialize<'de> for ConfigRule { - fn deserialize(deserializer: D) -> Result - where - D: serde::de::Deserializer<'de>, - { - let src = String::deserialize(deserializer)?; - let compiled = compile(&src).map_err(serde::de::Error::custom)?; - Ok(ConfigRule { - src, - compiled: Arc::new(compiled), - }) - } -} -impl serde::ser::Serialize for ConfigRule { - fn serialize(&self, serializer: S) -> Result - where - S: serde::ser::Serializer, - { - serializer.serialize_str(&self.src) - } -} -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct ConfigRuleEntry { - pub rule: ConfigRule, - pub description: String, -} -impl ConfigRuleEntry { - pub fn check( - &self, - cfg: &Config, - cfgs: &LinearMap<&str, Cow>, - ) -> Result<(), anyhow::Error> { - if !(self.rule.compiled)(cfg, cfgs) { - return Err(anyhow::anyhow!("{}", self.description)); - } - Ok(()) - } -} - -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "kebab-case")] -pub enum SetVariant { - To(String), - ToValue(Value), - ToEntropy(super::spec::Entropy), -} - -#[derive(Clone)] -pub enum SuggestionVariant { - Set { - var: String, - to: SetVariant, - compiled: Arc, - }, - Delete { - src: String, - compiled: Arc, - }, - Push { - to: String, - value: Value, - compiled: Arc, - }, -} -impl SuggestionVariant { - pub fn apply<'a>( - &self, - id: &'a str, - cfg: &mut Config, - cfgs: &mut LinearMap<&'a str, Cow>, - ) { - match self { - SuggestionVariant::Set { ref compiled, .. } => compiled(cfg, cfgs), - SuggestionVariant::Delete { ref compiled, .. } => compiled(cfg, cfgs), - SuggestionVariant::Push { ref compiled, .. } => compiled(cfg, cfgs), - } - cfgs.insert(id, Cow::Owned(cfg.clone())); - } -} -impl std::fmt::Debug for SuggestionVariant { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - SuggestionVariant::Set { - ref var, ref to, .. - } => f - .debug_struct("SuggestionVariant::Set") - .field("var", var) - .field("to", to) - .field("compiled", &"Fn(&mut Config, Config)") - .finish(), - SuggestionVariant::Delete { ref src, .. } => f - .debug_struct("SuggestionVariant::Delete") - .field("src", src) - .field("compiled", &"Fn(&mut Config, Config)") - .finish(), - SuggestionVariant::Push { - ref to, ref value, .. - } => f - .debug_struct("SuggestionVariant::Delete") - .field("to", to) - .field("value", value) - .field("compiled", &"Fn(&mut Config, Config)") - .finish(), - } - } -} -impl<'de> serde::de::Deserialize<'de> for SuggestionVariant { - fn deserialize(deserializer: D) -> Result - where - D: serde::de::Deserializer<'de>, - { - #[derive(serde::Deserialize)] - enum _SuggestionVariant { - SET { - var: String, - #[serde(flatten)] - to: SetVariant, - }, - DELETE(String), - PUSH { - to: String, - value: Value, - }, - } - let raw = _SuggestionVariant::deserialize(deserializer)?; - Ok(match raw { - _SuggestionVariant::SET { var, to } => SuggestionVariant::Set { - compiled: Arc::new( - compile_set_action(&var, &to).map_err(serde::de::Error::custom)?, - ), - to: to, - var: var, - }, - _SuggestionVariant::DELETE(src) => SuggestionVariant::Delete { - compiled: Arc::new( - compile_del_action( - RuleParser::parse(Rule::del_action, &src) - .map_err(serde::de::Error::custom)?, - ) - .map_err(serde::de::Error::custom)?, - ), - src, - }, - _SuggestionVariant::PUSH { to, value } => SuggestionVariant::Push { - compiled: Arc::new( - compile_push_action( - RuleParser::parse(Rule::reference, &to) - .map_err(serde::de::Error::custom)?, - value.clone(), - ) - .map_err(serde::de::Error::custom)?, - ), - to, - value, - }, - }) - } -} -impl serde::ser::Serialize for SuggestionVariant { - fn serialize(&self, serializer: S) -> Result - where - S: serde::ser::Serializer, - { - #[derive(serde::Serialize)] - enum _SuggestionVariant<'a> { - SET { - var: &'a str, - #[serde(flatten)] - to: &'a SetVariant, - }, - DELETE(&'a str), - PUSH { - to: &'a str, - value: &'a Value, - }, - } - match self { - SuggestionVariant::Set { - ref var, ref to, .. - } => serde::ser::Serialize::serialize(&_SuggestionVariant::SET { var, to }, serializer), - SuggestionVariant::Delete { ref src, .. } => { - serde::ser::Serialize::serialize(&_SuggestionVariant::DELETE(src), serializer) - } - SuggestionVariant::Push { - ref to, ref value, .. - } => serde::ser::Serialize::serialize( - &_SuggestionVariant::PUSH { to, value }, - serializer, - ), - } - } -} -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct Suggestion { - #[serde(rename = "if")] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - pub condition: Option, - #[serde(flatten)] - pub variant: SuggestionVariant, -} -impl Suggestion { - pub fn apply<'a>( - &self, - id: &'a str, - cfg: &mut Config, - cfgs: &mut LinearMap<&'a str, Cow>, - ) { - match &self.condition { - Some(condition) if !(condition.compiled)(cfg, cfgs) => (), - _ => self.variant.apply(id, cfg, cfgs), - } - } -} -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct ConfigRuleEntryWithSuggestions { - #[serde(flatten)] - pub entry: ConfigRuleEntry, - pub suggestions: Vec, -} -impl ConfigRuleEntryWithSuggestions { - pub fn apply<'a>( - &self, - id: &'a str, - cfg: &mut Config, - cfgs: &mut LinearMap<&'a str, Cow>, - ) -> Result<(), anyhow::Error> { - if self.entry.check(cfg, cfgs).is_err() { - for suggestion in &self.suggestions { - suggestion.apply(id, cfg, cfgs); - } - self.entry.check(cfg, cfgs) - } else { - Ok(()) - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum VarRes { - Exactly(T), - Any(Vec>), - All(Vec>), -} -impl VarRes { - fn map U>(self, mut f: F) -> VarRes { - fn map_rec U>(s: VarRes, f: &mut F) -> VarRes { - match s { - VarRes::Exactly(a) => VarRes::Exactly(f(a)), - VarRes::Any(a) => VarRes::Any(a.into_iter().map(|a| map_rec(a, f)).collect()), - VarRes::All(a) => VarRes::All(a.into_iter().map(|a| map_rec(a, f)).collect()), - } - } - map_rec(self, &mut f) - } - fn and_then VarRes>(self, mut f: F) -> VarRes { - fn and_then_rec VarRes>(s: VarRes, f: &mut F) -> VarRes { - match s { - VarRes::Exactly(a) => f(a), - VarRes::Any(a) => VarRes::Any(a.into_iter().map(|a| and_then_rec(a, f)).collect()), - VarRes::All(a) => VarRes::All(a.into_iter().map(|a| and_then_rec(a, f)).collect()), - } - } - and_then_rec(self, &mut f) - } -} -impl VarRes { - fn resolve(self) -> bool { - match self { - VarRes::Exactly(a) => a, - VarRes::Any(a) => a.into_iter().any(|a| a.resolve()), - VarRes::All(a) => a.into_iter().all(|a| a.resolve()), - } - } -} - -fn compile_var_rec(mut ident: Pairs) -> Option { - let idx = ident.next(); - if let Some(idx) = idx { - let deref: Accessor = match idx.as_rule() { - Rule::sub_ident_any => Box::new(|v, _| match v { - Value::Array(l) => VarRes::Any(l.iter().map(VarRes::Exactly).collect()), - Value::Object(o) => { - VarRes::Any(o.iter().map(|(_, a)| VarRes::Exactly(a)).collect()) - } - _ => VarRes::Exactly(&STATIC_NULL), - }), - Rule::sub_ident_all => Box::new(|v, _| match v { - Value::Array(l) => VarRes::All(l.iter().map(VarRes::Exactly).collect()), - Value::Object(o) => { - VarRes::All(o.iter().map(|(_, a)| VarRes::Exactly(a)).collect()) - } - _ => VarRes::Exactly(&STATIC_NULL), - }), - Rule::sub_ident_fn => { - let idx = idx.into_inner().next().unwrap(); - match idx.as_rule() { - Rule::list_access_function_first => { - let mut pred_iter = idx.into_inner(); - let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); - let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); - Box::new(move |v, cfgs| match v { - Value::Array(l) => VarRes::Exactly( - l.iter() - .filter(|item| { - let mut cfg = Config::default(); - cfg.insert(item_var.clone(), (*item).clone()); - predicate(&cfg, cfgs) - }) - .next() - .unwrap_or(&STATIC_NULL), - ), - Value::Object(o) => VarRes::Exactly( - &o.iter() - .map(|(_, item)| item) - .filter(|item| { - let mut cfg = Config::default(); - cfg.insert(item_var.clone(), (*item).clone()); - predicate(&cfg, cfgs) - }) - .next() - .unwrap_or(&STATIC_NULL), - ), - _ => VarRes::Exactly(&STATIC_NULL), - }) - } - Rule::list_access_function_last => { - let mut pred_iter = idx.into_inner(); - let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); - let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); - Box::new(move |v, cfgs| match v { - Value::Array(l) => VarRes::Exactly( - l.iter() - .filter(|item| { - let mut cfg = Config::default(); - cfg.insert(item_var.clone(), (*item).clone()); - predicate(&cfg, cfgs) - }) - .next_back() - .unwrap_or(&STATIC_NULL), - ), - Value::Object(o) => VarRes::Exactly( - &o.iter() - .map(|(_, item)| item) - .filter(|item| { - let mut cfg = Config::default(); - cfg.insert(item_var.clone(), (*item).clone()); - predicate(&cfg, cfgs) - }) - .next_back() - .unwrap_or(&STATIC_NULL), - ), - _ => VarRes::Exactly(&STATIC_NULL), - }) - } - Rule::list_access_function_any => { - let mut pred_iter = idx.into_inner(); - let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); - let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); - Box::new(move |v, cfgs| match v { - Value::Array(l) => VarRes::Any( - l.iter() - .filter(|item| { - let mut cfg = Config::default(); - cfg.insert(item_var.clone(), (*item).clone()); - predicate(&cfg, cfgs) - }) - .map(VarRes::Exactly) - .collect(), - ), - Value::Object(o) => VarRes::Any( - o.iter() - .map(|(_, item)| item) - .filter(|item| { - let mut cfg = Config::default(); - cfg.insert(item_var.clone(), (*item).clone()); - predicate(&cfg, cfgs) - }) - .map(VarRes::Exactly) - .collect(), - ), - _ => VarRes::Exactly(&STATIC_NULL), - }) - } - Rule::list_access_function_all => { - let mut pred_iter = idx.into_inner(); - let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); - let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); - Box::new(move |v, cfgs| match v { - Value::Array(l) => VarRes::All( - l.iter() - .filter(|item| { - let mut cfg = Config::default(); - cfg.insert(item_var.clone(), (*item).clone()); - predicate(&cfg, cfgs) - }) - .map(VarRes::Exactly) - .collect(), - ), - Value::Object(o) => VarRes::All( - o.iter() - .map(|(_, item)| item) - .filter(|item| { - let mut cfg = Config::default(); - cfg.insert(item_var.clone(), (*item).clone()); - predicate(&cfg, cfgs) - }) - .map(VarRes::Exactly) - .collect(), - ), - _ => VarRes::Exactly(&STATIC_NULL), - }) - } - _ => unreachable!(), - } - } - Rule::sub_ident_regular => { - let idx = idx.into_inner().next().unwrap(); - match idx.as_rule() { - Rule::sub_ident_regular_base => { - let idx = idx.as_str().to_owned(); - Box::new(move |v, _| match v { - Value::Object(o) => { - VarRes::Exactly(o.get(&*idx).unwrap_or(&STATIC_NULL)) - } - _ => VarRes::Exactly(&STATIC_NULL), - }) - } - Rule::sub_ident_regular_expr => { - let idx = compile_str_expr(idx.into_inner().next().unwrap().into_inner()); - Box::new(move |v, dep_cfg| match v { - Value::Object(o) => idx(&Config::default(), dep_cfg).map(|idx| { - idx.and_then(|idx| o.get(&*idx)).unwrap_or(&STATIC_NULL) - }), - _ => VarRes::Exactly(&STATIC_NULL), - }) - } - _ => unreachable!(), - } - } - Rule::sub_ident_index => { - let idx = idx.into_inner().next().unwrap(); - match idx.as_rule() { - Rule::sub_ident_index_base => { - let idx: usize = idx.as_str().parse().unwrap(); - Box::new(move |v, _| match v { - Value::Array(l) => VarRes::Exactly(l.get(idx).unwrap_or(&STATIC_NULL)), - _ => VarRes::Exactly(&STATIC_NULL), - }) - } - Rule::sub_ident_index_expr => { - let idx = compile_num_expr(idx.into_inner().next().unwrap().into_inner()); - Box::new(move |v, dep_cfg| match v { - Value::Array(l) => idx(&Config::default(), dep_cfg) - .map(|idx| l.get(idx as usize).unwrap_or(&STATIC_NULL)), - _ => VarRes::Exactly(&STATIC_NULL), - }) - } - _ => unreachable!(), - } - } - _ => unreachable!(), - }; - Some(if let Some(rest) = compile_var_rec(ident) { - Box::new(move |v, cfgs| deref(v, cfgs).and_then(|v| rest(v, cfgs))) - } else { - deref - }) - } else { - None - } -} - -fn compile_var(mut var: Pairs) -> CompiledExpr> { - let mut first_seg = var.next().unwrap(); - let app_id = if first_seg.as_rule() == Rule::app_id { - let app_id = first_seg.into_inner().next().unwrap().as_str().to_owned(); - first_seg = var.next().unwrap(); - Some(app_id) - } else { - None - }; - let first_seg_string = first_seg.as_str().to_owned(); - let accessor = compile_var_rec(var); - Box::new(move |cfg, cfgs| { - let mut cfg: &Config = cfg; - if let Some(ref app_id) = app_id { - cfg = if let Some(cfg) = cfgs.get(&app_id.as_str()) { - cfg - } else { - return VarRes::Exactly(Value::Null); - }; - } - let val = cfg.get(&*first_seg_string).unwrap_or(&STATIC_NULL); - if let Some(accessor) = &accessor { - accessor(val, cfgs).map(|v| v.clone()) - } else { - VarRes::Exactly(val.clone()) - } - }) -} - -fn compile_var_mut_rec(mut ident: Pairs) -> Result, failure::Error> { - let idx = ident.next(); - Ok(if let Some(idx) = idx { - let deref: AccessorMut = match idx.as_rule() { - Rule::sub_ident_fn => { - let idx = idx.into_inner().next().unwrap(); - match idx.as_rule() { - Rule::list_access_function_first => { - let mut pred_iter = idx.into_inner(); - let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); - let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); - Box::new(move |v, cfgs| match v { - Value::Array(l) => l - .iter_mut() - .filter(|item| { - let mut cfg = Config::default(); - cfg.insert(item_var.clone(), (*item).clone()); - predicate(&cfg, cfgs) - }) - .next(), - Value::Object(o) => o - .iter_mut() - .map(|(_, item)| item) - .filter(|item| { - let mut cfg = Config::default(); - cfg.insert(item_var.clone(), (*item).clone()); - predicate(&cfg, cfgs) - }) - .next(), - _ => None, - }) - } - Rule::list_access_function_last => { - let mut pred_iter = idx.into_inner(); - let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); - let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); - Box::new(move |v, cfgs| match v { - Value::Array(l) => l - .iter_mut() - .filter(|item| { - let mut cfg = Config::default(); - cfg.insert(item_var.clone(), (*item).clone()); - predicate(&cfg, cfgs) - }) - .next_back(), - Value::Object(o) => o - .iter_mut() - .map(|(_, item)| item) - .filter(|item| { - let mut cfg = Config::default(); - cfg.insert(item_var.clone(), (*item).clone()); - predicate(&cfg, cfgs) - }) - .next_back(), - _ => None, - }) - } - Rule::list_access_function_any | Rule::list_access_function_all => { - failure::bail!("Any and All are immutable") - } - _ => unreachable!(), - } - } - Rule::sub_ident_regular => { - let idx = idx.into_inner().next().unwrap(); - match idx.as_rule() { - Rule::sub_ident_regular_base => { - let idx: InternedString = idx.as_str().into(); - Box::new(move |v, _| match v { - Value::Object(ref mut o) => { - if o.contains_key(&*idx) { - o.get_mut(&*idx) - } else { - o.insert(idx.clone(), Value::Null); - o.get_mut(&*idx) - } - } - _ => None, - }) - } - Rule::sub_ident_regular_expr => { - let idx = compile_str_expr(idx.into_inner().next().unwrap().into_inner()); - Box::new( - move |v, dep_cfg| match (v, idx(&Config::default(), dep_cfg)) { - (Value::Object(ref mut o), VarRes::Exactly(Some(ref idx))) => { - if o.contains_key(&**idx) { - o.get_mut(&**idx) - } else { - o.insert(idx.clone(), Value::Null); - o.get_mut(&**idx) - } - } - _ => None, - }, - ) - } - _ => unreachable!(), - } - } - Rule::sub_ident_index => { - let idx = idx.into_inner().next().unwrap(); - match idx.as_rule() { - Rule::sub_ident_index_base => { - let idx: usize = idx.as_str().parse().unwrap(); - Box::new(move |v, _| match v { - Value::Array(l) => { - if l.len() > idx { - l.get_mut(idx) - } else if idx == l.len() { - l.push_back(Value::Null); - l.get_mut(idx) - } else { - None - } - } - _ => None, - }) - } - Rule::sub_ident_index_expr => { - let idx = compile_num_expr(idx.into_inner().next().unwrap().into_inner()); - Box::new( - move |v, dep_cfg| match (v, idx(&Config::default(), dep_cfg)) { - (Value::Array(l), VarRes::Exactly(idx)) => { - let idx = idx as usize; - if l.len() > idx { - l.get_mut(idx) - } else if idx == l.len() { - l.push_back(Value::Null); - l.get_mut(idx) - } else { - None - } - } - _ => None, - }, - ) - } - _ => unreachable!(), - } - } - _ => failure::bail!("invalid token: {:?}", idx.as_rule()), - }; - Some(if let Some(rest) = compile_var_mut_rec(ident)? { - Box::new(move |v, cfgs| deref(v, cfgs).and_then(|v| rest(v, cfgs))) - } else { - deref - }) - } else { - None - }) -} - -fn compile_var_mut(mut var: Pairs) -> Result { - let first_seg = var.next().unwrap(); - if first_seg.as_rule() == Rule::app_id { - failure::bail!("Can only assign to relative path"); - } - let first_seg_string: InternedString = first_seg.as_str().into(); - let accessor_mut = compile_var_mut_rec(var)?; - Ok(Box::new(move |cfg, cfgs| { - let var = if cfg.contains_key(&*first_seg_string) { - cfg.get_mut(&*first_seg_string).unwrap() - } else { - cfg.insert(first_seg_string.clone(), Value::Null); - cfg.get_mut(&first_seg_string).unwrap() - }; - if let Some(accessor_mut) = &accessor_mut { - accessor_mut(var, cfgs) - } else { - Some(var) - } - })) -} - -fn compile_bool_var(var: Pairs) -> CompiledRule { - let var = compile_var(var); - Box::new(move |cfg, cfgs| { - var(cfg, cfgs) - .map(|a| match a { - Value::Bool(false) | Value::Null => false, - _ => true, - }) - .resolve() - }) -} - -fn compile_num_var(var: Pairs) -> CompiledExpr> { - let var = compile_var(var); - Box::new(move |cfg, cfgs| { - var(cfg, cfgs).map(|a| match a { - Value::Number(n) => n.as_f64().unwrap(), - Value::String(s) => match s.parse() { - Ok(n) => n, - Err(_) => panic!("string cannot be parsed as an f64"), - }, - Value::Bool(b) => { - if b { - 1.0 - } else { - 0.0 - } - } - _ => panic!("object or list cannot be parsed as an f64"), - }) - }) -} - -fn compile_num(num_str: &str) -> CompiledExpr> { - let num = VarRes::Exactly(num_str.parse().unwrap()); - Box::new(move |_, _| num.clone()) -} - -fn compile_num_expr(pairs: Pairs) -> CompiledExpr> { - NUM_PREC_CLIMBER.climb( - pairs, - |pair| match pair.as_rule() { - Rule::num_var => compile_num_var(pair.into_inner()), - Rule::num => compile_num(pair.as_str()), - Rule::num_expr => compile_num_expr(pair.into_inner()), - _ => unreachable!(), - }, - |lhs, op, rhs| match op.as_rule() { - Rule::add => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs).and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs + rhs)) - }), - Rule::sub => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs).and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs - rhs)) - }), - Rule::mul => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs).and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs * rhs)) - }), - Rule::div => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs).and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs / rhs)) - }), - Rule::pow => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs).and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs.powf(rhs))) - }), - _ => unreachable!(), - }, - ) -} - -fn compile_num_cmp_expr(mut pairs: Pairs) -> CompiledRule { - let lhs = compile_num_expr(pairs.next().unwrap().into_inner()); - let op = pairs.next().unwrap(); - let rhs = compile_num_expr(pairs.next().unwrap().into_inner()); - match op.as_rule() { - Rule::lt => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs) - .and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs < rhs)) - .resolve() - }), - Rule::lte => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs) - .and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs <= rhs)) - .resolve() - }), - Rule::eq => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs) - .and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs == rhs)) - .resolve() - }), - Rule::neq => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs) - .and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs != rhs)) - .resolve() - }), - Rule::gt => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs) - .and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs > rhs)) - .resolve() - }), - Rule::gte => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs) - .and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs >= rhs)) - .resolve() - }), - _ => unreachable!(), - } -} - -fn compile_str_var(var: Pairs) -> CompiledExpr>> { - let var = compile_var(var); - Box::new(move |cfg, cfgs| { - var(cfg, cfgs).map(|a| match a { - Value::String(s) => Some(InternedString::from(&*s)), - Value::Number(n) => Some(InternedString::from_display(&n)), - Value::Bool(b) => Some(InternedString::from_display(&b)), - _ => None, - }) - }) -} - -fn compile_str(str_str: &str) -> CompiledExpr>> { - let str_str = &str_str[1..str_str.len() - 1]; - let mut out = String::with_capacity(str_str.len()); - let mut escape = false; - for c in str_str.chars() { - match c { - '\\' => { - if escape { - out.push('\\'); - } else { - escape = true; - } - } - 'n' if escape => out.push('\n'), - 'r' if escape => out.push('\r'), - 't' if escape => out.push('\t'), - '0' if escape => out.push('\0'), - '"' if escape => out.push('"'), - '\'' if escape => out.push('\''), - _ => { - if escape { - out.push('\\') - } - out.push(c) - } - } - } - let res = VarRes::Exactly(Some(out.into())); - Box::new(move |_, _| res.clone()) -} - -fn compile_str_expr(pairs: Pairs) -> CompiledExpr>> { - STR_PREC_CLIMBER.climb( - pairs, - |pair| match pair.as_rule() { - Rule::str_var => compile_str_var(pair.into_inner()), - Rule::str => compile_str(pair.as_str()), - Rule::str_expr => compile_str_expr(pair.into_inner()), - _ => unreachable!(), - }, - |lhs, op, rhs| match op.as_rule() { - Rule::add => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs).and_then(|lhs| { - rhs(cfg, cfgs).map(|rhs| { - let lhs = lhs.as_ref()?.to_string(); - let rhs = rhs?; - Some(InternedString::from(lhs + &*rhs)) - }) - }) - }), - _ => unreachable!(), - }, - ) -} - -fn compile_str_cmp_expr(mut pairs: Pairs) -> CompiledRule { - let lhs = compile_str_expr(pairs.next().unwrap().into_inner()); - let op = pairs.next().unwrap(); - let rhs = compile_str_expr(pairs.next().unwrap().into_inner()); - match op.as_rule() { - Rule::lt => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs) - .and_then(|lhs| { - rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { - (Some(lhs), Some(rhs)) => rhs.contains(&**lhs) && lhs.len() < rhs.len(), - _ => false, - }) - }) - .resolve() - }), - Rule::lte => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs) - .and_then(|lhs| { - rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { - (Some(lhs), Some(rhs)) => rhs.contains(&**lhs), - _ => false, - }) - }) - .resolve() - }), - Rule::eq => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs) - .and_then(|lhs| { - rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { - (Some(lhs), Some(rhs)) => lhs == rhs, - (None, None) => true, - _ => false, - }) - }) - .resolve() - }), - Rule::neq => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs) - .and_then(|lhs| { - rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { - (Some(lhs), Some(rhs)) => lhs != rhs, - (None, None) => false, - _ => true, - }) - }) - .resolve() - }), - Rule::gt => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs) - .and_then(|lhs| { - rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { - (Some(lhs), Some(rhs)) => lhs.contains(&**rhs) && lhs.len() > rhs.len(), - _ => true, - }) - }) - .resolve() - }), - Rule::gte => Box::new(move |cfg, cfgs| { - lhs(cfg, cfgs) - .and_then(|lhs| { - rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { - (Some(lhs), Some(rhs)) => lhs.contains(&**rhs), - _ => true, - }) - }) - .resolve() - }), - _ => unreachable!(), - } -} - -fn compile_inv_bool_expr(mut pairs: Pairs) -> CompiledRule { - let expr = compile_bool_expr(pairs.next().unwrap().into_inner()); - Box::new(move |cfg, cfgs| !expr(cfg, cfgs)) -} - -fn compile_bool_expr(pairs: Pairs) -> CompiledRule { - BOOL_PREC_CLIMBER.climb( - pairs, - |pair| match pair.as_rule() { - Rule::bool_var => compile_bool_var(pair.into_inner()), - Rule::bool_expr => compile_bool_expr(pair.into_inner()), - Rule::inv_bool_expr => compile_inv_bool_expr(pair.into_inner()), - Rule::num_cmp_expr => compile_num_cmp_expr(pair.into_inner()), - Rule::str_cmp_expr => compile_str_cmp_expr(pair.into_inner()), - _ => unreachable!(), - }, - |lhs, op, rhs| -> CompiledRule { - match op.as_rule() { - Rule::and => Box::new(move |cfg, cfgs| lhs(cfg, cfgs) && rhs(cfg, cfgs)), - Rule::or => Box::new(move |cfg, cfgs| lhs(cfg, cfgs) || rhs(cfg, cfgs)), - Rule::xor => Box::new(move |cfg, cfgs| lhs(cfg, cfgs) ^ rhs(cfg, cfgs)), - _ => unreachable!(), - } - }, - ) -} - -fn compile_value_expr(mut pairs: Pairs) -> CompiledExpr> { - let expr = pairs.next().unwrap(); - match expr.as_rule() { - Rule::any_var => compile_var(expr.into_inner()), - Rule::str_expr => { - let expr = compile_str_expr(expr.into_inner()); - Box::new(move |cfg, cfgs| { - expr(cfg, cfgs).map(|s| { - s.map(|s| Value::String(Arc::new(s.to_string()))) - .unwrap_or(Value::Null) - }) - }) - } - Rule::num_expr => { - let expr = compile_num_expr(expr.into_inner()); - Box::new(move |cfg, cfgs| { - expr(cfg, cfgs).map(|n| match serde_json::Number::from_f64(n) { - Some(a) => Value::Number(a), - None => panic!("cannot coerce f64 into numberc type"), - }) - }) - } - Rule::bool_expr => { - let expr = compile_bool_expr(expr.into_inner()); - Box::new(move |cfg, cfgs| VarRes::Exactly(expr(cfg, cfgs)).map(Value::Bool)) - } - _ => unreachable!(), - } -} - -fn compile_del_action(mut pairs: Pairs) -> Result { - let list_mut = compile_var_mut(pairs.next().unwrap().into_inner())?; - let var: InternedString = pairs.next().unwrap().as_str().into(); - let predicate = compile_bool_expr(pairs.next().unwrap().into_inner()); - Ok(Box::new(move |cfg, cfgs| match (&list_mut)(cfg, cfgs) { - Some(Value::Array(ref mut l)) => { - *l = std::mem::take(l) - .into_iter() - .filter(|item| { - let mut obj = Config::default(); - obj.insert(var.clone(), item.clone()); - !predicate(&obj, cfgs) - }) - .collect(); - } - Some(Value::Object(ref mut o)) => { - *o = std::mem::take(o) - .into_iter() - .filter(|(_, item)| { - let mut obj = Config::default(); - obj.insert(var.clone(), item.clone()); - !predicate(&obj, cfgs) - }) - .collect(); - } - _ => return, - })) -} - -fn compile_push_action(mut pairs: Pairs, value: Value) -> Result { - let list_mut = compile_var_mut(pairs.next().unwrap().into_inner())?; - Ok(Box::new(move |cfg, cfgs| { - let vec = match (&list_mut)(cfg, cfgs) { - Some(Value::Array(ref mut a)) => a, - _ => return, - }; - vec.push_back(value.clone()) - })) -} - -fn compile_set_action(var: &str, to: &SetVariant) -> Result { - let mut var = RuleParser::parse(Rule::reference, var)?; - let get_mut = compile_var_mut(var.next().unwrap().into_inner())?; - Ok(match to { - SetVariant::To(expr) => { - let expr = compile_expr(&expr)?; - Box::new(move |cfg, cfgs| { - let val = expr(cfg, cfgs); - if let Some(var) = get_mut(cfg, cfgs) { - *var = val; - } - }) - } - SetVariant::ToValue(val) => { - let val = val.clone(); - Box::new(move |cfg, cfgs| { - if let Some(var) = get_mut(cfg, cfgs) { - *var = val.clone() - } - }) - } - SetVariant::ToEntropy(entropy) => { - let entropy = entropy.clone(); - Box::new(move |cfg, cfgs| { - if let Some(var) = get_mut(cfg, cfgs) { - *var = Value::String(Arc::new( - entropy.gen(&mut rand::rngs::StdRng::from_entropy()), - )); - } - }) - } - }) -} - -pub fn validate_key(key: &str) -> Result<(), pest::error::Error> { - RuleParser::parse(Rule::obj_key, key)?; - Ok(()) -} - -pub fn parse_and) -> T>( - rule: &str, - f: F, -) -> Result> { - let mut parsed = RuleParser::parse(Rule::rule, rule)?; - let pairs = parsed.next().unwrap().into_inner(); - Ok(f(pairs)) -} - -pub fn compile(rule: &str) -> Result { - parse_and(rule, compile_bool_expr).map_err(From::from) -} - -pub fn compile_expr(expr: &str) -> Result, failure::Error> { - let compiled = compile_value_expr(RuleParser::parse(Rule::value, expr)?); - Ok(Box::new(move |cfg, cfgs| match compiled(cfg, cfgs) { - VarRes::Exactly(v) => v, - _ => Value::Null, - })) -} - -#[cfg(test)] -mod test { - use serde_json::json; - - use super::*; - - #[test] - fn test_compile_str() { - assert_eq!( - compile_str("\"foo\"")(&Default::default(), &Default::default()), - VarRes::Exactly(Some("foo".to_owned())) - ); - } - - #[test] - fn test_access_expr() { - let mut cfg = Config::default(); - let mut cfgs = LinearMap::new(); - let mut foo = Config::default(); - foo.insert("bar!\"".to_owned(), json!(3.0)); - cfg.insert( - "foo".to_owned(), - Value::Array(vec![Value::Null, Value::Object(foo), json!(3)]), - ); - cfgs.insert("my-app", Cow::Borrowed(&cfg)); - assert!((compile("#[my-app].foo.1.[\"ba\" + \"r!\\\"\"] = 3") - .map_err(|e| eprintln!("{}", e)) - .expect("compile failed"))(&cfg, &cfgs)); - assert!((compile("#[my-app].foo.[0 + 1].[\"bar!\\\"\"] = 3") - .map_err(|e| eprintln!("{}", e)) - .expect("compile failed"))(&cfg, &cfgs)); - } - - #[test] - fn test_any_all() { - let mut cfg = Config::default(); - let mut cfgs = LinearMap::new(); - let mut foo = Config::default(); - foo.insert("bar".to_owned(), json!(3.0)); - cfg.insert( - "foo".to_owned(), - Value::Array(vec![Value::Null, Value::Object(foo), json!(3.0)]), - ); - cfgs.insert("my-app", Cow::Borrowed(&cfg)); - // NOTE: these now fail due to added panic for parsing f64's - // assert!((compile("#[my-app].foo.*.bar = 3") - // .map_err(|e| eprintln!("{}", e)) - // .expect("compile failed"))(&cfg, &cfgs)); - // assert!(!(compile("#[my-app].foo.&.bar = 3") - // .map_err(|e| eprintln!("{}", e)) - // .expect("compile failed"))(&cfg, &cfgs)); - } - - #[test] - fn test_first_last() { - let mut cfg = Config::default(); - let mut cfgs = LinearMap::new(); - let mut foo = Config::default(); - foo.insert("bar".to_owned(), json!(3.0)); - foo.insert("baz".to_owned(), json!(4.0)); - let mut qux = Config::default(); - qux.insert("bar".to_owned(), json!(7.0)); - qux.insert("baz".to_owned(), json!(4.0)); - cfg.insert( - "foo".to_owned(), - Value::Array(vec![ - Value::Null, - Value::Object(foo), - Value::Object(qux), - json!(3.0), - ]), - ); - cfgs.insert("my-app", Cow::Borrowed(&cfg)); - // NOTE: these now fail due to added panic for parsing f64's - // assert!((compile("#foo.[first(item => #item.baz = 4)].bar = 3") - // .map_err(|e| eprintln!("{}", e)) - // .expect("compile failed"))(&cfg, &cfgs)); - // assert!((compile("#foo.[last(item => #item.baz = 4)].bar = 7") - // .map_err(|e| eprintln!("{}", e)) - // .expect("compile failed"))(&cfg, &cfgs)); - } - - #[test] - fn test_app_id() { - let mut dependent_cfg = Config::default(); - let mut dependency_cfg = Config::default(); - let mut cfgs = LinearMap::new(); - dependent_cfg.insert("foo".to_owned(), Value::String("bar".to_owned())); - dependency_cfg.insert("foo".to_owned(), Value::String("bar!".to_owned())); - cfgs.insert("my-dependent", Cow::Borrowed(&dependent_cfg)); - cfgs.insert("my-dependency", Cow::Borrowed(&dependency_cfg)); - assert!((compile("'foo = '[my-dependent].foo + \"!\"") - .map_err(|e| eprintln!("{}", e)) - .expect("compile failed"))( - &dependency_cfg, &cfgs - )) - } -} diff --git a/system-images/compat/src/main.rs b/system-images/compat/src/main.rs deleted file mode 100644 index 338e01f5b..000000000 --- a/system-images/compat/src/main.rs +++ /dev/null @@ -1,337 +0,0 @@ -use std::{ - env, - fs::File, - io::{stdin, stdout}, - path::Path, -}; - -#[macro_use] -extern crate failure; -extern crate pest; -#[macro_use] -extern crate pest_derive; - -mod backup; -mod config; -use anyhow::anyhow; -use backup::{create_backup, restore_backup}; -use clap::{App, Arg, SubCommand}; -use config::{ - apply_dependency_configuration, validate_configuration, validate_dependency_configuration, -}; -use serde_json::json; -use startos::config::action::ConfigRes; - -const PROPERTIES_FALLBACK_MESSAGE: &str = - "Could not find properties. The service might still be starting"; -pub enum CompatRes { - SetResult, - ConfigRes, -} - -fn main() { - match inner_main() { - Ok(a) => a, - Err(e) => { - eprintln!("{}", e); - log::debug!("{:?}", e.backtrace()); - drop(e); - std::process::exit(1) - } - } -} - -fn inner_main() -> Result<(), anyhow::Error> { - let app = App::new("compat") - .subcommand( - SubCommand::with_name("config") - .subcommand( - SubCommand::with_name("get") - .arg( - Arg::with_name("mountpoint") - .help("Path to the data mountpoint") - .required(true), - ) - .arg( - Arg::with_name("spec") - .help("The path to the config spec in the container") - .required(true), - ), - ) - .subcommand( - SubCommand::with_name("set") - .arg( - Arg::with_name("package_id") - .help("The `id` field from the manifest file") - .required(true), - ) - .arg( - Arg::with_name("mountpoint") - .help("Path to the data mountpoint") - .required(true), - ) - .arg( - Arg::with_name("assets") - .help("Path to the rules file") - .required(true), - ) - .arg( - Arg::with_name("dependencies") - .help("Path to rules for optional dependency config") - ), - ), - ) - .subcommand( - SubCommand::with_name("dependency") - .subcommand( - SubCommand::with_name("check") - .arg( - Arg::with_name("dependent_package_id") - .help("Package identifier of this package (the child/depdendent)") - .required(true), - ) - .arg( - Arg::with_name("dependency_package_id") - .help("Identifier of the dependency") - .required(true), - ) - .arg( - Arg::with_name("mountpoint") - .help(" ountpoint for the dependent's config file") - .required(true), - ) - .arg( - Arg::with_name("assets") - .help("Path to the dependency's config rules file") - .required(true), - ), - ) - .subcommand( - SubCommand::with_name("auto-configure") - .arg( - Arg::with_name("dependent_package_id") - .help("Package identifier of this package (the child/depdendent)") - .required(true), - ) - .arg( - Arg::with_name("dependency_package_id") - .help("Package identifier of the parent/dependency") - .required(true), - ) - .arg( - Arg::with_name("mountpoint") - .help("Mountpoint for the dependent's config file") - .required(true), - ) - .arg( - Arg::with_name("assets") - .help("Path to the dependency's config rules file") - .required(true), - ), - ), - ) - .subcommand( - SubCommand::with_name("duplicity") - .subcommand( - SubCommand::with_name("create") - .arg( - Arg::with_name("mountpoint") - .help("The backups mount point") - .required(true), - ) - .arg( - Arg::with_name("datapath") - .help("The path to the data to be backed up in the container") - .required(true), - ), - ) - .subcommand( - SubCommand::with_name("restore") - .arg( - Arg::with_name("mountpoint") - .help("The backups mount point") - .required(true), - ) - .arg( - Arg::with_name("datapath") - .help("The path to the data to be restored to the container") - .required(true), - ), - ), - ) - .subcommand( - SubCommand::with_name("properties").arg( - Arg::with_name("mountpoint") - .help("The data directory of the service to mount to.") - .required(true), - ).arg( - Arg::with_name("fallbackMessage") - .help("The message to indicate that the startup is still working, or stats.yaml couldn't be found") - .required(false), - ), - ); - let matches = app.get_matches(); - match matches.subcommand() { - ("config", Some(sub_m)) => match sub_m.subcommand() { - ("get", Some(sub_m)) => { - let cfg_path = - Path::new(sub_m.value_of("mountpoint").unwrap()).join("start9/config.yaml"); - let cfg = if cfg_path.exists() { - Some(serde_yaml::from_reader(File::open(cfg_path).unwrap()).unwrap()) - } else { - None - }; - let spec_path = Path::new(sub_m.value_of("spec").unwrap()); - let spec = serde_yaml::from_reader(File::open(spec_path).unwrap()).unwrap(); - serde_yaml::to_writer(stdout(), &ConfigRes { config: cfg, spec })?; - Ok(()) - } - ("set", Some(sub_m)) => { - let config = serde_yaml::from_reader(stdin())?; - let cfg_path = Path::new(sub_m.value_of("mountpoint").unwrap()).join("start9"); - if !cfg_path.exists() { - std::fs::create_dir_all(&cfg_path).unwrap(); - }; - let rules_path = Path::new(sub_m.value_of("assets").unwrap()); - let name = sub_m.value_of("package_id").unwrap(); - let deps_path = sub_m.value_of("dependencies"); - match validate_configuration( - &name, - config, - rules_path, - &cfg_path.join("config.yaml"), - deps_path, - ) { - Ok(a) => { - serde_yaml::to_writer(stdout(), &a)?; - Ok(()) - } - Err(e) => Err(e), - } - } - (subcmd, _) => { - panic!("Unknown subcommand: {}", subcmd); - } - }, - ("dependency", Some(sub_m)) => match sub_m.subcommand() { - ("check", Some(sub_m)) => { - let parent_config = serde_yaml::from_reader(stdin())?; - let cfg_path = - Path::new(sub_m.value_of("mountpoint").unwrap()).join("start9/config.yaml"); - let config = if cfg_path.exists() { - Some(serde_yaml::from_reader(File::open(cfg_path).unwrap()).unwrap()) - } else { - None - }; - let rules_path = Path::new(sub_m.value_of("assets").unwrap()); - let name = sub_m.value_of("dependent_package_id").unwrap(); - let parent_name = sub_m.value_of("dependency_package_id").unwrap(); - match validate_dependency_configuration( - name, - &config, - parent_name, - parent_config, - rules_path, - ) { - Ok(a) => { - serde_yaml::to_writer(stdout(), &a)?; - Ok(()) - } - Err(e) => { - // error string is configs rules failure description - Err(e) - } - } - } - ("auto-configure", Some(sub_m)) => { - let dep_config = serde_yaml::from_reader(stdin())?; - let cfg_path = - Path::new(sub_m.value_of("mountpoint").unwrap()).join("start9/config.yaml"); - let config = if cfg_path.exists() { - Some(serde_yaml::from_reader(File::open(cfg_path).unwrap()).unwrap()) - } else { - None - }; - let rules_path = Path::new(sub_m.value_of("assets").unwrap()); - let package_id = sub_m.value_of("dependent_package_id").unwrap(); - let dependency_id = sub_m.value_of("dependency_package_id").unwrap(); - match apply_dependency_configuration( - package_id, - config, - dependency_id, - dep_config, - rules_path, - ) { - Ok(a) => { - serde_yaml::to_writer(stdout(), &a)?; - Ok(()) - } - Err(e) => Err(e), - } - } - (subcmd, _) => { - panic!("Unknown subcommand: {}", subcmd); - } - }, - ("duplicity", Some(sub_m)) => match sub_m.subcommand() { - ("create", Some(sub_m)) => { - let res = create_backup( - sub_m.value_of("mountpoint").unwrap(), - sub_m.value_of("datapath").unwrap(), - ); - match res { - Ok(r) => { - serde_yaml::to_writer(stdout(), &r)?; - Ok(()) - } - Err(e) => Err(anyhow!("Could not create backup: {}", e)), - } - } - ("restore", Some(sub_m)) => { - let res = restore_backup( - sub_m.value_of("mountpoint").unwrap(), - sub_m.value_of("datapath").unwrap(), - ); - match res { - Ok(r) => { - serde_yaml::to_writer(stdout(), &r)?; - Ok(()) - } - Err(e) => Err(anyhow!("Could not restore backup: {}", e)), - } - } - (subcmd, _) => { - panic!("Unknown subcommand: {}", subcmd); - } - }, - ("properties", Some(sub_m)) => { - let stats_path = - Path::new(sub_m.value_of("mountpoint").unwrap()).join("start9/stats.yaml"); - let stats: serde_json::Value = if stats_path.exists() { - serde_yaml::from_reader(File::open(stats_path).unwrap()).unwrap() - } else { - let fallback_message: &str = sub_m - .value_of("fallbackMessage") - .unwrap_or_else(|| PROPERTIES_FALLBACK_MESSAGE); - json!({ - "version": 2i64, - "data": { - "Not Ready": { - "type": "string", - "value": fallback_message, - "qr": false, - "copyable": false, - "masked": false, - "description":"Fallback Message When Properties could not be found" - } - } - }) - }; - serde_json::to_writer(stdout(), &stats)?; - Ok(()) - } - (subcmd, _) => { - panic!("Unknown subcommand: {}", subcmd); - } - } -} diff --git a/system-images/utils/.gitignore b/system-images/utils/.gitignore deleted file mode 100644 index 9ea8ef8d2..000000000 --- a/system-images/utils/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/docker-images \ No newline at end of file diff --git a/system-images/utils/Dockerfile b/system-images/utils/Dockerfile deleted file mode 100644 index d2a7129f5..000000000 --- a/system-images/utils/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM alpine:latest - -RUN apk update && apk add --no-cache yq jq curl bash diff --git a/system-images/utils/Makefile b/system-images/utils/Makefile deleted file mode 100644 index ffb8c0779..000000000 --- a/system-images/utils/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -.DELETE_ON_ERROR: - -all: docker-images/aarch64.tar docker-images/x86_64.tar - -clean: - rm -rf docker-images - -docker-images: - mkdir docker-images - -docker-images/aarch64.tar: Dockerfile docker-images - docker buildx build --tag start9/x_system/utils --platform=linux/arm64 -o type=docker,dest=docker-images/aarch64.tar . - -docker-images/x86_64.tar: Dockerfile docker-images - docker buildx build --tag start9/x_system/utils --platform=linux/amd64 -o type=docker,dest=docker-images/x86_64.tar . \ No newline at end of file diff --git a/web/package-lock.json b/web/package-lock.json index 3e4bb536a..c7157e837 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "startos-ui", - "version": "0.4.0-alpha.9", + "version": "0.4.0-alpha.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "startos-ui", - "version": "0.4.0-alpha.9", + "version": "0.4.0-alpha.10", "license": "MIT", "dependencies": { "@angular/animations": "^20.1.0", @@ -25,19 +25,18 @@ "@noble/hashes": "^1.4.0", "@start9labs/argon2": "^0.3.0", "@start9labs/start-sdk": "file:../sdk/baseDist", - "@taiga-ui/addon-charts": "4.44.0", - "@taiga-ui/addon-commerce": "4.44.0", - "@taiga-ui/addon-mobile": "4.44.0", - "@taiga-ui/addon-table": "4.44.0", - "@taiga-ui/cdk": "4.44.0", - "@taiga-ui/core": "4.44.0", + "@taiga-ui/addon-charts": "4.52.0", + "@taiga-ui/addon-commerce": "4.52.0", + "@taiga-ui/addon-mobile": "4.52.0", + "@taiga-ui/addon-table": "4.52.0", + "@taiga-ui/cdk": "4.52.0", + "@taiga-ui/core": "4.52.0", "@taiga-ui/dompurify": "4.1.11", - "@taiga-ui/event-plugins": "4.6.0", - "@taiga-ui/experimental": "4.44.0", - "@taiga-ui/icons": "4.44.0", - "@taiga-ui/kit": "4.44.0", - "@taiga-ui/layout": "4.44.0", - "@taiga-ui/legacy": "4.44.0", + "@taiga-ui/event-plugins": "4.7.0", + "@taiga-ui/experimental": "4.52.0", + "@taiga-ui/icons": "4.52.0", + "@taiga-ui/kit": "4.52.0", + "@taiga-ui/layout": "4.52.0", "@taiga-ui/polymorpheus": "4.9.0", "ansi-to-html": "^0.7.2", "base64-js": "^1.5.1", @@ -62,6 +61,7 @@ "patch-db-client": "file:../patch-db/client", "pbkdf2": "^3.1.2", "rxjs": "^7.8.2", + "tldts": "^7.0.11", "ts-matches": "^6.3.2", "tslib": "^2.8.1", "uuid": "^8.3.2", @@ -142,6 +142,215 @@ "typescript": "^5.7.3" } }, + "node_modules/@algolia/abtesting": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.1.0.tgz", + "integrity": "sha512-sEyWjw28a/9iluA37KLGu8vjxEIlb60uxznfTUmXImy7H5NvbpSO6yYgmgH5KiD7j+zTUUihiST0jEP12IoXow==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.35.0.tgz", + "integrity": "sha512-uUdHxbfHdoppDVflCHMxRlj49/IllPwwQ2cQ8DLC4LXr3kY96AHBpW0dMyi6ygkn2MtFCc6BxXCzr668ZRhLBQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.35.0.tgz", + "integrity": "sha512-SunAgwa9CamLcRCPnPHx1V2uxdQwJGqb1crYrRWktWUdld0+B2KyakNEeVn5lln4VyeNtW17Ia7V7qBWyM/Skw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.35.0.tgz", + "integrity": "sha512-ipE0IuvHu/bg7TjT2s+187kz/E3h5ssfTtjpg1LbWMgxlgiaZIgTTbyynM7NfpSJSKsgQvCQxWjGUO51WSCu7w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.35.0.tgz", + "integrity": "sha512-UNbCXcBpqtzUucxExwTSfAe8gknAJ485NfPN6o1ziHm6nnxx97piIbcBQ3edw823Tej2Wxu1C0xBY06KgeZ7gA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.35.0.tgz", + "integrity": "sha512-/KWjttZ6UCStt4QnWoDAJ12cKlQ+fkpMtyPmBgSS2WThJQdSV/4UWcqCUqGH7YLbwlj3JjNirCu3Y7uRTClxvA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.35.0.tgz", + "integrity": "sha512-8oCuJCFf/71IYyvQQC+iu4kgViTODbXDk3m7yMctEncRSRV+u2RtDVlpGGfPlJQOrAY7OONwJlSHkmbbm2Kp/w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.35.0.tgz", + "integrity": "sha512-FfmdHTrXhIduWyyuko1YTcGLuicVbhUyRjO3HbXE4aP655yKZgdTIfMhZ/V5VY9bHuxv/fGEh3Od1Lvv2ODNTg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.35.0.tgz", + "integrity": "sha512-gPzACem9IL1Co8mM1LKMhzn1aSJmp+Vp434An4C0OBY4uEJRcqsLN3uLBlY+bYvFg8C8ImwM9YRiKczJXRk0XA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.35.0.tgz", + "integrity": "sha512-w9MGFLB6ashI8BGcQoVt7iLgDIJNCn4OIu0Q0giE3M2ItNrssvb8C0xuwJQyTy1OFZnemG0EB1OvXhIHOvQwWw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.35.0.tgz", + "integrity": "sha512-AhrVgaaXAb8Ue0u2nuRWwugt0dL5UmRgS9LXe0Hhz493a8KFeZVUE56RGIV3hAa6tHzmAV7eIoqcWTQvxzlJeQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.35.0.tgz", + "integrity": "sha512-diY415KLJZ6x1Kbwl9u96Jsz0OstE3asjXtJ9pmk1d+5gPuQ5jQyEsgC+WmEXzlec3iuVszm8AzNYYaqw6B+Zw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.35.0.tgz", + "integrity": "sha512-uydqnSmpAjrgo8bqhE9N1wgcB98psTRRQXcjc4izwMB7yRl9C8uuAQ/5YqRj04U0mMQ+fdu2fcNF6m9+Z1BzDQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.35.0.tgz", + "integrity": "sha512-RgLX78ojYOrThJHrIiPzT4HW3yfQa0D7K+MQ81rhxqaNyNBu4F1r+72LNHYH/Z+y9I1Mrjrd/c/Ue5zfDgAEjQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -157,13 +366,13 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.2001.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2001.0.tgz", - "integrity": "sha512-IDBG+YP0nPaA/tIjtJ1ZPh0VEfbxSn0yCvbS7dTfqyrnmanPUFpU5qsT9vJTU6yzkuzBEhNFRzkUCQaUAziLRA==", + "version": "0.2002.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2002.2.tgz", + "integrity": "sha512-amppp/UqKyj+B8hYFU16j4t6SVN+SS0AEnHivDjKy41NNJgXv+5Sm2Q2jaMHviCT3rclyT0wqwNAi0RDjyLx5Q==", "devOptional": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.1.0", + "@angular-devkit/core": "20.2.2", "rxjs": "7.8.2" }, "engines": { @@ -172,19 +381,18 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/architect/node_modules/@angular-devkit/core": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.1.0.tgz", - "integrity": "sha512-i2t22bklvKsqdwmUtjXltRyxmJ+lJW8isrdc7XeN0N6VW/lDHSJqFlucT1+pO9+FxXJQyz3Hc1dpRd6G65mGyw==", - "devOptional": true, + "node_modules/@angular-devkit/core": { + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.2.2.tgz", + "integrity": "sha512-SC+f5isSWJBpEgR+R7jP++2Z14WExNWLAdKpIickLWjuL8FlGkj+kaF3dWXhh0KcXo+r6kKb4pWUptSaqer5gA==", "license": "MIT", "dependencies": { "ajv": "8.17.1", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", + "picomatch": "4.0.3", "rxjs": "7.8.2", - "source-map": "0.7.4" + "source-map": "0.7.6" }, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0", @@ -200,85 +408,13 @@ } } }, - "node_modules/@angular-devkit/architect/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@angular-devkit/architect/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@angular-devkit/core": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", - "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/core/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/@angular-devkit/schematics": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.1.0.tgz", - "integrity": "sha512-0UtJAptrqsfABi0DxrY7cyvlGe5kHRiqVwB+h3g2DEv3ikXKZh1dOFR3o2bK+sVhUqgFaV8qgSnCmR9a48xY0g==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.2.2.tgz", + "integrity": "sha512-rtL7slZjzdChQoiADKZv/Ra8D3C3tIw/WcVxd2stiLHdK/Oaf9ejx5m/X9o0QMEbNsy2Fy/RKodNqmz1CjzpCg==", "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.1.0", + "@angular-devkit/core": "20.2.2", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "8.2.0", @@ -290,65 +426,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.1.0.tgz", - "integrity": "sha512-i2t22bklvKsqdwmUtjXltRyxmJ+lJW8isrdc7XeN0N6VW/lDHSJqFlucT1+pO9+FxXJQyz3Hc1dpRd6G65mGyw==", - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.2", - "source-map": "0.7.4" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^4.0.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/schematics/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@angular-experts/hawkeye": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@angular-experts/hawkeye/-/hawkeye-1.7.2.tgz", @@ -367,33 +444,10 @@ "hawkeye": "index.mjs" } }, - "node_modules/@angular-experts/hawkeye/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@angular-experts/hawkeye/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/@angular/animations": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.1.0.tgz", - "integrity": "sha512-5ILngsvu5VPQYaIm7lRyegZaDaAEtLUIPSS8h1dzWPaCxBIJ4uwzx9RDMiF32zhbxi+q0mAO2w2FdDlzWTT3og==", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.2.4.tgz", + "integrity": "sha512-mXiTlXZgAF4uYonOt7l2w7uvLLTJEk6jqs3H291bYuoDRM8R166UjN7ygAeBmPiJ4TLMyKGkwMQy3b1Vvw4RQA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -402,42 +456,41 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.1.0", - "@angular/core": "20.1.0" + "@angular/core": "20.2.4" } }, "node_modules/@angular/build": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.1.0.tgz", - "integrity": "sha512-Sl4rkq5PQIrbVNk8cXx2JQhQ156H4bXLvfAYpgXPHAfSfbIIzaV25LJIfTdWSEjMzBGdIX5E0Vpi0SGwcNS7Uw==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.2.2.tgz", + "integrity": "sha512-rvlKMt3OmeenHOwejRpI4OLcyERQn6Hl4ODRWlYfNX70Ki1zu6eAD0pWULzcD+HSQd0a26Xzt3gcpEy2vOEAzg==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2001.0", - "@babel/core": "7.27.7", + "@angular-devkit/architect": "0.2002.2", + "@babel/core": "7.28.3", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", - "@inquirer/confirm": "5.1.13", + "@inquirer/confirm": "5.1.14", "@vitejs/plugin-basic-ssl": "2.1.0", - "beasties": "0.3.4", + "beasties": "0.3.5", "browserslist": "^4.23.0", - "esbuild": "0.25.5", + "esbuild": "0.25.9", "https-proxy-agent": "7.0.6", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", - "listr2": "8.3.3", + "listr2": "9.0.1", "magic-string": "0.30.17", "mrmime": "2.0.1", - "parse5-html-rewriting-stream": "7.1.0", - "picomatch": "4.0.2", - "piscina": "5.1.2", - "rollup": "4.44.1", - "sass": "1.89.2", + "parse5-html-rewriting-stream": "8.0.0", + "picomatch": "4.0.3", + "piscina": "5.1.3", + "rolldown": "1.0.0-beta.32", + "sass": "1.90.0", "semver": "7.7.2", "source-map-support": "0.5.21", "tinyglobby": "0.2.14", - "vite": "7.0.0", + "vite": "7.1.2", "watchpack": "2.4.4" }, "engines": { @@ -446,7 +499,7 @@ "yarn": ">= 1.13.0" }, "optionalDependencies": { - "lmdb": "3.4.1" + "lmdb": "3.4.2" }, "peerDependencies": { "@angular/compiler": "^20.0.0", @@ -456,14 +509,14 @@ "@angular/platform-browser": "^20.0.0", "@angular/platform-server": "^20.0.0", "@angular/service-worker": "^20.0.0", - "@angular/ssr": "^20.1.0", + "@angular/ssr": "^20.2.2", "karma": "^6.4.0", "less": "^4.2.0", "ng-packagr": "^20.0.0", "postcss": "^8.4.0", "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", "tslib": "^2.3.0", - "typescript": ">=5.8 <5.9", + "typescript": ">=5.8 <6.0", "vitest": "^3.1.1" }, "peerDependenciesMeta": { @@ -505,360 +558,13 @@ } } }, - "node_modules/@angular/build/node_modules/@babel/core": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", - "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.27.7", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.7", - "@babel/types": "^7.27.7", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@angular/build/node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@angular/build/node_modules/@vitejs/plugin-basic-ssl": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.0.tgz", - "integrity": "sha512-dOxxrhgyDIEUADhb/8OlV9JIqYLgos03YorAueTIeOUskLJSEsfwCByjbu98ctXitUN3znXKp0bYD/WHSudCeA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "peerDependencies": { - "vite": "^6.0.0 || ^7.0.0" - } - }, - "node_modules/@angular/build/node_modules/ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/build/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@angular/build/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@angular/build/node_modules/cli-truncate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/build/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular/build/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular/build/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular/build/node_modules/is-fullwidth-code-point": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", - "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/build/node_modules/listr2": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", - "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^4.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@angular/build/node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/build/node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", - "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/@angular/build/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/build/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@angular/build/node_modules/vite": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.0.tgz", - "integrity": "sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.6", - "picomatch": "^4.0.2", - "postcss": "^8.5.6", - "rollup": "^4.40.0", - "tinyglobby": "^0.2.14" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/@angular/build/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@angular/build/node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/@angular/cdk": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.1.0.tgz", - "integrity": "sha512-JhgbSOv7xZqWNZjuCh8A3A7pGv0mhtmGjHo36157LrxRO6R7x2yJJjxC5nQeroKZWhgN+X/jG/EJlzEvl9PxTw==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.2.tgz", + "integrity": "sha512-jLvIMmFI8zoi6vAu1Aszua59GmhqBOtsVfkwLUGg5Hi86DI/inJr9BznNX2EKDtaulYMGZCmDgsltXQXeqP5Lg==", "license": "MIT", "dependencies": { - "parse5": "^7.1.2", + "parse5": "^8.0.0", "tslib": "^2.3.0" }, "peerDependencies": { @@ -868,30 +574,30 @@ } }, "node_modules/@angular/cli": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.1.0.tgz", - "integrity": "sha512-jZudpHlPVAvrywVZuhUkUr5K7ThW/6CPjT7qxZBSdOvu7cD49JPpDivCdlMh0kCBSHsJ0ZbLx35oi6zF8PegiA==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.2.2.tgz", + "integrity": "sha512-0K8cmuHzRTpPzy/w0+S5o3s0JPV++9/s2JhK4aw/+OnQRpUbodoqjm1ur5k5DUBQfIHi7aM73ZIW3G43lv4F0g==", "devOptional": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.2001.0", - "@angular-devkit/core": "20.1.0", - "@angular-devkit/schematics": "20.1.0", - "@inquirer/prompts": "7.6.0", - "@listr2/prompt-adapter-inquirer": "2.0.22", - "@modelcontextprotocol/sdk": "1.13.3", - "@schematics/angular": "20.1.0", + "@angular-devkit/architect": "0.2002.2", + "@angular-devkit/core": "20.2.2", + "@angular-devkit/schematics": "20.2.2", + "@inquirer/prompts": "7.8.2", + "@listr2/prompt-adapter-inquirer": "3.0.1", + "@modelcontextprotocol/sdk": "1.17.3", + "@schematics/angular": "20.2.2", "@yarnpkg/lockfile": "1.1.0", + "algoliasearch": "5.35.0", "ini": "5.0.0", "jsonc-parser": "3.3.1", - "listr2": "8.3.3", - "npm-package-arg": "12.0.2", - "npm-pick-manifest": "10.0.0", + "listr2": "9.0.1", + "npm-package-arg": "13.0.0", "pacote": "21.0.0", "resolve": "1.22.10", "semver": "7.7.2", "yargs": "18.0.0", - "zod": "3.25.75" + "zod": "3.25.76" }, "bin": { "ng": "bin/ng.js" @@ -902,268 +608,10 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.1.0.tgz", - "integrity": "sha512-i2t22bklvKsqdwmUtjXltRyxmJ+lJW8isrdc7XeN0N6VW/lDHSJqFlucT1+pO9+FxXJQyz3Hc1dpRd6G65mGyw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.2", - "source-map": "0.7.4" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^4.0.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular/cli/node_modules/ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/cli/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@angular/cli/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@angular/cli/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@angular/cli/node_modules/cli-truncate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/cli/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@angular/cli/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@angular/cli/node_modules/is-fullwidth-code-point": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", - "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/cli/node_modules/listr2": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", - "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^4.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@angular/cli/node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/cli/node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", - "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/@angular/cli/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@angular/cli/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/cli/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@angular/cli/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@angular/common": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.1.0.tgz", - "integrity": "sha512-RsHClHJux+4lXrHdGHVw22wekRbSjYtx6Xwjox2S+IRPP51CbX0KskAALZ9ZmtCttkYSFVtvr0S+SQrU2cu5WA==", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.2.4.tgz", + "integrity": "sha512-mc6Sq1cYjaPJYThnvG6x0f/E27pWksqwaNJxT1RtwhAGc1i2jsc0su6b7e5NnXEgVbdPqu1MZHAEFdXZ5+/MwQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1172,14 +620,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "20.1.0", + "@angular/core": "20.2.4", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.1.0.tgz", - "integrity": "sha512-sM8H3dJotIDDmI1u8qGuAn16XVfR7A4+/5s5cKLI/osnnIjafi5HHqAf76R5IlGoIv0ZHVQIYaJ/Qdvfyvdhfg==", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.2.4.tgz", + "integrity": "sha512-LQzf+Azb/Ms+BavpCFIat+f1C0gUJpby2RW4yebF3JkBFKfJ7M8d49TQpF8rSnGxMRTf49mln7laz4nBYTLDGA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1189,13 +637,13 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.1.0.tgz", - "integrity": "sha512-ajbCmvYYFxeXRdKSfdHjp62MZ2lCMUS0UzswBDAbT9sPd/ThppbvLXLsMBj8SlwaXSSBeTAa1oSHEO1MeuVvGQ==", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.2.4.tgz", + "integrity": "sha512-II2hEpfbo73dL12D42DoIHYGiTYAiO9cpwh29BIo8VD054ei4cm0oK+jCyryDQH5T3+wyCWlj0OFjcZ/GmO7HQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "7.28.0", + "@babel/core": "7.28.3", "@jridgewell/sourcemap-codec": "^1.4.14", "chokidar": "^4.0.0", "convert-source-map": "^1.5.1", @@ -1212,8 +660,8 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.1.0", - "typescript": ">=5.8 <5.9" + "@angular/compiler": "20.2.4", + "typescript": ">=5.8 <6.0" }, "peerDependenciesMeta": { "typescript": { @@ -1221,40 +669,10 @@ } } }, - "node_modules/@angular/compiler-cli/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@angular/compiler-cli/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@angular/core": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.1.0.tgz", - "integrity": "sha512-/dJooZi+OAACkjWgGMPrOOGikdtlTJXwdeXPJTgZSUD5L8oQMbhZFG0XW/1Hldvsti87wPjZPz67ivB7zR86VA==", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.2.4.tgz", + "integrity": "sha512-8yvfvPDWX8M7o82GBl5P1nlvm1ywQ2XZi5HWj3llKpSJE2XjzhATgPrpKwiNVnpgjZWTOwM11fpoAaRKqQjxTA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1263,7 +681,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.1.0", + "@angular/compiler": "20.2.4", "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0" }, @@ -1277,9 +695,9 @@ } }, "node_modules/@angular/forms": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.1.0.tgz", - "integrity": "sha512-NgQxowyyG2yiSOXxtQS1xK1vAQT+4GRoMFuzmS3uBshIifgCgFckSxJHQXhlQOInuv2NsZ1Q0HuCvao+yZfIow==", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.2.4.tgz", + "integrity": "sha512-wbgnW+GALVAmK6hgFegkwlHKw35onvh9Z5A236HCyUySEAOiaD/3CoDg5Hw4iHQAiSU6Fn2NwDiv+W0xki6WDw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1288,16 +706,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.1.0", - "@angular/core": "20.1.0", - "@angular/platform-browser": "20.1.0", + "@angular/common": "20.2.4", + "@angular/core": "20.2.4", + "@angular/platform-browser": "20.2.4", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/language-service": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.1.0.tgz", - "integrity": "sha512-qHZk5LI2uaDRk0QOIdg2AZqyij00XOgZb3yiP7C3fjv3YkyRjXvobTRCLieTq10vdMt9TuCwdOISFK7g1/1eEg==", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.2.4.tgz", + "integrity": "sha512-sf0/u5lsWi4BmnBX5tuZzRX2qjnOVUYFFkxmDbpzFaRoY2H1PJtvDsHfzxBfSYtXXxeyeXfxdetXpSr9jNfx6g==", "dev": true, "license": "MIT", "engines": { @@ -1305,9 +723,9 @@ } }, "node_modules/@angular/platform-browser": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.1.0.tgz", - "integrity": "sha512-l3+Ijq5SFxT0v10DbOyMc7NzGdbK76yot2i8pXyArlPSPmpWvbbjXbiBqzrv3TSTrksHBhG3mMvyhTmHQ1cQFA==", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.2.4.tgz", + "integrity": "sha512-81vzW8xhnJU7AiYJKXLR2MuvawzhRDgwyNkPEep58wty5zNuIUCXdUERJSsXo7m/U2Dg1FUFfqLm4RC2UkqLzA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1316,9 +734,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/animations": "20.1.0", - "@angular/common": "20.1.0", - "@angular/core": "20.1.0" + "@angular/animations": "20.2.4", + "@angular/common": "20.2.4", + "@angular/core": "20.2.4" }, "peerDependenciesMeta": { "@angular/animations": { @@ -1327,9 +745,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.1.0.tgz", - "integrity": "sha512-s+Rm2akzYTE2UFdXZPvf02TxDCDskGdUxAxa/jmJbVuOpniuY0RlbnxIKDUD0qj3bYMUkbr7f2KJwHVldqJP6w==", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.2.4.tgz", + "integrity": "sha512-ktunGTMWuWtnKUicOdXjF8Nc1RInf78YW7TqMV35rF32VXpHwRRKw2M7OKViPk18MlbDE2pc5HCX558BEUla0A==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1338,21 +756,21 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.1.0", - "@angular/compiler": "20.1.0", - "@angular/core": "20.1.0", - "@angular/platform-browser": "20.1.0" + "@angular/common": "20.2.4", + "@angular/compiler": "20.2.4", + "@angular/core": "20.2.4", + "@angular/platform-browser": "20.2.4" } }, "node_modules/@angular/pwa": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/pwa/-/pwa-20.1.0.tgz", - "integrity": "sha512-X11zbxlONAnNADMie9ISmFk0dMFO9u/0qN+fenYdwjZ+QtkZeFXy4gtsjFgym0gtsX2yuVxY4TiJ57R1QKa4MA==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@angular/pwa/-/pwa-20.2.2.tgz", + "integrity": "sha512-n7X0YsfF4LlqLg+xI7tmB0NhrQ8BGN4FGrLHeNo63wpz5mnIjMJaZCRk/eGWo3YjmtS4zPdLpNlU+3GsR/weVQ==", "license": "MIT", "dependencies": { - "@angular-devkit/schematics": "20.1.0", - "@schematics/angular": "20.1.0", - "parse5-html-rewriting-stream": "7.1.0" + "@angular-devkit/schematics": "20.2.2", + "@schematics/angular": "20.2.2", + "parse5-html-rewriting-stream": "8.0.0" }, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0", @@ -1360,7 +778,7 @@ "yarn": ">= 1.13.0" }, "peerDependencies": { - "@angular/cli": "^20.1.0" + "@angular/cli": "^20.2.2" }, "peerDependenciesMeta": { "@angular/cli": { @@ -1369,9 +787,9 @@ } }, "node_modules/@angular/router": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.1.0.tgz", - "integrity": "sha512-fuUX1+AhcVSDgSSx85o6VOtXKM3oXAza+44jQ+nJGf316P0xpLKA586DKRNPjS4sRsWM7otKuOOTXXc4AMUHpQ==", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.2.4.tgz", + "integrity": "sha512-KoduI1o+iBfCBGtXMvmy/qncDIwGxd2hNt2hDkkiYZTftmSg/XUJDxJqN84ckm2WLkdJpR9EirrwfHapJBIZOQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1380,16 +798,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.1.0", - "@angular/core": "20.1.0", - "@angular/platform-browser": "20.1.0", + "@angular/common": "20.2.4", + "@angular/core": "20.2.4", + "@angular/platform-browser": "20.2.4", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/service-worker": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-20.1.0.tgz", - "integrity": "sha512-ulJwc6L6QCYEjKNycsDHhzUneGqADFkdqeEBctXWym0adkngsjUUBzF4jCtT0KoCRHOcENfPLa1o1TaF0KkC7A==", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-20.2.4.tgz", + "integrity": "sha512-lBwgdBrwEwW0Mxn2tKA6JIKviegi6HvY+ujXre15B5KkeweSflrMQMayXOz/fdRz1hCB4/DoUQx3vn7GveYP4Q==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1401,7 +819,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "20.1.0", + "@angular/core": "20.2.4", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -1421,9 +839,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", - "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", "dev": true, "license": "MIT", "engines": { @@ -1431,22 +849,22 @@ } }, "node_modules/@babel/core": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", - "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.28.0", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -1479,14 +897,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", - "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -1560,15 +978,15 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -1621,27 +1039,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.0" + "@babel/types": "^7.28.4" }, "bin": { "parser": "bin/babel-parser.js" @@ -1666,18 +1084,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", - "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.0", + "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.0", + "@babel/types": "^7.28.4", "debug": "^4.3.1" }, "engines": { @@ -1685,9 +1103,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz", - "integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1722,10 +1140,44 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@emnapi/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", + "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", - "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", "cpu": [ "ppc64" ], @@ -1740,9 +1192,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", - "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", "cpu": [ "arm" ], @@ -1757,9 +1209,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", - "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", "cpu": [ "arm64" ], @@ -1774,9 +1226,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", - "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", "cpu": [ "x64" ], @@ -1791,9 +1243,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", - "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", "cpu": [ "arm64" ], @@ -1808,9 +1260,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", - "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", "cpu": [ "x64" ], @@ -1825,9 +1277,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", - "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", "cpu": [ "arm64" ], @@ -1842,9 +1294,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", - "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", "cpu": [ "x64" ], @@ -1859,9 +1311,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", - "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", "cpu": [ "arm" ], @@ -1876,9 +1328,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", - "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", "cpu": [ "arm64" ], @@ -1893,9 +1345,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", - "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", "cpu": [ "ia32" ], @@ -1910,9 +1362,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", - "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", "cpu": [ "loong64" ], @@ -1927,9 +1379,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", - "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", "cpu": [ "mips64el" ], @@ -1944,9 +1396,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", - "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", "cpu": [ "ppc64" ], @@ -1961,9 +1413,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", - "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", "cpu": [ "riscv64" ], @@ -1978,9 +1430,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", - "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", "cpu": [ "s390x" ], @@ -1995,9 +1447,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", - "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", "cpu": [ "x64" ], @@ -2012,9 +1464,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", - "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", "cpu": [ "arm64" ], @@ -2029,9 +1481,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", - "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", "cpu": [ "x64" ], @@ -2046,9 +1498,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", - "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", "cpu": [ "arm64" ], @@ -2063,9 +1515,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", - "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", "cpu": [ "x64" ], @@ -2079,10 +1531,27 @@ "node": ">=18" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", - "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", "cpu": [ "x64" ], @@ -2097,9 +1566,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", - "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", "cpu": [ "arm64" ], @@ -2114,9 +1583,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", - "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", "cpu": [ "ia32" ], @@ -2131,9 +1600,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", - "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", "cpu": [ "x64" ], @@ -2148,15 +1617,15 @@ } }, "node_modules/@inquirer/checkbox": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.9.tgz", - "integrity": "sha512-DBJBkzI5Wx4jFaYm221LHvAhpKYkhVS0k9plqHwaHhofGNxvYB7J3Bz8w+bFJ05zaMb0sZNHo4KdmENQFlNTuQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.2.2.tgz", + "integrity": "sha512-E+KExNurKcUJJdxmjglTl141EwxWyAHplvsYJQgSwXf8qiNWkTxTuCCqmhFEmbIXd4zLaGMfQFJ6WrZ7fSeV3g==", "devOptional": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.2.0", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, @@ -2172,33 +1641,15 @@ } } }, - "node_modules/@inquirer/checkbox/node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/@inquirer/confirm": { - "version": "5.1.13", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.13.tgz", - "integrity": "sha512-EkCtvp67ICIVVzjsquUiVSd+V5HRGOGQfsqA4E4vMWhYnB7InUL0pa0TIWt1i+OfP16Gkds8CdIu6yGZwOM1Yw==", + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", + "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", "devOptional": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7" + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" }, "engines": { "node": ">=18" @@ -2212,33 +1663,15 @@ } } }, - "node_modules/@inquirer/confirm/node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/@inquirer/core": { - "version": "10.1.14", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.14.tgz", - "integrity": "sha512-Ma+ZpOJPewtIYl6HZHZckeX1STvDnHTCB2GVINNUlSEn2Am6LddWwfPkIGY0IUFVjUUrr/93XlBwTK6mfLjf0A==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.2.0.tgz", + "integrity": "sha512-NyDSjPqhSvpZEMZrLCYUquWNl+XC/moEcVFqS55IEYIYsY0a1cUCevSqk7ctOlnm/RaSBU5psFryNlxcmGrjaA==", "devOptional": true, "license": "MIT", "dependencies": { - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", @@ -2258,47 +1691,16 @@ } } }, - "node_modules/@inquirer/core/node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/core/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "devOptional": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@inquirer/editor": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.14.tgz", - "integrity": "sha512-yd2qtLl4QIIax9DTMZ1ZN2pFrrj+yL3kgIWxm34SS6uwCr0sIhsNyudUjAo5q3TqI03xx4SEBkUJqZuAInp9uA==", + "version": "4.2.18", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.18.tgz", + "integrity": "sha512-yeQN3AXjCm7+Hmq5L6Dm2wEDeBRdAZuyZ4I7tWSSanbxDzqM0KqzoDbKM7p4ebllAYdoQuPJS6N71/3L281i6w==", "devOptional": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", - "external-editor": "^3.1.0" + "@inquirer/core": "^10.2.0", + "@inquirer/external-editor": "^1.0.1", + "@inquirer/type": "^3.0.8" }, "engines": { "node": ">=18" @@ -2312,33 +1714,15 @@ } } }, - "node_modules/@inquirer/editor/node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/@inquirer/expand": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.16.tgz", - "integrity": "sha512-oiDqafWzMtofeJyyGkb1CTPaxUkjIcSxePHHQCfif8t3HV9pHcw1Kgdw3/uGpDvaFfeTluwQtWiqzPVjAqS3zA==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.18.tgz", + "integrity": "sha512-xUjteYtavH7HwDMzq4Cn2X4Qsh5NozoDHCJTdoXg9HfZ4w3R6mxV1B9tL7DGJX2eq/zqtsFjhm0/RJIMGlh3ag==", "devOptional": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.2.0", + "@inquirer/type": "^3.0.8", "yoctocolors-cjs": "^2.1.2" }, "engines": { @@ -2353,12 +1737,16 @@ } } }, - "node_modules/@inquirer/expand/node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", + "node_modules/@inquirer/external-editor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.1.tgz", + "integrity": "sha512-Oau4yL24d2B5IL4ma4UpbQigkVhzPDXLoqy1ggK4gnHg/stmkffJE4oOXHXF3uz0UEpywG68KcyXsyYpA1Re/Q==", "devOptional": true, "license": "MIT", + "dependencies": { + "chardet": "^2.1.0", + "iconv-lite": "^0.6.3" + }, "engines": { "node": ">=18" }, @@ -2372,9 +1760,9 @@ } }, "node_modules/@inquirer/figures": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", - "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", + "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", "devOptional": true, "license": "MIT", "engines": { @@ -2382,14 +1770,14 @@ } }, "node_modules/@inquirer/input": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.0.tgz", - "integrity": "sha512-opqpHPB1NjAmDISi3uvZOTrjEEU5CWVu/HBkDby8t93+6UxYX0Z7Ps0Ltjm5sZiEbWenjubwUkivAEYQmy9xHw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.2.tgz", + "integrity": "sha512-hqOvBZj/MhQCpHUuD3MVq18SSoDNHy7wEnQ8mtvs71K8OPZVXJinOzcvQna33dNYLYE4LkA9BlhAhK6MJcsVbw==", "devOptional": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7" + "@inquirer/core": "^10.2.0", + "@inquirer/type": "^3.0.8" }, "engines": { "node": ">=18" @@ -2403,33 +1791,15 @@ } } }, - "node_modules/@inquirer/input/node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/@inquirer/number": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.16.tgz", - "integrity": "sha512-kMrXAaKGavBEoBYUCgualbwA9jWUx2TjMA46ek+pEKy38+LFpL9QHlTd8PO2kWPUgI/KB+qi02o4y2rwXbzr3Q==", + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.18.tgz", + "integrity": "sha512-7exgBm52WXZRczsydCVftozFTrrwbG5ySE0GqUd2zLNSBXyIucs2Wnm7ZKLe/aUu6NUg9dg7Q80QIHCdZJiY4A==", "devOptional": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7" + "@inquirer/core": "^10.2.0", + "@inquirer/type": "^3.0.8" }, "engines": { "node": ">=18" @@ -2443,33 +1813,15 @@ } } }, - "node_modules/@inquirer/number/node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/@inquirer/password": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.16.tgz", - "integrity": "sha512-g8BVNBj5Zeb5/Y3cSN+hDUL7CsIFDIuVxb9EPty3lkxBaYpjL5BNRKSYOF9yOLe+JOcKFd+TSVeADQ4iSY7rbg==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.18.tgz", + "integrity": "sha512-zXvzAGxPQTNk/SbT3carAD4Iqi6A2JS2qtcqQjsL22uvD+JfQzUrDEtPjLL7PLn8zlSNyPdY02IiQjzoL9TStA==", "devOptional": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.2.0", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2" }, "engines": { @@ -2484,41 +1836,23 @@ } } }, - "node_modules/@inquirer/password/node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/@inquirer/prompts": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.6.0.tgz", - "integrity": "sha512-jAhL7tyMxB3Gfwn4HIJ0yuJ5pvcB5maYUcouGcgd/ub79f9MqZ+aVnBtuFf+VC2GTkCBF+R+eo7Vi63w5VZlzw==", + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.8.2.tgz", + "integrity": "sha512-nqhDw2ZcAUrKNPwhjinJny903bRhI0rQhiDz1LksjeRxqa36i3l75+4iXbOy0rlDpLJGxqtgoPavQjmmyS5UJw==", "devOptional": true, "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^4.1.9", - "@inquirer/confirm": "^5.1.13", - "@inquirer/editor": "^4.2.14", - "@inquirer/expand": "^4.0.16", - "@inquirer/input": "^4.2.0", - "@inquirer/number": "^3.0.16", - "@inquirer/password": "^4.0.16", - "@inquirer/rawlist": "^4.1.4", - "@inquirer/search": "^3.0.16", - "@inquirer/select": "^4.2.4" + "@inquirer/checkbox": "^4.2.1", + "@inquirer/confirm": "^5.1.14", + "@inquirer/editor": "^4.2.17", + "@inquirer/expand": "^4.0.17", + "@inquirer/input": "^4.2.1", + "@inquirer/number": "^3.0.17", + "@inquirer/password": "^4.0.17", + "@inquirer/rawlist": "^4.1.5", + "@inquirer/search": "^3.1.0", + "@inquirer/select": "^4.3.1" }, "engines": { "node": ">=18" @@ -2533,14 +1867,14 @@ } }, "node_modules/@inquirer/rawlist": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.4.tgz", - "integrity": "sha512-5GGvxVpXXMmfZNtvWw4IsHpR7RzqAR624xtkPd1NxxlV5M+pShMqzL4oRddRkg8rVEOK9fKdJp1jjVML2Lr7TQ==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.6.tgz", + "integrity": "sha512-KOZqa3QNr3f0pMnufzL7K+nweFFCCBs6LCXZzXDrVGTyssjLeudn5ySktZYv1XiSqobyHRYYK0c6QsOxJEhXKA==", "devOptional": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.2.0", + "@inquirer/type": "^3.0.8", "yoctocolors-cjs": "^2.1.2" }, "engines": { @@ -2555,34 +1889,16 @@ } } }, - "node_modules/@inquirer/rawlist/node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/@inquirer/search": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.16.tgz", - "integrity": "sha512-POCmXo+j97kTGU6aeRjsPyuCpQQfKcMXdeTMw708ZMtWrj5aykZvlUxH4Qgz3+Y1L/cAVZsSpA+UgZCu2GMOMg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.1.1.tgz", + "integrity": "sha512-TkMUY+A2p2EYVY3GCTItYGvqT6LiLzHBnqsU1rJbrpXUijFfM6zvUx0R4civofVwFCmJZcKqOVwwWAjplKkhxA==", "devOptional": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.2.0", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "yoctocolors-cjs": "^2.1.2" }, "engines": { @@ -2597,34 +1913,16 @@ } } }, - "node_modules/@inquirer/search/node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/@inquirer/select": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.2.4.tgz", - "integrity": "sha512-unTppUcTjmnbl/q+h8XeQDhAqIOmwWYWNyiiP2e3orXrg6tOaa5DHXja9PChCSbChOsktyKgOieRZFnajzxoBg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.3.2.tgz", + "integrity": "sha512-nwous24r31M+WyDEHV+qckXkepvihxhnyIaod2MG7eCE6G0Zm/HUF6jgN8GXgf4U7AU6SLseKdanY195cwvU6w==", "devOptional": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.2.0", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, @@ -2640,10 +1938,10 @@ } } }, - "node_modules/@inquirer/select/node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", + "node_modules/@inquirer/type": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", + "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", "devOptional": true, "license": "MIT", "engines": { @@ -2658,22 +1956,27 @@ } } }, - "node_modules/@inquirer/type": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.6.tgz", - "integrity": "sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==", - "dev": true, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "devOptional": true, "license": "MIT", "engines": { - "node": ">=18" + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "engines": { + "node": "20 || >=22" } }, "node_modules/@isaacs/cliui": { @@ -2694,32 +1997,6 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -2745,22 +2022,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -2803,9 +2064,9 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { @@ -2824,15 +2085,15 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2841,48 +2102,26 @@ } }, "node_modules/@listr2/prompt-adapter-inquirer": { - "version": "2.0.22", - "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.22.tgz", - "integrity": "sha512-hV36ZoY+xKL6pYOt1nPNnkciFkn89KZwqLhAFzJvYysAvL5uBQdiADZx/8bIDXIukzzwG0QlPYolgMzQUtKgpQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.1.tgz", + "integrity": "sha512-3XFmGwm3u6ioREG+ynAQB7FoxfajgQnMhIu8wC5eo/Lsih4aKDg0VuIMGaOsYn7hJSJagSeaD4K8yfpkEoDEmA==", "devOptional": true, "license": "MIT", "dependencies": { - "@inquirer/type": "^1.5.5" + "@inquirer/type": "^3.0.7" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { - "@inquirer/prompts": ">= 3 < 8" - } - }, - "node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", - "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "mute-stream": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@listr2/prompt-adapter-inquirer/node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", - "devOptional": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "@inquirer/prompts": ">= 3 < 8", + "listr2": "9.0.1" } }, "node_modules/@lmdb/lmdb-darwin-arm64": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.4.1.tgz", - "integrity": "sha512-kKeP5PaY3bFrrF6GY5aDd96iuh1eoS+5CHJ+7hIP629KIEwzGNwbIzBmEX9TAhRJOivSRDTHCIsbu//+NsYKkg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.4.2.tgz", + "integrity": "sha512-NK80WwDoODyPaSazKbzd3NEJ3ygePrkERilZshxBViBARNz21rmediktGHExoj9n5t9+ChlgLlxecdFKLCuCKg==", "cpu": [ "arm64" ], @@ -2894,9 +2133,9 @@ ] }, "node_modules/@lmdb/lmdb-darwin-x64": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.4.1.tgz", - "integrity": "sha512-9CMB3seTyHs3EOVWdKiB8IIEDBJ3Gq00Tqyi0V7DS3HL90BjM/AkbZGuhzXwPrfeFazR24SKaRrUQF74f+CmWw==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.4.2.tgz", + "integrity": "sha512-zevaowQNmrp3U7Fz1s9pls5aIgpKRsKb3dZWDINtLiozh3jZI9fBrI19lYYBxqdyiIyNdlyiidPnwPShj4aK+w==", "cpu": [ "x64" ], @@ -2908,9 +2147,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-arm": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.4.1.tgz", - "integrity": "sha512-1Mi69vU0akHgCI7tF6YbimPaNEKJiBm/p5A+aM8egr0joj27cQmCCOm2mZQ+Ht2BqmCfZaIgQnMg4gFYNMlpCA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.4.2.tgz", + "integrity": "sha512-OmHCULY17rkx/RoCoXlzU7LyR8xqrksgdYWwtYa14l/sseezZ8seKWXcogHcjulBddER5NnEFV4L/Jtr2nyxeg==", "cpu": [ "arm" ], @@ -2922,9 +2161,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-arm64": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.4.1.tgz", - "integrity": "sha512-d0vuXOdoKjHHJYZ/CRWopnkOiUpev+bgBBW+1tXtWsYWUj8uxl9ZmTBEmsL5mjUlpQDrlYiJSrhOU1hg5QWBSw==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.4.2.tgz", + "integrity": "sha512-ZBEfbNZdkneebvZs98Lq30jMY8V9IJzckVeigGivV7nTHJc+89Ctomp1kAIWKlwIG0ovCDrFI448GzFPORANYg==", "cpu": [ "arm64" ], @@ -2936,9 +2175,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-x64": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.4.1.tgz", - "integrity": "sha512-00RbEpvfnyPodlICiGFuiOmyvWaL9nzCRSqZz82BVFsGTiSQnnF0gpD1C8tO6OvtptELbtRuM7BS9f97LcowZw==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.4.2.tgz", + "integrity": "sha512-vL9nM17C77lohPYE4YaAQvfZCSVJSryE4fXdi8M7uWPBnU+9DJabgKVAeyDb84ZM2vcFseoBE4/AagVtJeRE7g==", "cpu": [ "x64" ], @@ -2950,9 +2189,9 @@ ] }, "node_modules/@lmdb/lmdb-win32-arm64": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.4.1.tgz", - "integrity": "sha512-4h8tm3i1ODf+28UyqQZLP7c2jmRM26AyEEyYp994B4GiBdGvGAsYUu3oiHANYK9xFpvLuFzyGeqFm1kdNC0D1A==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.4.2.tgz", + "integrity": "sha512-SXWjdBfNDze4ZPeLtYIzsIeDJDJ/SdsA0pEXcUBayUIMO0FQBHfVZZyHXQjjHr4cvOAzANBgIiqaXRwfMhzmLw==", "cpu": [ "arm64" ], @@ -2964,9 +2203,9 @@ ] }, "node_modules/@lmdb/lmdb-win32-x64": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.4.1.tgz", - "integrity": "sha512-HqqKIhTbq6piJhkJpTTf3w1m/CgrmwXRAL9R9j7Ru5xdZSeO7Mg4AWiBC9B00uXR+LvVZKtUyRMVZfhmIZztmQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.4.2.tgz", + "integrity": "sha512-IY+r3bxKW6Q6sIPiMC0L533DEfRJSXibjSI3Ft/w9Q8KQBNqEIvUFXt+09wV8S5BRk0a8uSF19YWxuRwEfI90g==", "cpu": [ "x64" ], @@ -2978,9 +2217,9 @@ ] }, "node_modules/@maskito/angular": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-3.10.0.tgz", - "integrity": "sha512-5WwzV12MLJoCUD4ROEafUmyrElzGesWI4BqAFkh9jzzQRtrF1QNomK9tOVBXmXUBWb5sohiiNViAvRCtGdyXiA==", + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-3.10.3.tgz", + "integrity": "sha512-Wu64iLuuMZH/3fXgQSj15i/XRDcGdxIYY1eoq+zEUX0JkN+f1DLYzS4QVUMz/APNb7mnpnmNP0omr0feEWj+Kg==", "license": "Apache-2.0", "peer": true, "dependencies": { @@ -2989,35 +2228,35 @@ "peerDependencies": { "@angular/core": ">=16.0.0", "@angular/forms": ">=16.0.0", - "@maskito/core": "^3.10.0" + "@maskito/core": "^3.10.3" } }, "node_modules/@maskito/core": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@maskito/core/-/core-3.10.0.tgz", - "integrity": "sha512-T3PaMb4ipMmN9hkaAj8uyN0Mqj8XcXMZ1GRZ2WfZePRPHoi/L3tEEEh7vjg1m4TpI3lReRkNQs9yaPZV9ce8HA==", + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/@maskito/core/-/core-3.10.3.tgz", + "integrity": "sha512-4SZeEF6PjDHC+J5ADrJaSrFmgqmGkqfE5Yi6BrNXze9TGvVRy9aHJCizShFvheqCEu6MsK0XprZot28wH9AhjQ==", "license": "Apache-2.0", "peer": true }, "node_modules/@maskito/kit": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-3.10.0.tgz", - "integrity": "sha512-b/aN200U0w/tNfLPRiXJaHGZRNVimq7UnhjKYoLXejX1+pKKhQ6S/dVg9k0+30IXdmUJ5Uk29y5X3UBc5d1w8A==", + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-3.10.3.tgz", + "integrity": "sha512-4IAL5WPlz4zi6vCMp8KbSAVh67WT+o0PzQ56dU4E7crN1jzBm1cN7MIbGawefOIXwAiqCb8zOSyTv/qqSL0xGQ==", "license": "Apache-2.0", "peer": true, "peerDependencies": { - "@maskito/core": "^3.10.0" + "@maskito/core": "^3.10.3" } }, "node_modules/@maskito/phone": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@maskito/phone/-/phone-3.10.0.tgz", - "integrity": "sha512-FrjC0l/SyLvSH7w+MG9v3lVT3OnD098dVCBR8HZlL6l5oI1Y69LTEBgRrNWIso841V8AEmj1ryJwrjHWX/zF5Q==", + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/@maskito/phone/-/phone-3.10.3.tgz", + "integrity": "sha512-xt0WLrzLbxiS+0j5QoR6lBjU+FvtqfUL3BOEAKcopgUa8lrswZr3g6fRy0BCcvrzpf4Jdpj3FsZcBRd6ljiEkg==", "license": "Apache-2.0", "peer": true, "peerDependencies": { - "@maskito/core": "^3.10.0", - "@maskito/kit": "^3.10.0", + "@maskito/core": "^3.10.3", + "@maskito/kit": "^3.10.3", "libphonenumber-js": ">=1.0.0" } }, @@ -3035,9 +2274,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.13.3.tgz", - "integrity": "sha512-bGwA78F/U5G2jrnsdRkPY3IwIwZeWUEfb5o764b79lb0rJmMT76TLwKhdNZOWakOQtedYefwIR4emisEMvInKA==", + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz", + "integrity": "sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg==", "devOptional": true, "license": "MIT", "dependencies": { @@ -3167,9 +2406,9 @@ ] }, "node_modules/@napi-rs/nice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.4.tgz", - "integrity": "sha512-Sqih1YARrmMoHlXGgI9JrrgkzxcaaEso0AH+Y7j8NHonUs+xe4iDsgC3IBIDNdzEewbNpccNN6hip+b5vmyRLw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", + "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", "dev": true, "license": "MIT", "optional": true, @@ -3181,28 +2420,29 @@ "url": "https://github.com/sponsors/Brooooooklyn" }, "optionalDependencies": { - "@napi-rs/nice-android-arm-eabi": "1.0.4", - "@napi-rs/nice-android-arm64": "1.0.4", - "@napi-rs/nice-darwin-arm64": "1.0.4", - "@napi-rs/nice-darwin-x64": "1.0.4", - "@napi-rs/nice-freebsd-x64": "1.0.4", - "@napi-rs/nice-linux-arm-gnueabihf": "1.0.4", - "@napi-rs/nice-linux-arm64-gnu": "1.0.4", - "@napi-rs/nice-linux-arm64-musl": "1.0.4", - "@napi-rs/nice-linux-ppc64-gnu": "1.0.4", - "@napi-rs/nice-linux-riscv64-gnu": "1.0.4", - "@napi-rs/nice-linux-s390x-gnu": "1.0.4", - "@napi-rs/nice-linux-x64-gnu": "1.0.4", - "@napi-rs/nice-linux-x64-musl": "1.0.4", - "@napi-rs/nice-win32-arm64-msvc": "1.0.4", - "@napi-rs/nice-win32-ia32-msvc": "1.0.4", - "@napi-rs/nice-win32-x64-msvc": "1.0.4" + "@napi-rs/nice-android-arm-eabi": "1.1.1", + "@napi-rs/nice-android-arm64": "1.1.1", + "@napi-rs/nice-darwin-arm64": "1.1.1", + "@napi-rs/nice-darwin-x64": "1.1.1", + "@napi-rs/nice-freebsd-x64": "1.1.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/nice-linux-arm64-gnu": "1.1.1", + "@napi-rs/nice-linux-arm64-musl": "1.1.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", + "@napi-rs/nice-linux-s390x-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-musl": "1.1.1", + "@napi-rs/nice-openharmony-arm64": "1.1.1", + "@napi-rs/nice-win32-arm64-msvc": "1.1.1", + "@napi-rs/nice-win32-ia32-msvc": "1.1.1", + "@napi-rs/nice-win32-x64-msvc": "1.1.1" } }, "node_modules/@napi-rs/nice-android-arm-eabi": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.4.tgz", - "integrity": "sha512-OZFMYUkih4g6HCKTjqJHhMUlgvPiDuSLZPbPBWHLjKmFTv74COzRlq/gwHtmEVaR39mJQ6ZyttDl2HNMUbLVoA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", "cpu": [ "arm" ], @@ -3217,9 +2457,9 @@ } }, "node_modules/@napi-rs/nice-android-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.0.4.tgz", - "integrity": "sha512-k8u7cjeA64vQWXZcRrPbmwjH8K09CBnNaPnI9L1D5N6iMPL3XYQzLcN6WwQonfcqCDv5OCY3IqX89goPTV4KMw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", "cpu": [ "arm64" ], @@ -3234,9 +2474,9 @@ } }, "node_modules/@napi-rs/nice-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-GsLdQvUcuVzoyzmtjsThnpaVEizAqH5yPHgnsBmq3JdVoVZHELFo7PuJEdfOH1DOHi2mPwB9sCJEstAYf3XCJA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", "cpu": [ "arm64" ], @@ -3251,9 +2491,9 @@ } }, "node_modules/@napi-rs/nice-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.0.4.tgz", - "integrity": "sha512-1y3gyT3e5zUY5SxRl3QDtJiWVsbkmhtUHIYwdWWIQ3Ia+byd/IHIEpqAxOGW1nhhnIKfTCuxBadHQb+yZASVoA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", "cpu": [ "x64" ], @@ -3268,9 +2508,9 @@ } }, "node_modules/@napi-rs/nice-freebsd-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.0.4.tgz", - "integrity": "sha512-06oXzESPRdXUuzS8n2hGwhM2HACnDfl3bfUaSqLGImM8TA33pzDXgGL0e3If8CcFWT98aHows5Lk7xnqYNGFeA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", "cpu": [ "x64" ], @@ -3285,9 +2525,9 @@ } }, "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.0.4.tgz", - "integrity": "sha512-CgklZ6g8WL4+EgVVkxkEvvsi2DSLf9QIloxWO0fvQyQBp6VguUSX3eHLeRpqwW8cRm2Hv/Q1+PduNk7VK37VZw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", "cpu": [ "arm" ], @@ -3302,9 +2542,9 @@ } }, "node_modules/@napi-rs/nice-linux-arm64-gnu": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.0.4.tgz", - "integrity": "sha512-wdAJ7lgjhAlsANUCv0zi6msRwq+D4KDgU+GCCHssSxWmAERZa2KZXO0H2xdmoJ/0i03i6YfK/sWaZgUAyuW2oQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", "cpu": [ "arm64" ], @@ -3319,9 +2559,9 @@ } }, "node_modules/@napi-rs/nice-linux-arm64-musl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.0.4.tgz", - "integrity": "sha512-4b1KYG+sriufhFrpUS9uNOEYYJqSfcbnwGx6uGX7JjrH8tELG90cOpCawz5THNIwlS3DhLgnCOcn0+4p6z26QA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", "cpu": [ "arm64" ], @@ -3336,9 +2576,9 @@ } }, "node_modules/@napi-rs/nice-linux-ppc64-gnu": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.0.4.tgz", - "integrity": "sha512-iaf3vMRgr23oe1PUaKpxaH3DS0IMN0+N9iEiWVwYPm/U15vZFYdqVegGfN2PzrZLUl5lc8ZxbmEKDfuqslhAMA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", "cpu": [ "ppc64" ], @@ -3353,9 +2593,9 @@ } }, "node_modules/@napi-rs/nice-linux-riscv64-gnu": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.0.4.tgz", - "integrity": "sha512-UXoREY6Yw6rHrGuTwQgBxpfjK34t6mTjibE9/cXbefL9AuUCJ9gEgwNKZiONuR5QGswChqo9cnthjdKkYyAdDg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", "cpu": [ "riscv64" ], @@ -3370,9 +2610,9 @@ } }, "node_modules/@napi-rs/nice-linux-s390x-gnu": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.0.4.tgz", - "integrity": "sha512-eFbgYCRPmsqbYPAlLYU5hYTNbogmIDUvknilehHsFhCH1+0/kN87lP+XaLT0Yeq4V/rpwChSd9vlz4muzFArtw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", "cpu": [ "s390x" ], @@ -3387,9 +2627,9 @@ } }, "node_modules/@napi-rs/nice-linux-x64-gnu": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.0.4.tgz", - "integrity": "sha512-4T3E6uTCwWT6IPnwuPcWVz3oHxvEp/qbrCxZhsgzwTUBEwu78EGNXGdHfKJQt3soth89MLqZJw+Zzvnhrsg1mQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", + "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", "cpu": [ "x64" ], @@ -3404,9 +2644,9 @@ } }, "node_modules/@napi-rs/nice-linux-x64-musl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.0.4.tgz", - "integrity": "sha512-NtbBkAeyBPLvCBkWtwkKXkNSn677eaT0cX3tygq+2qVv71TmHgX4gkX6o9BXjlPzdgPGwrUudavCYPT9tzkEqQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", + "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", "cpu": [ "x64" ], @@ -3420,10 +2660,27 @@ "node": ">= 10" } }, + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@napi-rs/nice-win32-arm64-msvc": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.0.4.tgz", - "integrity": "sha512-vubOe3i+YtSJGEk/++73y+TIxbuVHi+W8ZzrRm2eETCjCRwNlgbfToQZ85dSA+4iBB/NJRGNp+O4hfdbbttZWA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", "cpu": [ "arm64" ], @@ -3438,9 +2695,9 @@ } }, "node_modules/@napi-rs/nice-win32-ia32-msvc": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.0.4.tgz", - "integrity": "sha512-BMOVrUDZeg1RNRKVlh4eyLv5djAAVLiSddfpuuQ47EFjBcklg0NUeKMFKNrKQR4UnSn4HAiACLD7YK7koskwmg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", "cpu": [ "ia32" ], @@ -3455,9 +2712,9 @@ } }, "node_modules/@napi-rs/nice-win32-x64-msvc": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.0.4.tgz", - "integrity": "sha512-kCNk6HcRZquhw/whwh4rHsdPyOSCQCgnVDVik+Y9cuSVTDy3frpiCJTScJqPPS872h4JgZKkr/+CwcwttNEo9Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", "cpu": [ "x64" ], @@ -3471,6 +2728,19 @@ "node": ">= 10" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.3.tgz", + "integrity": "sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.5", + "@emnapi/runtime": "^1.4.5", + "@tybys/wasm-util": "^0.10.0" + } + }, "node_modules/@ng-web-apis/common": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/@ng-web-apis/common/-/common-4.12.0.tgz", @@ -3554,12 +2824,12 @@ } }, "node_modules/@noble/curves": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", - "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", "license": "MIT", "dependencies": { - "@noble/hashes": "1.7.1" + "@noble/hashes": "1.8.0" }, "engines": { "node": "^14.21.3 || >=16" @@ -3569,9 +2839,9 @@ } }, "node_modules/@noble/hashes": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", - "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -3754,10 +3024,30 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "devOptional": true, + "license": "ISC" + }, "node_modules/@npmcli/promise-spawn": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.2.tgz", - "integrity": "sha512-/bNJhjc+o6qL+Dwz/bqfTQClkEO5nTQ1ZEcdCkAQjhkZMHIh22LPG7fNh1enJP1NKWDqYiiABnjFCY7E0zHYtQ==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.3.tgz", + "integrity": "sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==", "devOptional": true, "license": "ISC", "dependencies": { @@ -3847,6 +3137,26 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/@oxc-project/runtime": { + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.81.0.tgz", + "integrity": "sha512-zm/LDVOq9FEmHiuM8zO4DWirv0VP2Tv2VsgaiHby9nvpq+FVrcqNYgv+TysLKOITQXWZj/roluTxFvpkHP0Iuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.81.0.tgz", + "integrity": "sha512-CnOqkybZK8z6Gx7Wb1qF7AEnSzbol1WwcIzxYOr8e91LytGOjo0wCpgoYWZo8sdbpqX+X+TJayIzo4Pv0R/KjA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", @@ -4190,6 +3500,212 @@ "node": ">=14" } }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.32.tgz", + "integrity": "sha512-Gs+313LfR4Ka3hvifdag9r44WrdKQaohya7ZXUXzARF7yx0atzFlVZjsvxtKAw1Vmtr4hB/RjUD1jf73SW7zDw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.32.tgz", + "integrity": "sha512-W8oMqzGcI7wKPXUtS3WJNXzbghHfNiuM1UBAGpVb+XlUCgYRQJd2PRGP7D3WGql3rR3QEhUvSyAuCBAftPQw6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.32.tgz", + "integrity": "sha512-pM4c4sKUk37noJrnnDkJknLhCsfZu7aWyfe67bD0GQHfzAPjV16wPeD9CmQg4/0vv+5IfHYaa4VE536xbA+W0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.32.tgz", + "integrity": "sha512-M8SUgFlYb5kJJWcFC8gUMRiX4WLFxPKMed3SJ2YrxontgIrEcpizPU8nLNVsRYEStoSfKHKExpQw3OP6fm+5bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.32.tgz", + "integrity": "sha512-FuQpbNC/hE//bvv29PFnk0AtpJzdPdYl5CMhlWPovd9g3Kc3lw9TrEPIbL7gRPUdhKAiq6rVaaGvOnXxsa0eww==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.32.tgz", + "integrity": "sha512-hRZygRlaGCjcNTNY9GV7dDI18sG1dK3cc7ujHq72LoDad23zFDUGMQjiSxHWK+/r92iMV+j2MiHbvzayxqynsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.32.tgz", + "integrity": "sha512-HzgT6h+CXLs+GKAU0Wvkt3rvcv0CmDBsDjlPhh4GHysOKbG9NjpKYX2zvjx671E9pGbTvcPpwy7gGsy7xpu+8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.32.tgz", + "integrity": "sha512-Ab/wbf6gdzphDbsg51UaxsC93foQ7wxhtg0SVCXd25BrV4MAJ1HoDtKN/f4h0maFmJobkqYub2DlmoasUzkvBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.32.tgz", + "integrity": "sha512-VoxqGEfh5A1Yx+zBp/FR5QwAbtzbuvky2SVc+ii4g1gLD4zww6mt/hPi5zG+b88zYPFBKHpxMtsz9cWqXU5V5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.32.tgz", + "integrity": "sha512-qZ1ViyOUDGbiZrSAJ/FIAhYUElDfVxxFW6DLT/w4KeoZN3HsF4jmRP95mXtl51/oGrqzU9l9Q2f7/P4O/o2ZZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.32.tgz", + "integrity": "sha512-hEkG3wD+f3wytV0lqwb/uCrXc4r4Ny/DWJFJPfQR3VeMWplhWGgSHNwZc2Q7k86Yi36f9NNzzWmrIuvHI9lCVw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.32.tgz", + "integrity": "sha512-k3MvDf8SiA7uP2ikP0unNouJ2YCrnwi7xcVW+RDgMp5YXVr3Xu6svmT3HGn0tkCKUuPmf+uy8I5uiHt5qWQbew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rolldown/binding-win32-ia32-msvc": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.32.tgz", + "integrity": "sha512-wAi/FxGh7arDOUG45UmnXE1sZUa0hY4cXAO2qWAjFa3f7bTgz/BqwJ7XN5SUezvAJPNkME4fEpInfnBvM25a0w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.32.tgz", + "integrity": "sha512-Ej0i4PZk8ltblZtzVK8ouaGUacUtxRmTm5S9794mdyU/tYxXjAJNseOfxrnHpMWKjMDrOKbqkPqJ52T9NR4LQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.32.tgz", + "integrity": "sha512-QReCdvxiUZAPkvp1xpAg62IeNzykOFA6syH2CnClif4YmALN1XKpB39XneL80008UbtMShthSVDKmrx05N1q/g==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/plugin-json": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", @@ -4212,9 +3728,9 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", - "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -4242,9 +3758,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", - "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.1.tgz", + "integrity": "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==", "cpu": [ "arm" ], @@ -4256,9 +3772,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz", - "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.1.tgz", + "integrity": "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==", "cpu": [ "arm64" ], @@ -4270,9 +3786,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz", - "integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.1.tgz", + "integrity": "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==", "cpu": [ "arm64" ], @@ -4284,9 +3800,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz", - "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.1.tgz", + "integrity": "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==", "cpu": [ "x64" ], @@ -4298,9 +3814,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz", - "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.1.tgz", + "integrity": "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==", "cpu": [ "arm64" ], @@ -4312,9 +3828,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz", - "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.1.tgz", + "integrity": "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==", "cpu": [ "x64" ], @@ -4326,9 +3842,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz", - "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.1.tgz", + "integrity": "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==", "cpu": [ "arm" ], @@ -4340,9 +3856,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz", - "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.1.tgz", + "integrity": "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==", "cpu": [ "arm" ], @@ -4354,9 +3870,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz", - "integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.1.tgz", + "integrity": "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==", "cpu": [ "arm64" ], @@ -4368,9 +3884,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz", - "integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.1.tgz", + "integrity": "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==", "cpu": [ "arm64" ], @@ -4382,9 +3898,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz", - "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.1.tgz", + "integrity": "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==", "cpu": [ "loong64" ], @@ -4395,10 +3911,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz", - "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.1.tgz", + "integrity": "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==", "cpu": [ "ppc64" ], @@ -4410,9 +3926,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz", - "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.1.tgz", + "integrity": "sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==", "cpu": [ "riscv64" ], @@ -4424,9 +3940,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz", - "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.1.tgz", + "integrity": "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==", "cpu": [ "riscv64" ], @@ -4438,9 +3954,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz", - "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.1.tgz", + "integrity": "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==", "cpu": [ "s390x" ], @@ -4452,9 +3968,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz", - "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.1.tgz", + "integrity": "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==", "cpu": [ "x64" ], @@ -4466,9 +3982,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz", - "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.1.tgz", + "integrity": "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==", "cpu": [ "x64" ], @@ -4479,10 +3995,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.1.tgz", + "integrity": "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz", - "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.1.tgz", + "integrity": "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==", "cpu": [ "arm64" ], @@ -4494,9 +4024,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz", - "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.1.tgz", + "integrity": "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==", "cpu": [ "ia32" ], @@ -4508,9 +4038,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz", - "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.1.tgz", + "integrity": "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==", "cpu": [ "x64" ], @@ -4522,9 +4052,9 @@ ] }, "node_modules/@rollup/wasm-node": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/wasm-node/-/wasm-node-4.44.2.tgz", - "integrity": "sha512-bKl9rKDle9BbO/Tb5RFU4PDYtRcxNdllUL2cC6vixXkxV0U5XOIoi11KUNVoKyB2RLRSk3BHouMfw9fJdMsJkg==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/wasm-node/-/wasm-node-4.50.1.tgz", + "integrity": "sha512-3oCUcKNdkemnqy6r12UdAtfYMWywGxVHSCQvtDYeEtnOcOQC/SihSXkO6+rByH2ZhbgfeTbqLiw1NDGfJDptyg==", "dev": true, "license": "MIT", "dependencies": { @@ -4549,13 +4079,13 @@ "license": "MIT" }, "node_modules/@schematics/angular": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.1.0.tgz", - "integrity": "sha512-sAEwygjY/j0tvo+EDFUAc54Hfp++K43ISe1/fdCU/M3Pseuf7oPPIm6VxxTrRc6fu4Lp5DBaD/PBkXNt/FqZpg==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.2.2.tgz", + "integrity": "sha512-VzJsEIiBmHzJAOVaKHn1CwTuOqvI1GwZuneUk/tmyYKkKdWEgxnoNBvz1ql6eHstkLz3S9yt6aUuAgjQC+J2Xw==", "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.1.0", - "@angular-devkit/schematics": "20.1.0", + "@angular-devkit/core": "20.2.2", + "@angular-devkit/schematics": "20.2.2", "jsonc-parser": "3.3.1" }, "engines": { @@ -4564,65 +4094,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.1.0.tgz", - "integrity": "sha512-i2t22bklvKsqdwmUtjXltRyxmJ+lJW8isrdc7XeN0N6VW/lDHSJqFlucT1+pO9+FxXJQyz3Hc1dpRd6G65mGyw==", - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.2", - "source-map": "0.7.4" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^4.0.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@schematics/angular/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@schematics/angular/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@sigstore/bundle": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", @@ -4713,9 +4184,9 @@ "link": true }, "node_modules/@taiga-ui/addon-charts": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.44.0.tgz", - "integrity": "sha512-NiwY1P1NkDEOiSWgo3EGmXBWFmltmKA+Xkbu7fHnH7+8oenYLWd/orNvQEU5ey6qiSGj6wVr7kyeQMP8aau3NQ==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.52.0.tgz", + "integrity": "sha512-FFH9kJU15SDcaxBRFl5l6gTts6UJpsK2KFISHqkPon6A9bJYMEPYAqE6MDSUvLK+H6WuuX+D2FCgIlxT7kTicw==", "license": "Apache-2.0", "dependencies": { "tslib": ">=2.8.1" @@ -4724,15 +4195,15 @@ "@angular/common": ">=16.0.0", "@angular/core": ">=16.0.0", "@ng-web-apis/common": "^4.12.0", - "@taiga-ui/cdk": "^4.44.0", - "@taiga-ui/core": "^4.44.0", + "@taiga-ui/cdk": "^4.52.0", + "@taiga-ui/core": "^4.52.0", "@taiga-ui/polymorpheus": "^4.9.0" } }, "node_modules/@taiga-ui/addon-commerce": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.44.0.tgz", - "integrity": "sha512-hpYDis6cFDewm1PR6CTXDoOvGmPbqJwcrl1wOyAfmGWCY4rle9+Jj1P1fW4tDMkOY/TVhQ3GIuSUYn/UZN9v4A==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.52.0.tgz", + "integrity": "sha512-GRryJ+lwhGSG66GK6nbxPjOJjDPmg4iIQr+KaSbrVK8FIpANTWQjuGP3tGgZbIDATPEkrkyx1Sb/FII6HA2utw==", "license": "Apache-2.0", "dependencies": { "tslib": ">=2.8.1" @@ -4741,22 +4212,22 @@ "@angular/common": ">=16.0.0", "@angular/core": ">=16.0.0", "@angular/forms": ">=16.0.0", - "@maskito/angular": "^3.10.0", - "@maskito/core": "^3.10.0", - "@maskito/kit": "^3.10.0", + "@maskito/angular": "^3.10.3", + "@maskito/core": "^3.10.3", + "@maskito/kit": "^3.10.3", "@ng-web-apis/common": "^4.12.0", - "@taiga-ui/cdk": "^4.44.0", - "@taiga-ui/core": "^4.44.0", - "@taiga-ui/i18n": "^4.44.0", - "@taiga-ui/kit": "^4.44.0", + "@taiga-ui/cdk": "^4.52.0", + "@taiga-ui/core": "^4.52.0", + "@taiga-ui/i18n": "^4.52.0", + "@taiga-ui/kit": "^4.52.0", "@taiga-ui/polymorpheus": "^4.9.0", "rxjs": ">=7.0.0" } }, "node_modules/@taiga-ui/addon-mobile": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.44.0.tgz", - "integrity": "sha512-NTV6DpyrI6Pv9FuXiYwOsOljyKkrKX+HJ35SRev1hrxzw9ECKrnOA1Q0aUCn09PgWIUY7rV+OXUZJ/w/Hmlm5Q==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.52.0.tgz", + "integrity": "sha512-xd0F9jGtr12SA05I850E5BPexr8nT9T5ZaqUPwRWhyzR9EJwsYrafLLovL50r4Ur1p1ZoZrK8Y3GRSJPuwYBTA==", "license": "Apache-2.0", "dependencies": { "tslib": ">=2.8.1" @@ -4766,18 +4237,18 @@ "@angular/common": ">=16.0.0", "@angular/core": ">=16.0.0", "@ng-web-apis/common": "^4.12.0", - "@taiga-ui/cdk": "^4.44.0", - "@taiga-ui/core": "^4.44.0", - "@taiga-ui/kit": "^4.44.0", - "@taiga-ui/layout": "^4.44.0", + "@taiga-ui/cdk": "^4.52.0", + "@taiga-ui/core": "^4.52.0", + "@taiga-ui/kit": "^4.52.0", + "@taiga-ui/layout": "^4.52.0", "@taiga-ui/polymorpheus": "^4.9.0", "rxjs": ">=7.0.0" } }, "node_modules/@taiga-ui/addon-table": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.44.0.tgz", - "integrity": "sha512-DmWZCPouoF21gWOpABepncWdg8W4Enk2z8FTiwGMsN0nGXmQ1SnU2QwEHCLumW2XNiNP8EHUpU42MZf9CuwIeA==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.52.0.tgz", + "integrity": "sha512-JadX1QCe5Cf2Kzc9libg2x7Nk2237ZHIyFgNmujYQ2a/ytGlKFpMajCLVarj2LHwQgFMbdijMk8muFTJm2MGzA==", "license": "Apache-2.0", "dependencies": { "tslib": ">=2.8.1" @@ -4786,18 +4257,18 @@ "@angular/common": ">=16.0.0", "@angular/core": ">=16.0.0", "@ng-web-apis/intersection-observer": "^4.12.0", - "@taiga-ui/cdk": "^4.44.0", - "@taiga-ui/core": "^4.44.0", - "@taiga-ui/i18n": "^4.44.0", - "@taiga-ui/kit": "^4.44.0", + "@taiga-ui/cdk": "^4.52.0", + "@taiga-ui/core": "^4.52.0", + "@taiga-ui/i18n": "^4.52.0", + "@taiga-ui/kit": "^4.52.0", "@taiga-ui/polymorpheus": "^4.9.0", "rxjs": ">=7.0.0" } }, "node_modules/@taiga-ui/cdk": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.44.0.tgz", - "integrity": "sha512-4x0ISp+0oYhTC2E9SN2yDhA+rFC707m/rL4lf7RSTIbdefMyP3TdmsN2Emuhc0WSXcNs0OgYYEOaIx9jr7OqDw==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.52.0.tgz", + "integrity": "sha512-rYs6xSVWuShQBbLul+7z/SnFeGe+j4qxJwZYb0C6CfAJLHoeIBjEwgY6cT07Eidu91fKaa8EMtETrkXxH5jkPQ==", "license": "Apache-2.0", "dependencies": { "tslib": "2.8.1" @@ -4807,7 +4278,7 @@ "@angular-devkit/schematics": ">=16.0.0", "@schematics/angular": ">=16.0.0", "ng-morph": "^4.8.4", - "parse5": ">=7.3.0" + "parse5": "^8.0.0" }, "peerDependencies": { "@angular/animations": ">=16.0.0", @@ -4826,9 +4297,9 @@ } }, "node_modules/@taiga-ui/core": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.44.0.tgz", - "integrity": "sha512-KQPD63ZoFJKBZS/m1XkyTXDzUH3IYDcgNE3CVeZ9bmEkRwS0x7/fpFFdbr06sR/ej/eEzgop3LKOk4JHoQgFCA==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.52.0.tgz", + "integrity": "sha512-MLt2A+SBm4wrp0/SE7zOA70al+ZamoDwZB3OB4e9YmQhsFTULqZxFIWZWZrBaQOtVVOt3mHFOK20rRnCwVYlrg==", "license": "Apache-2.0", "dependencies": { "tslib": ">=2.8.1" @@ -4842,9 +4313,9 @@ "@angular/router": ">=16.0.0", "@ng-web-apis/common": "^4.12.0", "@ng-web-apis/mutation-observer": "^4.12.0", - "@taiga-ui/cdk": "^4.44.0", + "@taiga-ui/cdk": "^4.52.0", "@taiga-ui/event-plugins": "^4.6.0", - "@taiga-ui/i18n": "^4.44.0", + "@taiga-ui/i18n": "^4.52.0", "@taiga-ui/polymorpheus": "^4.9.0", "rxjs": ">=7.0.0" } @@ -4865,9 +4336,9 @@ } }, "node_modules/@taiga-ui/event-plugins": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/event-plugins/-/event-plugins-4.6.0.tgz", - "integrity": "sha512-5qshJXrwpJmsrdePjOofF8nr+6ONk7fXRdVG4MH0BGSLiV6ChwUm9K78ROjanVyI4vEkoFqBbh1bhS6DdJTwhA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/event-plugins/-/event-plugins-4.7.0.tgz", + "integrity": "sha512-j3HPRPR7XxKxgMeytb+r/CNUoLBMVrfdfL8KJr1XiFO9jyEvoC4chFXDXWlkGyUHJIC6wy5VIXlIlI/kpqOiGg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.3.0" @@ -4879,9 +4350,9 @@ } }, "node_modules/@taiga-ui/experimental": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-4.44.0.tgz", - "integrity": "sha512-ef/fSFjSOTwgsqRoWlwlXQkQrPcbuAlmEyGj4gMhdZC99HvKuYzUdihH4eZX4nx8fDyBOOZbM7QnW7ETQbKWHg==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-4.52.0.tgz", + "integrity": "sha512-wq1zpYFeT1MAbu7NX0orWE5zqjrJbzUczGDYkzhk8lpiLMf9QA/R8Yp0VKQOXj/l36tGDNr0fhBtZ9d4DN/BUA==", "license": "Apache-2.0", "dependencies": { "tslib": ">=2.8.1" @@ -4889,18 +4360,19 @@ "peerDependencies": { "@angular/common": ">=16.0.0", "@angular/core": ">=16.0.0", - "@taiga-ui/addon-commerce": "^4.44.0", - "@taiga-ui/cdk": "^4.44.0", - "@taiga-ui/core": "^4.44.0", - "@taiga-ui/kit": "^4.44.0", + "@taiga-ui/addon-commerce": "^4.52.0", + "@taiga-ui/cdk": "^4.52.0", + "@taiga-ui/core": "^4.52.0", + "@taiga-ui/kit": "^4.52.0", + "@taiga-ui/layout": "^4.52.0", "@taiga-ui/polymorpheus": "^4.9.0", "rxjs": ">=7.0.0" } }, "node_modules/@taiga-ui/i18n": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.44.0.tgz", - "integrity": "sha512-SnRQQPKI1k3Tkn17yByuB1ix4Nj9v70RLxVM84GX3okgQk2NIAj9hn8WXd36ljZUlcy7TqtJ+xlii0FqQVv7Sw==", + "version": "4.53.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.53.0.tgz", + "integrity": "sha512-JCGh87+0KYUTyzDxLms3gHoEbYMu7gEhFWvrZxGgYKS/wAAA5nj5P7b4bjRhpYVfqda7oXJ/wwYFynDd7/cLCw==", "license": "Apache-2.0", "peer": true, "dependencies": { @@ -4913,18 +4385,18 @@ } }, "node_modules/@taiga-ui/icons": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.44.0.tgz", - "integrity": "sha512-KiIxxkG59kJDFV/g6e1YAbBff7y7EynNbmKY6sGo+CLcAQ52Y7Cz6i9us6ySf8fbOQRm4hKDXL3M10TKDeS+cg==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.52.0.tgz", + "integrity": "sha512-a/mjQ7wjzVGMuK0GosjvO+oH1KzsKLrz+3pLJh+z79iSC3tH+jsaUbI3HNWWgI+0MHHDLSA31yOy1EHLLDRoUw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.3.0" } }, "node_modules/@taiga-ui/kit": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.44.0.tgz", - "integrity": "sha512-X8JNEE/WR7ftEx/bmibe2JTPOwge/o1VYHk1Z2vDx53e8JWWG+lLce2xx7NYexFPqP+4Ey2bPxn1Vx+rgGZYgA==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.52.0.tgz", + "integrity": "sha512-ePym6t9KsUfBTfqSwdCf8lUx+sU0hmbLpUdJGNg3KlOTPI6k7zV3bLQjN1hnOU6eoZjaqvFYNMz+D0MbgiZiIQ==", "license": "Apache-2.0", "dependencies": { "tslib": ">=2.8.1" @@ -4934,25 +4406,25 @@ "@angular/core": ">=16.0.0", "@angular/forms": ">=16.0.0", "@angular/router": ">=16.0.0", - "@maskito/angular": "^3.10.0", - "@maskito/core": "^3.10.0", - "@maskito/kit": "^3.10.0", - "@maskito/phone": "^3.10.0", + "@maskito/angular": "^3.10.3", + "@maskito/core": "^3.10.3", + "@maskito/kit": "^3.10.3", + "@maskito/phone": "^3.10.3", "@ng-web-apis/common": "^4.12.0", "@ng-web-apis/intersection-observer": "^4.12.0", "@ng-web-apis/mutation-observer": "^4.12.0", "@ng-web-apis/resize-observer": "^4.12.0", - "@taiga-ui/cdk": "^4.44.0", - "@taiga-ui/core": "^4.44.0", - "@taiga-ui/i18n": "^4.44.0", + "@taiga-ui/cdk": "^4.52.0", + "@taiga-ui/core": "^4.52.0", + "@taiga-ui/i18n": "^4.52.0", "@taiga-ui/polymorpheus": "^4.9.0", "rxjs": ">=7.0.0" } }, "node_modules/@taiga-ui/layout": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.44.0.tgz", - "integrity": "sha512-aidH7MgEAcz0nxauc5tm+0a5Z7PmG9bsSq+tsfkiOd6mV4BOf5xyYlpDyQn9gUBK8UYSxtMojp9Zs/2jH57gmw==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.52.0.tgz", + "integrity": "sha512-Oqsi5v9Awj9dA5NIf9q+El4xtuKvNxDJKGY7yvyMp7MzFjEvjdb18Rg24r0dXiOFjayqsXHBbTezp163ad54+Q==", "license": "Apache-2.0", "dependencies": { "tslib": ">=2.8.1" @@ -4960,25 +4432,13 @@ "peerDependencies": { "@angular/common": ">=16.0.0", "@angular/core": ">=16.0.0", - "@taiga-ui/cdk": "^4.44.0", - "@taiga-ui/core": "^4.44.0", - "@taiga-ui/kit": "^4.44.0", + "@taiga-ui/cdk": "^4.52.0", + "@taiga-ui/core": "^4.52.0", + "@taiga-ui/kit": "^4.52.0", "@taiga-ui/polymorpheus": "^4.9.0", "rxjs": ">=7.0.0" } }, - "node_modules/@taiga-ui/legacy": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/legacy/-/legacy-4.44.0.tgz", - "integrity": "sha512-vVXEj2BkKAS3ShOQ8OV6+j2yGFcRqtXMxZEU3kX8iM4fWRYS3C1669LLK7f0VzZd1miNex79SZx3NJM4Rvx7Kg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": ">=2.8.1" - }, - "peerDependencies": { - "@angular/core": ">=16.0.0" - } - }, "node_modules/@taiga-ui/polymorpheus": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@taiga-ui/polymorpheus/-/polymorpheus-4.9.0.tgz", @@ -5105,6 +4565,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", + "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/dompurify": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", @@ -5156,9 +4627,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.15.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.19.tgz", - "integrity": "sha512-3vMNr4TzNQyjHcRZadojpRaD9Ofr6LsonZAoQ+HMUa/9ORTPoxVIw0e0mpqWpdjj8xybyCM+oKOUH2vwFu/oEw==", + "version": "22.18.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.1.tgz", + "integrity": "sha512-rzSDyhn4cYznVG+PCzGe1lwuMYJrcBS1fc3JqSa2PvtABwWo+dZ1ij5OVok3tqfpEBCBoaR4d7upFJk73HRJDw==", "dev": true, "license": "MIT", "dependencies": { @@ -5205,6 +4676,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.0.tgz", + "integrity": "sha512-dOxxrhgyDIEUADhb/8OlV9JIqYLgos03YorAueTIeOUskLJSEsfwCByjbu98ctXitUN3znXKp0bYD/WHSudCeA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0" + } + }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -5237,9 +4721,9 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -5263,9 +4747,9 @@ } }, "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "devOptional": true, "license": "MIT", "engines": { @@ -5305,6 +4789,32 @@ } } }, + "node_modules/algoliasearch": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.35.0.tgz", + "integrity": "sha512-Y+moNhsqgLmvJdgTsO4GZNgsaDWv8AOGAaPeIeHKlDn/XunoAqYbA+XNpBd1dW8GOXAUDyxC9Rxc7AV4kpFcIg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.1.0", + "@algolia/client-abtesting": "5.35.0", + "@algolia/client-analytics": "5.35.0", + "@algolia/client-common": "5.35.0", + "@algolia/client-insights": "5.35.0", + "@algolia/client-personalization": "5.35.0", + "@algolia/client-query-suggestions": "5.35.0", + "@algolia/client-search": "5.35.0", + "@algolia/ingestion": "1.35.0", + "@algolia/monitoring": "1.35.0", + "@algolia/recommend": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -5332,24 +4842,25 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "devOptional": true, "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -5370,33 +4881,14 @@ "node": ">=8.0.0" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/ansis": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz", + "integrity": "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==", + "dev": true, "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=14" } }, "node_modules/arg": { @@ -5449,6 +4941,21 @@ "dev": true, "license": "MIT" }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5506,14 +5013,14 @@ "license": "MIT" }, "node_modules/beasties": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.3.4.tgz", - "integrity": "sha512-NmzN1zN1cvGccXFyZ73335+ASXwBlVWcUPssiUDIlFdfyatHPRRufjCd5w8oPaQPvVnf9ELklaCGb1gi9FBwIw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.3.5.tgz", + "integrity": "sha512-NaWu+f4YrJxEttJSm16AzMIFtVldCvaJ68b1L098KpqXmxt9xOLtKoLkKxb8ekhOrLqEJAbvT6n6SEvB/sac7A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "css-select": "^5.1.0", - "css-what": "^6.1.0", + "css-select": "^6.0.0", + "css-what": "^7.0.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "htmlparser2": "^10.0.0", @@ -5525,20 +5032,6 @@ "node": ">=14.0.0" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/body-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", @@ -5568,9 +5061,9 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "devOptional": true, "license": "MIT", "dependencies": { @@ -5591,9 +5084,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.25.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz", + "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==", "dev": true, "funding": [ { @@ -5611,10 +5104,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001737", + "electron-to-chromium": "^1.5.211", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -5775,11 +5268,28 @@ "node": ">=18" } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "devOptional": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5793,7 +5303,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "devOptional": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5826,9 +5335,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001699", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz", - "integrity": "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==", + "version": "1.0.30001741", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", + "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", "dev": true, "funding": [ { @@ -5863,53 +5372,38 @@ } }, "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", + "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==", "devOptional": true, "license": "MIT" }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, "node_modules/chownr": { @@ -5970,76 +5464,22 @@ } }, "node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "devOptional": true, "license": "MIT", "dependencies": { "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" + "string-width": "^7.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-truncate/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/cli-truncate/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cli-truncate/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/cli-width": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", @@ -6065,77 +5505,10 @@ "node": ">=20" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/cliui/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "devOptional": true, "license": "MIT", "dependencies": { @@ -6183,13 +5556,13 @@ "license": "MIT" }, "node_modules/commander": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", - "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/common-path-prefix": { @@ -6277,9 +5650,9 @@ } }, "node_modules/core-js": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.42.0.tgz", - "integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==", + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", + "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -6328,16 +5701,25 @@ "node": ">=10" } }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==", "license": "MIT", "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", + "ripemd160": "^2.0.0", "sha.js": "^2.4.0" } }, @@ -6373,9 +5755,9 @@ } }, "node_modules/cronstrue": { - "version": "2.54.0", - "resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-2.54.0.tgz", - "integrity": "sha512-vyp5NklDxA5MjPfQgkn0bA+0vRQe7Q9keX7RPdV6rMgd7LtDvbuKgnT+3T5ZAX16anSP5HmahcRp8mziXsLfaw==", + "version": "2.59.0", + "resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-2.59.0.tgz", + "integrity": "sha512-YKGmAy84hKH+hHIIER07VCAHf9u0Ldelx1uU6EBxsRPDXIA1m5fsKmJfyC3xBhw6cVC/1i83VdbL4PvepTrt8A==", "license": "MIT", "bin": { "cronstrue": "bin/cli.js" @@ -6397,26 +5779,26 @@ } }, "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" }, "funding": { "url": "https://github.com/sponsors/fb55" } }, "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -6492,6 +5874,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-lazy-prop": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", @@ -6634,7 +6033,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "devOptional": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -6660,16 +6058,16 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.96", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.96.tgz", - "integrity": "sha512-8AJUW6dh75Fm/ny8+kZKJzI1pgoE8bKLZlzDU2W1ENd+DXKJrx7I7l9hb8UWR4ojlnb5OlixMt00QWiYJoVw1w==", + "version": "1.5.215", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.215.tgz", + "integrity": "sha512-TIvGp57UpeNetj/wV/xpFNpWGb0b/ROw372lHPx5Aafx02gjTBtWnEEcaSX3W2dLM3OSdGGyHX/cHl01JQsLaQ==", "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz", + "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==", "license": "MIT" }, "node_modules/encodeurl": { @@ -6760,7 +6158,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6770,7 +6167,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6780,7 +6176,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "devOptional": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -6796,9 +6191,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", - "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6809,31 +6204,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.5", - "@esbuild/android-arm": "0.25.5", - "@esbuild/android-arm64": "0.25.5", - "@esbuild/android-x64": "0.25.5", - "@esbuild/darwin-arm64": "0.25.5", - "@esbuild/darwin-x64": "0.25.5", - "@esbuild/freebsd-arm64": "0.25.5", - "@esbuild/freebsd-x64": "0.25.5", - "@esbuild/linux-arm": "0.25.5", - "@esbuild/linux-arm64": "0.25.5", - "@esbuild/linux-ia32": "0.25.5", - "@esbuild/linux-loong64": "0.25.5", - "@esbuild/linux-mips64el": "0.25.5", - "@esbuild/linux-ppc64": "0.25.5", - "@esbuild/linux-riscv64": "0.25.5", - "@esbuild/linux-s390x": "0.25.5", - "@esbuild/linux-x64": "0.25.5", - "@esbuild/netbsd-arm64": "0.25.5", - "@esbuild/netbsd-x64": "0.25.5", - "@esbuild/openbsd-arm64": "0.25.5", - "@esbuild/openbsd-x64": "0.25.5", - "@esbuild/sunos-x64": "0.25.5", - "@esbuild/win32-arm64": "0.25.5", - "@esbuild/win32-ia32": "0.25.5", - "@esbuild/win32-x64": "0.25.5" + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" } }, "node_modules/escalade": { @@ -6915,13 +6311,13 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz", - "integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", "devOptional": true, "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=18.0.0" } }, "node_modules/execa": { @@ -6948,6 +6344,13 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/exponential-backoff": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", @@ -7014,34 +6417,6 @@ "express": ">= 4.11" } }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/external-editor/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7065,6 +6440,33 @@ "node": ">=8.6.0" } }, + "node_modules/fast-glob/node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "optional": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/fast-glob/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/fast-json-patch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", @@ -7079,9 +6481,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "funding": [ { "type": "github", @@ -7095,9 +6497,9 @@ "license": "BSD-3-Clause" }, "node_modules/fastq": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", - "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "license": "ISC", "optional": true, "dependencies": { @@ -7105,11 +6507,14 @@ } }, "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "devOptional": true, "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -7167,17 +6572,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/find-cache-directory/node_modules/pkg-dir": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-8.0.0.tgz", + "integrity": "sha512-4peoBq4Wks0riS0z8741NVv+/8IiTvqnZAr8QGgtdifrtpdXbNw/FxRS1l6NFqm4EMzuS0EDqNNx4XGaz8cuyQ==", + "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", + "find-up-simple": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/find-up-simple": { @@ -7210,9 +6635,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, "funding": [ { @@ -7230,6 +6655,21 @@ } } }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -7247,19 +6687,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "devOptional": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -7304,6 +6731,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -7318,7 +6746,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7353,9 +6780,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.1.tgz", + "integrity": "sha512-R1QfovbPsKmosqTnPoRFiJ7CF9MLRgb53ChvMZm+r4p76/+8yKDy17qLL2PKInORy2RkZZekuK0efYgmzTkXyQ==", "license": "MIT", "engines": { "node": ">=18" @@ -7368,7 +6795,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "devOptional": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -7393,7 +6819,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "devOptional": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -7477,7 +6902,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7503,11 +6927,22 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7516,25 +6951,34 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" + "has-symbols": "^1.0.3" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1" } }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "devOptional": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -7554,24 +6998,27 @@ } }, "node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.0.tgz", + "integrity": "sha512-gEf705MZLrDPkbbhi8PnoO4ZwYgKoNL+ISZ3AjZMht2r3N5tuTwncyDi6Fv2/qDnMmZxgs0yI8WDOyR8q3G+SQ==", "devOptional": true, "license": "ISC", "dependencies": { - "lru-cache": "^10.0.1" + "lru-cache": "^11.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.1.tgz", + "integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==", "devOptional": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": "20 || >=22" + } }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", @@ -7710,6 +7157,39 @@ "node": ">=12" } }, + "node_modules/http-server/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/http-server/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/http-server/node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -7778,92 +7258,37 @@ "url": "https://opencollective.com/husky" } }, - "node_modules/husky/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/husky/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "color-convert": "^2.0.1" }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/husky/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/husky/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/husky/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/husky/node_modules/pkg-dir": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", - "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^5.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/husky/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/husky/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/iconv-lite": { @@ -7900,29 +7325,29 @@ "license": "BSD-3-Clause" }, "node_modules/ignore-walk": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-7.0.0.tgz", - "integrity": "sha512-T4gbf83A4NH95zvhVYZc+qWocBBGlpzUXLPGurJggw/WIOwicfXJChLDP/iBZnN5WqROSu5Bm3hhle4z8a8YGQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", "devOptional": true, "license": "ISC", "dependencies": { - "minimatch": "^9.0.0" + "minimatch": "^10.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/ignore-walk/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "devOptional": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -7966,16 +7391,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -8025,18 +7440,18 @@ } }, "node_modules/inquirer": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.6.1.tgz", - "integrity": "sha512-MGFnzHVS3l3oM3cy+LWkyR7UUtVEn3D5U41CZbEY34szToWoJAvaVtCTz1mxsEzZFk/HXWyCArn0HDgloTXMDw==", + "version": "12.9.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.9.4.tgz", + "integrity": "sha512-5bV3LOgLtMAiJq1QpaUddfRrvaX59wiMYppS7z2jNRSQ64acI0yqx7WMxWhgymenSXOyD657g9tlsTjqGYM8sg==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.11", - "@inquirer/prompts": "^7.5.1", - "@inquirer/type": "^3.0.6", + "@inquirer/core": "^10.2.0", + "@inquirer/prompts": "^7.8.4", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "mute-stream": "^2.0.0", - "run-async": "^3.0.0", + "run-async": "^4.0.5", "rxjs": "^7.8.2" }, "engines": { @@ -8051,16 +7466,64 @@ } } }, - "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "devOptional": true, + "node_modules/inquirer/node_modules/@inquirer/confirm": { + "version": "5.1.16", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.16.tgz", + "integrity": "sha512-j1a5VstaK5KQy8Mu8cHmuQvN1Zc62TbLhjJxwHvKPPKEoowSF6h/0UdOpA9DNdWZ+9Inq73+puRq1df6OJ8Sag==", + "dev": true, "license": "MIT", "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" + "@inquirer/core": "^10.2.0", + "@inquirer/type": "^3.0.8" }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/@inquirer/prompts": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.8.4.tgz", + "integrity": "sha512-MuxVZ1en1g5oGamXV3DWP89GEkdD54alcfhHd7InUW5BifAdKQEK9SLFa/5hlWbvuhMPlobF0WAx7Okq988Jxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.2.2", + "@inquirer/confirm": "^5.1.16", + "@inquirer/editor": "^4.2.18", + "@inquirer/expand": "^4.0.18", + "@inquirer/input": "^4.2.2", + "@inquirer/number": "^3.0.18", + "@inquirer/password": "^4.0.18", + "@inquirer/rawlist": "^4.1.6", + "@inquirer/search": "^3.1.1", + "@inquirer/select": "^4.3.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "devOptional": true, + "license": "MIT", "engines": { "node": ">= 12" } @@ -8082,18 +7545,16 @@ "dev": true, "license": "MIT" }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-core-module": { @@ -8225,6 +7686,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-unicode-supported": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", @@ -8260,6 +7736,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -8338,13 +7820,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "devOptional": true, - "license": "MIT" - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -8404,9 +7879,9 @@ "license": "MIT" }, "node_modules/less": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/less/-/less-4.3.0.tgz", - "integrity": "sha512-X9RyH9fvemArzfdP8Pi3irr7lor2Ok4rOttDXBhlwDg+wKQsXOXgHWduAJE1EsF7JJx0w0bcO6BC6tCKKYnXKA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/less/-/less-4.4.1.tgz", + "integrity": "sha512-X9HKyiXPi0f/ed0XhgUlBeFfxrlDP3xR4M7768Zl+WXLUViuL9AOPPJP4nCV0tgRWvTYvpNmN0SFhZOQzy16PA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -8456,9 +7931,9 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.12.9", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.9.tgz", - "integrity": "sha512-VWwAdNeJgN7jFOD+wN4qx83DTPMVPPAUyx9/TUkBXKLiNkuWWk6anV0439tgdtwaJDrEdqkvdN22iA6J4bUCZg==", + "version": "1.12.15", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.15.tgz", + "integrity": "sha512-TMDCtIhWUDHh91wRC+wFuGlIzKdPzaTUHHVrIZ3vPUEoNaXFLrsIQ1ZpAeZeXApIF6rvDksMTvjrIQlLKaYxqQ==", "license": "MIT", "peer": true }, @@ -8507,6 +7982,22 @@ "url": "https://opencollective.com/lint-staged" } }, + "node_modules/lint-staged/node_modules/ansi-escapes": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^1.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lint-staged/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -8520,6 +8011,49 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/lint-staged/node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", + "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/lint-staged/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -8538,51 +8072,21 @@ } } }, - "node_modules/lint-staged/node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/lint-staged/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "node_modules/lint-staged/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, - "node_modules/lint-staged/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/lint-staged/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } + "license": "MIT" }, - "node_modules/lint-staged/node_modules/yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 14" - } - }, - "node_modules/listr2": { + "node_modules/lint-staged/node_modules/listr2": { "version": "6.6.1", "resolved": "https://registry.npmjs.org/listr2/-/listr2-6.6.1.tgz", "integrity": "sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==", @@ -8608,47 +8112,84 @@ } } }, - "node_modules/listr2/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/listr2/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/listr2/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/listr2/node_modules/eventemitter3": { + "node_modules/lint-staged/node_modules/log-update": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", + "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^5.0.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^5.0.0", + "strip-ansi": "^7.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lint-staged/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, "license": "MIT" }, - "node_modules/listr2/node_modules/string-width": { + "node_modules/lint-staged/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/lint-staged/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", @@ -8666,23 +8207,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/listr2/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/lint-staged/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/listr2/node_modules/wrap-ansi": { + "node_modules/lint-staged/node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", @@ -8700,10 +8238,63 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/lint-staged/node_modules/yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 14" + } + }, + "node_modules/listr2": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.1.tgz", + "integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/lmdb": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.4.1.tgz", - "integrity": "sha512-hoG9RIv42kdGJiieyElgWcKCTaw5S6Jqwyd1gLSVdsJ3+8MVm8e4yLronThiRJI9DazFAAs9xfB9nWeMQ2DWKA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.4.2.tgz", + "integrity": "sha512-nwVGUfTBUwJKXd6lRV8pFNfnrCC1+l49ESJRM19t/tFb/97QfJEixe5DYRvug5JO7DSFKoKaVy7oGMt5rVqZvg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -8719,25 +8310,29 @@ "download-lmdb-prebuilds": "bin/download-prebuilds.js" }, "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "3.4.1", - "@lmdb/lmdb-darwin-x64": "3.4.1", - "@lmdb/lmdb-linux-arm": "3.4.1", - "@lmdb/lmdb-linux-arm64": "3.4.1", - "@lmdb/lmdb-linux-x64": "3.4.1", - "@lmdb/lmdb-win32-arm64": "3.4.1", - "@lmdb/lmdb-win32-x64": "3.4.1" + "@lmdb/lmdb-darwin-arm64": "3.4.2", + "@lmdb/lmdb-darwin-x64": "3.4.2", + "@lmdb/lmdb-linux-arm": "3.4.2", + "@lmdb/lmdb-linux-arm64": "3.4.2", + "@lmdb/lmdb-linux-x64": "3.4.2", + "@lmdb/lmdb-win32-arm64": "3.4.2", + "@lmdb/lmdb-win32-x64": "3.4.2" } }, "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash": { @@ -8762,18 +8357,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/log-symbols/node_modules/is-unicode-supported": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", @@ -8787,202 +8370,96 @@ } }, "node_modules/log-update": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", - "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", - "dev": true, + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "devOptional": true, "license": "MIT", "dependencies": { - "ansi-escapes": "^5.0.0", - "cli-cursor": "^4.0.0", - "slice-ansi": "^5.0.0", - "strip-ansi": "^7.0.1", - "wrap-ansi": "^8.0.1" + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/log-update/node_modules/ansi-escapes": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", - "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^1.0.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-update/node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-update/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/log-update/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/strip-ansi": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.0.tgz", + "integrity": "sha512-YdhtCd19sKRKfAAUsrcC1wzm4JuzJoiX4pOJqIoW2qmKj5WzG/dL8uUJ0361zaXtHqK7gEhOwtAtz7t3Yq3X5g==", + "devOptional": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "environment": "^1.0.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, "node_modules/log-update/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "devOptional": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/long": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.4.tgz", - "integrity": "sha512-qtzLbJE8hq7VabR3mISmVGtoXP8KGc2Z/AT8OuqlYD7JTR3oqrgwdjnk07wpj1twXxYmgDXgoKVWUG/fReSzHg==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, "node_modules/lru-cache": { @@ -9085,23 +8562,11 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "license": "MIT", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, "node_modules/media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -9143,13 +8608,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "braces": "^3.0.3", + "braces": "^3.0.2", "picomatch": "^2.3.1" }, "engines": { @@ -9160,8 +8625,8 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=8.6" }, @@ -9170,9 +8635,9 @@ } }, "node_modules/mime": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.6.tgz", - "integrity": "sha512-4rGt7rvQHBbaSOF9POGkk1ocRP16Md1x36Xma8sz8h8/vfCUI2OtEIeCqe4Ofes853x4xDoPiFLIT47J5fI/7A==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.7.tgz", + "integrity": "sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ==", "funding": [ "https://github.com/sponsors/broofa" ], @@ -9236,8 +8701,8 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -9448,9 +8913,9 @@ "license": "MIT" }, "node_modules/msgpackr": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.4.tgz", - "integrity": "sha512-uaff7RG9VIC4jacFW9xzL3jc0iM32DNHe4jYVycBcjUePT/Klnfj7pqtWJt9khvDFizmjN2TlYniYmSS2LIaZg==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", + "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", "dev": true, "license": "MIT", "optional": true, @@ -9502,9 +8967,9 @@ } }, "node_modules/multimatch/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "optional": true, "dependencies": { @@ -9610,9 +9075,9 @@ } }, "node_modules/ng-packagr": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-20.1.0.tgz", - "integrity": "sha512-objHk39HWnSSv54KD0Ct4A02rug6HiqbmXo1KJW39npzuVc37QWfiZy94afltH1zIx+mQqollmGaCmwibmagvQ==", + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-20.2.0.tgz", + "integrity": "sha512-U8kv9O5hD9ojKlSke44A2NIH5sH0EmQXtQTtMLLrpn7y4LUeCQgTi5t8KsDXoMyCmBKMhDJzioa3R22pOy5vFg==", "dev": true, "license": "MIT", "dependencies": { @@ -9648,10 +9113,10 @@ "rollup": "^4.24.0" }, "peerDependencies": { - "@angular/compiler-cli": "^20.0.0 || ^20.1.0-next.0 || ^20.2.0-next.0", + "@angular/compiler-cli": "^20.0.0 || ^20.2.0-rc", "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", "tslib": "^2.3.0", - "typescript": ">=5.8 <5.9" + "typescript": ">=5.8 <6.0" }, "peerDependenciesMeta": { "tailwindcss": { @@ -9659,22 +9124,6 @@ } } }, - "node_modules/ng-packagr/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/ng-packagr/node_modules/commander": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", @@ -9685,20 +9134,6 @@ "node": ">=20" } }, - "node_modules/ng-packagr/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/ng-qrcode": { "version": "20.0.0", "resolved": "https://registry.npmjs.org/ng-qrcode/-/ng-qrcode-20.0.0.tgz", @@ -9731,9 +9166,9 @@ } }, "node_modules/node-gyp": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.2.0.tgz", - "integrity": "sha512-T0S1zqskVUSxcsSTkAsLc7xCycrRYmtDHadDinzocrThjyQCn5kMlEBSj6H4qDbgsIOSLmmlRIeb0lZXj+UArA==", + "version": "11.4.2", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.4.2.tgz", + "integrity": "sha512-3gD+6zsrLQH7DyYOUIutaauuXrcyxeTPyQuZQCQoNPZMHMMS5m4y0xclNpvYzoK3VNzuyxT6eF4mkIL4WSZ1eQ==", "devOptional": true, "license": "MIT", "dependencies": { @@ -9879,6 +9314,19 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/node-html-parser/node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/node-html-parser/node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -9956,9 +9404,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.20.tgz", + "integrity": "sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA==", "dev": true, "license": "MIT" }, @@ -9978,17 +9426,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/npm-bundled": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", @@ -10003,9 +9440,9 @@ } }, "node_modules/npm-install-checks": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.1.tgz", - "integrity": "sha512-u6DCwbow5ynAX5BdiHQ9qvexme4U3qHW3MWe5NqH+NeBm0LbiH6zvGjNNew1fY+AZZUtVHbOPF3j7mJxbUzpXg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.2.tgz", + "integrity": "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ==", "devOptional": true, "license": "BSD-2-Clause", "dependencies": { @@ -10026,29 +9463,29 @@ } }, "node_modules/npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.0.tgz", + "integrity": "sha512-+t2etZAGcB7TbbLHfDwooV9ppB2LhhcT6A+L9cahsf9mEUAoQ6CktLEVvEnpD0N5CkX7zJqnPGaFtoQDy9EkHQ==", "devOptional": true, "license": "ISC", "dependencies": { - "hosted-git-info": "^8.0.0", + "hosted-git-info": "^9.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-packlist": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.0.tgz", - "integrity": "sha512-rht9U6nS8WOBDc53eipZNPo5qkAV4X2rhKE2Oj1DYUQ3DieXfj0mKkVmjnf3iuNdtMd8WfLdi2L6ASkD/8a+Kg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.1.tgz", + "integrity": "sha512-vaC03b2PqJA6QqmwHi1jNU8fAPXEnnyv4j/W4PVfgm24C4/zZGSVut3z0YUeN0WIFCo1oGOL02+6LbvFK7JL4Q==", "devOptional": true, "license": "ISC", "dependencies": { - "ignore-walk": "^7.0.0" + "ignore-walk": "^8.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -10070,6 +9507,42 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/npm-pick-manifest/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-pick-manifest/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/npm-pick-manifest/node_modules/npm-package-arg": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", + "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/npm-registry-fetch": { "version": "18.0.2", "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-18.0.2.tgz", @@ -10090,6 +9563,42 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/npm-registry-fetch/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/npm-registry-fetch/node_modules/npm-package-arg": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", + "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/npm-run-path": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", @@ -10204,16 +9713,16 @@ } }, "node_modules/open": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", - "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", "dev": true, "license": "MIT", "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", - "is-wsl": "^3.1.0" + "wsl-utils": "^0.1.0" }, "engines": { "node": ">=18" @@ -10265,68 +9774,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "license": "MIT" - }, - "node_modules/ora/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/ordered-binary": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.0.tgz", @@ -10335,41 +9782,36 @@ "license": "MIT", "optional": true }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-map": { @@ -10433,6 +9875,42 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/pacote/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/pacote/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/pacote/node_modules/npm-package-arg": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", + "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/pako": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", @@ -10489,9 +9967,9 @@ } }, "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", "license": "MIT", "dependencies": { "entities": "^6.0.0" @@ -10501,14 +9979,14 @@ } }, "node_modules/parse5-html-rewriting-stream": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.1.0.tgz", - "integrity": "sha512-2ifK6Jb+ONoqOy5f+cYHsqvx1obHQdvIk13Jmt/5ezxP0U9p+fqd+R6O73KblGswyuzBYfetmsfK9ThMgnuPPg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-8.0.0.tgz", + "integrity": "sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==", "license": "MIT", "dependencies": { "entities": "^6.0.0", - "parse5": "^7.0.0", - "parse5-sax-parser": "^7.0.0" + "parse5": "^8.0.0", + "parse5-sax-parser": "^8.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" @@ -10527,21 +10005,21 @@ } }, "node_modules/parse5-sax-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", - "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-8.0.0.tgz", + "integrity": "sha512-/dQ8UzHZwnrzs3EvDj6IkKrD/jIZyTlB+8XrHJvcjNgRdmWruNdN9i9RK/JtxakmlUdPwKubKPTCqvbTgzGhrw==", "license": "MIT", "dependencies": { - "parse5": "^7.0.0" + "parse5": "^8.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/parse5/node_modules/entities": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", - "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -10632,13 +10110,14 @@ "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "devOptional": true, "license": "MIT", - "engines": { - "node": ">=16" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/path-type": { @@ -10652,16 +10131,17 @@ } }, "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.3.tgz", + "integrity": "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==", "license": "MIT", "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "create-hash": "~1.1.3", + "create-hmac": "^1.1.7", + "ripemd160": "=2.0.1", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.11", + "to-buffer": "^1.2.0" }, "engines": { "node": ">=0.12" @@ -10675,9 +10155,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", "engines": { "node": ">=12" @@ -10711,16 +10191,16 @@ } }, "node_modules/piscina": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.1.2.tgz", - "integrity": "sha512-9cE/BTA/xhDiyNUEj6EKWLEQC17fh/24ydYzQwcA7QdYh75K6kzL2GHvxDF5i9rFGtUaaKk7/u4xp07qiKXccQ==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.1.3.tgz", + "integrity": "sha512-0u3N7H4+hbr40KjuVn2uNhOcthu/9usKhnw5vT3J7ply79v3D3M8naI00el9Klcy16x557VsEkkUQaHCWFXC/g==", "dev": true, "license": "MIT", "engines": { "node": ">=20.x" }, "optionalDependencies": { - "@napi-rs/nice": "^1.0.1" + "@napi-rs/nice": "^1.0.4" } }, "node_modules/pkce-challenge": { @@ -10734,19 +10214,16 @@ } }, "node_modules/pkg-dir": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-8.0.0.tgz", - "integrity": "sha512-4peoBq4Wks0riS0z8741NVv+/8IiTvqnZAr8QGgtdifrtpdXbNw/FxRS1l6NFqm4EMzuS0EDqNNx4XGaz8cuyQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", "dev": true, "license": "MIT", "dependencies": { - "find-up-simple": "^1.0.0" + "find-up": "^5.0.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10" } }, "node_modules/please-upgrade-node": { @@ -10782,6 +10259,15 @@ "node": ">= 10.12" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -10819,9 +10305,9 @@ "license": "MIT" }, "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", "bin": { @@ -10916,6 +10402,15 @@ "node": ">=10.13.0" } }, + "node_modules/qrcode/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/qrcode/node_modules/cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -10927,6 +10422,99 @@ "wrap-ansi": "^6.2.0" } }, + "node_modules/qrcode/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/qrcode/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/qrcode/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/qrcode/node_modules/y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", @@ -11016,61 +10604,50 @@ } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", "devOptional": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.6.3", + "iconv-lite": "0.7.0", "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "devOptional": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">= 6" + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.6" + "node": ">= 14.18.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/reflect-metadata": { @@ -11132,6 +10709,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -11163,18 +10750,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -11186,9 +10761,9 @@ } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "license": "MIT", "optional": true, "engines": { @@ -11224,15 +10799,15 @@ } }, "node_modules/rimraf/node_modules/glob": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz", - "integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", "dev": true, "license": "ISC", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" @@ -11248,9 +10823,9 @@ } }, "node_modules/rimraf/node_modules/jackspeak": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", - "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -11264,15 +10839,31 @@ } }, "node_modules/rimraf/node_modules/lru-cache": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", - "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.1.tgz", + "integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==", "dev": true, "license": "ISC", "engines": { "node": "20 || >=22" } }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rimraf/node_modules/path-scurry": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", @@ -11291,19 +10882,51 @@ } }, "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==", "license": "MIT", "dependencies": { - "hash-base": "^3.0.0", + "hash-base": "^2.0.0", "inherits": "^2.0.1" } }, + "node_modules/rolldown": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.32.tgz", + "integrity": "sha512-vxI2sPN07MMaoYKlFrVva5qZ1Y7DAZkgp7MQwTnyHt4FUMz9Sh+YeCzNFV9JYHI6ZNwoGWLCfCViE3XVsRC1cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/runtime": "=0.81.0", + "@oxc-project/types": "=0.81.0", + "@rolldown/pluginutils": "1.0.0-beta.32", + "ansis": "^4.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-beta.32", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.32", + "@rolldown/binding-darwin-x64": "1.0.0-beta.32", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.32", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.32", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.32", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.32", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.32", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.32", + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.32", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.32", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.32", + "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.32", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.32" + } + }, "node_modules/rollup": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", - "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.1.tgz", + "integrity": "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==", "dev": true, "license": "MIT", "dependencies": { @@ -11317,33 +10940,34 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.44.1", - "@rollup/rollup-android-arm64": "4.44.1", - "@rollup/rollup-darwin-arm64": "4.44.1", - "@rollup/rollup-darwin-x64": "4.44.1", - "@rollup/rollup-freebsd-arm64": "4.44.1", - "@rollup/rollup-freebsd-x64": "4.44.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.44.1", - "@rollup/rollup-linux-arm-musleabihf": "4.44.1", - "@rollup/rollup-linux-arm64-gnu": "4.44.1", - "@rollup/rollup-linux-arm64-musl": "4.44.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.44.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1", - "@rollup/rollup-linux-riscv64-gnu": "4.44.1", - "@rollup/rollup-linux-riscv64-musl": "4.44.1", - "@rollup/rollup-linux-s390x-gnu": "4.44.1", - "@rollup/rollup-linux-x64-gnu": "4.44.1", - "@rollup/rollup-linux-x64-musl": "4.44.1", - "@rollup/rollup-win32-arm64-msvc": "4.44.1", - "@rollup/rollup-win32-ia32-msvc": "4.44.1", - "@rollup/rollup-win32-x64-msvc": "4.44.1", + "@rollup/rollup-android-arm-eabi": "4.50.1", + "@rollup/rollup-android-arm64": "4.50.1", + "@rollup/rollup-darwin-arm64": "4.50.1", + "@rollup/rollup-darwin-x64": "4.50.1", + "@rollup/rollup-freebsd-arm64": "4.50.1", + "@rollup/rollup-freebsd-x64": "4.50.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.50.1", + "@rollup/rollup-linux-arm-musleabihf": "4.50.1", + "@rollup/rollup-linux-arm64-gnu": "4.50.1", + "@rollup/rollup-linux-arm64-musl": "4.50.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.50.1", + "@rollup/rollup-linux-ppc64-gnu": "4.50.1", + "@rollup/rollup-linux-riscv64-gnu": "4.50.1", + "@rollup/rollup-linux-riscv64-musl": "4.50.1", + "@rollup/rollup-linux-s390x-gnu": "4.50.1", + "@rollup/rollup-linux-x64-gnu": "4.50.1", + "@rollup/rollup-linux-x64-musl": "4.50.1", + "@rollup/rollup-openharmony-arm64": "4.50.1", + "@rollup/rollup-win32-arm64-msvc": "4.50.1", + "@rollup/rollup-win32-ia32-msvc": "4.50.1", + "@rollup/rollup-win32-x64-msvc": "4.50.1", "fsevents": "~2.3.2" } }, "node_modules/rollup-plugin-dts": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.2.1.tgz", - "integrity": "sha512-sR3CxYUl7i2CHa0O7bA45mCrgADyAQ0tVtGSqi3yvH28M+eg1+g5d7kQ9hLvEz5dorK3XVsH5L2jwHLQf72DzA==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.2.3.tgz", + "integrity": "sha512-UgnEsfciXSPpASuOelix7m4DrmyQgiaWBnvI0TM4GxuDh5FkqW8E5hu57bCxXB90VvR1WNfLV80yEDN18UogSA==", "dev": true, "license": "LGPL-3.0-only", "dependencies": { @@ -11356,7 +10980,7 @@ "url": "https://github.com/sponsors/Swatinem" }, "optionalDependencies": { - "@babel/code-frame": "^7.26.2" + "@babel/code-frame": "^7.27.1" }, "peerDependencies": { "rollup": "^3.29.4 || ^4", @@ -11388,9 +11012,9 @@ } }, "node_modules/run-applescript": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", - "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", "dev": true, "license": "MIT", "engines": { @@ -11401,9 +11025,9 @@ } }, "node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.6.tgz", + "integrity": "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==", "dev": true, "license": "MIT", "engines": { @@ -11471,9 +11095,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.89.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz", - "integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", + "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -11491,36 +11115,6 @@ "@parcel/watcher": "^2.4.1" } }, - "node_modules/sass/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/sass/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", @@ -11614,6 +11208,23 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "license": "ISC" }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -11622,16 +11233,23 @@ "license": "ISC" }, "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", "license": "(MIT AND BSD-3-Clause)", "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" }, "bin": { "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/shebang-command": { @@ -11734,11 +11352,16 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/sigstore": { "version": "3.1.0", @@ -11758,6 +11381,16 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -11775,19 +11408,6 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -11800,13 +11420,13 @@ } }, "node_modules/socks": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", - "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "devOptional": true, "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -11830,12 +11450,12 @@ } }, "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "license": "BSD-3-Clause", "engines": { - "node": ">= 8" + "node": ">= 12" } }, "node_modules/source-map-js": { @@ -11899,17 +11519,17 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", "devOptional": true, "license": "CC0-1.0" }, "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "devOptional": true, + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/ssri": { @@ -11947,15 +11567,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -11967,17 +11578,20 @@ } }, "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/string-width-cjs": { @@ -11996,6 +11610,23 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "devOptional": true, + "license": "MIT" + }, "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -12006,19 +11637,11 @@ "node": ">=8" } }, - "node_modules/string-width/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { + "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -12027,6 +11650,21 @@ "node": ">=8" } }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", @@ -12041,6 +11679,16 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -12185,17 +11833,36 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "devOptional": true, + "node_modules/tldts": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.13.tgz", + "integrity": "sha512-z/SgnxiICGb7Gli0z7ci9BZdjy1tQORUbdmzEUA7NbIJKWhdONn78Ji8gV0PAGfHPyEd+I+W2rMzhLjWkv2Olg==", "license": "MIT", "dependencies": { - "os-tmpdir": "~1.0.2" + "tldts-core": "^7.0.13" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.13.tgz", + "integrity": "sha512-Td0LeWLgXJGsikI4mO82fRexgPCEyTcwWiXJERF/GBHX3Dm+HQq/wx4HnYowCbiwQ8d+ENLZc+ktbZw8H+0oEA==", + "license": "MIT" + }, + "node_modules/to-buffer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" }, "engines": { - "node": ">=0.6.0" + "node": ">= 0.4" } }, "node_modules/to-regex-range": { @@ -12222,9 +11889,9 @@ } }, "node_modules/ts-matches": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.3.2.tgz", - "integrity": "sha512-UhSgJymF8cLd4y0vV29qlKVCkQpUtekAaujXbQVc729FezS8HwqzepqvtjzQ3HboatIqN/Idor85O2RMwT7lIQ==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.5.0.tgz", + "integrity": "sha512-MhuobYhHYn6MlOTPAF/qk3tsRRioPac5ofYn68tc3rAJaGjsw1MsX1MOSep52DkvNJPgNV0F73zfgcQfYTVeyQ==", "license": "MIT" }, "node_modules/ts-morph": { @@ -12344,9 +12011,9 @@ } }, "node_modules/tslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -12475,13 +12142,6 @@ "semver": "bin/semver" } }, - "node_modules/tslint/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/tslint/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -12565,10 +12225,24 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", "bin": { @@ -12635,9 +12309,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", - "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -12682,12 +12356,6 @@ "dev": true, "license": "MIT" }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -12716,9 +12384,9 @@ } }, "node_modules/validate-npm-package-name": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.1.tgz", - "integrity": "sha512-OaI//3H0J7ZkR1OqlhGA8cA+Cbk/2xFOQpJOt5+s27/ta9eZwpeervh4Mxh4w0im/kdgktowaqVNR7QOrUd7Yg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", + "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", "devOptional": true, "license": "ISC", "engines": { @@ -12735,6 +12403,81 @@ "node": ">= 0.8" } }, + "node_modules/vite": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", + "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/watchpack": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", @@ -12802,6 +12545,27 @@ "node": ">=4" } }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -12835,6 +12599,142 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -12842,6 +12742,22 @@ "devOptional": true, "license": "ISC" }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -12860,13 +12776,18 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 6" + "node": ">= 14.6" } }, "node_modules/yargs": { @@ -12897,60 +12818,6 @@ "node": "^20.19.0 || ^22.12.0 || >=23" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -12975,9 +12842,9 @@ } }, "node_modules/yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", "devOptional": true, "license": "MIT", "engines": { @@ -12988,9 +12855,9 @@ } }, "node_modules/zod": { - "version": "3.25.75", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.75.tgz", - "integrity": "sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg==", + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "devOptional": true, "license": "MIT", "funding": { @@ -13008,9 +12875,9 @@ } }, "node_modules/zone.js": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.0.tgz", - "integrity": "sha512-9oxn0IIjbCZkJ67L+LkhYWRyAy7axphb3VgE2MBDlOqnmHMPWGYMxJxBYFueFq/JGY2GMwS0rU+UCLunEmy5UA==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz", + "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==", "license": "MIT" } } diff --git a/web/package.json b/web/package.json index 86ee962ab..896ea8f6b 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "startos-ui", - "version": "0.4.0-alpha.9", + "version": "0.4.0-alpha.10", "author": "Start9 Labs, Inc", "homepage": "https://start9.com/", "license": "MIT", @@ -46,20 +46,19 @@ "@noble/hashes": "^1.4.0", "@start9labs/argon2": "^0.3.0", "@start9labs/start-sdk": "file:../sdk/baseDist", - "@taiga-ui/addon-charts": "4.44.0", - "@taiga-ui/addon-commerce": "4.44.0", - "@taiga-ui/addon-mobile": "4.44.0", - "@taiga-ui/addon-table": "4.44.0", - "@taiga-ui/cdk": "4.44.0", - "@taiga-ui/core": "4.44.0", - "@taiga-ui/event-plugins": "4.6.0", - "@taiga-ui/experimental": "4.44.0", - "@taiga-ui/icons": "4.44.0", - "@taiga-ui/kit": "4.44.0", - "@taiga-ui/layout": "4.44.0", - "@taiga-ui/legacy": "4.44.0", - "@taiga-ui/polymorpheus": "4.9.0", + "@taiga-ui/addon-charts": "4.52.0", + "@taiga-ui/addon-commerce": "4.52.0", + "@taiga-ui/addon-mobile": "4.52.0", + "@taiga-ui/addon-table": "4.52.0", + "@taiga-ui/cdk": "4.52.0", + "@taiga-ui/core": "4.52.0", "@taiga-ui/dompurify": "4.1.11", + "@taiga-ui/event-plugins": "4.7.0", + "@taiga-ui/experimental": "4.52.0", + "@taiga-ui/icons": "4.52.0", + "@taiga-ui/kit": "4.52.0", + "@taiga-ui/layout": "4.52.0", + "@taiga-ui/polymorpheus": "4.9.0", "ansi-to-html": "^0.7.2", "base64-js": "^1.5.1", "buffer": "^6.0.3", @@ -68,8 +67,8 @@ "core-js": "^3.42.0", "cron": "^2.2.0", "cronstrue": "^2.21.0", - "dompurify": "^3.1.7", "deep-equality-data-structures": "1.5.1", + "dompurify": "^3.1.7", "fast-json-patch": "^3.1.1", "fuse.js": "^6.4.6", "jose": "^4.9.0", @@ -83,17 +82,18 @@ "patch-db-client": "file:../patch-db/client", "pbkdf2": "^3.1.2", "rxjs": "^7.8.2", + "tldts": "^7.0.11", "ts-matches": "^6.3.2", "tslib": "^2.8.1", "uuid": "^8.3.2", "zone.js": "^0.15.0" }, "devDependencies": { + "@angular-experts/hawkeye": "^1.7.2", "@angular/build": "^20.1.0", "@angular/cli": "^20.1.0", "@angular/compiler-cli": "^20.1.0", "@angular/language-service": "^20.1.0", - "@angular-experts/hawkeye": "^1.7.2", "@types/dompurify": "3.0.5", "@types/estree": "^0.0.51", "@types/js-yaml": "^4.0.5", diff --git a/web/projects/install-wizard/src/app/app.component.html b/web/projects/install-wizard/src/app/app.component.html index 7f0e0761f..3b06469f9 100644 --- a/web/projects/install-wizard/src/app/app.component.html +++ b/web/projects/install-wizard/src/app/app.component.html @@ -16,7 +16,10 @@ Back } -

{{ selected ? 'Install Type' : 'Select Disk' }}

+

{{ selected ? 'Install Type' : 'StartOS Install' }}

+ @if (!selected) { +

Select Disk

+ }
{{ error }}
diff --git a/web/projects/install-wizard/src/app/app.component.scss b/web/projects/install-wizard/src/app/app.component.scss index a62a0f863..67be9a2db 100644 --- a/web/projects/install-wizard/src/app/app.component.scss +++ b/web/projects/install-wizard/src/app/app.component.scss @@ -34,6 +34,9 @@ main { text-align: center; padding-top: 0.25rem; margin-bottom: -2rem; + h2 { + margin-top: 0; + } } .back { diff --git a/web/projects/install-wizard/src/app/app.component.ts b/web/projects/install-wizard/src/app/app.component.ts index 983e7585a..62c2b030d 100644 --- a/web/projects/install-wizard/src/app/app.component.ts +++ b/web/projects/install-wizard/src/app/app.component.ts @@ -50,10 +50,7 @@ export class AppComponent { private async reboot() { this.dialogs - .open( - 'Remove the USB stick and reboot your device to begin using your new Start9 server', - SUCCESS, - ) + .open('1. Remove the USB stick
2. Click "Reboot" below', SUCCESS) .subscribe({ complete: async () => { const loader = this.loader.open().subscribe() @@ -62,7 +59,7 @@ export class AppComponent { await this.api.reboot() this.dialogs .open( - 'Please wait for StartOS to restart, then refresh this page', + 'Please wait 1-2 minutes, then refresh this page to access the StartOS setup wizard.', { label: 'Rebooting', size: 's', diff --git a/web/projects/install-wizard/src/app/app.module.ts b/web/projects/install-wizard/src/app/app.module.ts index c3bbb819d..8af5922e4 100644 --- a/web/projects/install-wizard/src/app/app.module.ts +++ b/web/projects/install-wizard/src/app/app.module.ts @@ -1,4 +1,8 @@ -import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' +import { + provideHttpClient, + withFetch, + withInterceptorsFromDi, +} from '@angular/common/http' import { NgModule } from '@angular/core' import { BrowserAnimationsModule } from '@angular/platform-browser/animations' import { @@ -50,7 +54,7 @@ const { provide: RELATIVE_URL, useValue: `/${api.url}/${api.version}`, }, - provideHttpClient(withInterceptorsFromDi()), + provideHttpClient(withInterceptorsFromDi(), withFetch()), ], bootstrap: [AppComponent], }) diff --git a/web/projects/install-wizard/src/app/app.utils.ts b/web/projects/install-wizard/src/app/app.utils.ts index 9ed8e5fce..a8e66e816 100644 --- a/web/projects/install-wizard/src/app/app.utils.ts +++ b/web/projects/install-wizard/src/app/app.utils.ts @@ -3,7 +3,7 @@ import { TuiDialogOptions } from '@taiga-ui/core' import { TuiConfirmData } from '@taiga-ui/kit' export const SUCCESS: Partial> = { - label: 'Install Success', + label: 'Install Success!', closeable: false, size: 's', data: { button: 'Reboot' }, diff --git a/web/projects/marketplace/src/components/menu/menu.component.html b/web/projects/marketplace/src/components/menu/menu.component.html index 49a48a345..f934c55c6 100644 --- a/web/projects/marketplace/src/components/menu/menu.component.html +++ b/web/projects/marketplace/src/components/menu/menu.component.html @@ -7,7 +7,7 @@ [url]="registry?.url || ''" />

- {{ registry?.info?.name || 'Unnamed Registry' }} + {{ registry?.info?.name || 'Unnamed registry' }}

@@ -62,12 +62,8 @@
@@ -90,12 +86,8 @@ diff --git a/web/projects/marketplace/src/components/menu/menu.component.module.ts b/web/projects/marketplace/src/components/menu/menu.component.module.ts index b8e54ecbd..11b74ffb8 100644 --- a/web/projects/marketplace/src/components/menu/menu.component.module.ts +++ b/web/projects/marketplace/src/components/menu/menu.component.module.ts @@ -1,6 +1,10 @@ import { CommonModule } from '@angular/common' import { NgModule } from '@angular/core' -import { SharedPipesModule } from '@start9labs/shared' +import { + DocsLinkDirective, + i18nPipe, + SharedPipesModule, +} from '@start9labs/shared' import { TuiLet } from '@taiga-ui/cdk' import { TuiAppearance, @@ -31,6 +35,8 @@ import { MenuComponent } from './menu.component' TuiSkeleton, TuiDrawer, TuiPopup, + i18nPipe, + DocsLinkDirective, ], declarations: [MenuComponent], exports: [MenuComponent], diff --git a/web/projects/marketplace/src/pages/show/about.component.ts b/web/projects/marketplace/src/pages/show/about.component.ts index ecd83b62b..17f432e33 100644 --- a/web/projects/marketplace/src/pages/show/about.component.ts +++ b/web/projects/marketplace/src/pages/show/about.component.ts @@ -6,7 +6,7 @@ import { output, } from '@angular/core' import { MarketplacePkgBase } from '../../types' -import { CopyService } from '@start9labs/shared' +import { CopyService, i18nPipe } from '@start9labs/shared' import { DatePipe } from '@angular/common' import { MarketplaceItemComponent } from './item.component' @@ -36,7 +36,7 @@ import { MarketplaceItemComponent } from './item.component' @@ -44,15 +44,15 @@ import { MarketplaceItemComponent } from './item.component' } @else {
} @@ -128,7 +128,7 @@ import { MarketplaceItemComponent } from './item.component' } `, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [MarketplaceItemComponent, DatePipe], + imports: [MarketplaceItemComponent, DatePipe, i18nPipe], }) export class MarketplaceAboutComponent { readonly copyService = inject(CopyService) diff --git a/web/projects/marketplace/src/pages/show/dependencies/dependencies.component.ts b/web/projects/marketplace/src/pages/show/dependencies/dependencies.component.ts index 20e214fbb..2557cc1c5 100644 --- a/web/projects/marketplace/src/pages/show/dependencies/dependencies.component.ts +++ b/web/projects/marketplace/src/pages/show/dependencies/dependencies.component.ts @@ -8,13 +8,14 @@ import { } from '@angular/core' import { MarketplacePkgBase } from '../../../types' import { MarketplaceDepItemComponent } from './dependency-item.component' +import { i18nPipe } from '@start9labs/shared' @Component({ selector: 'marketplace-dependencies', template: `
-

Dependencies

+

{{ 'Dependencies' | i18n }}

@for (dep of pkg.dependencyMetadata | keyvalue; track $index) {

@if (dep.value.optional) { - (optional) + ({{ 'Optional' | i18n }}) } @else { - (required) + ({{ 'Required' | i18n }}) }

@@ -49,10 +49,11 @@ import { MarketplacePkgBase } from '../../../types' filter: drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1)); - &:hover { - background-color: rgb(63 63 70 / 0.7); - cursor: pointer; - } + // @TODO re-engage when button can link to root with search QP + // &:hover { + // background-color: rgb(63 63 70 / 0.7); + // cursor: pointer; + // } } .title { @@ -88,7 +89,7 @@ import { MarketplacePkgBase } from '../../../types' } `, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [RouterModule, TuiAvatar, ExverPipesModule, TuiLineClamp], + imports: [RouterModule, TuiAvatar, ExverPipesModule, TuiLineClamp, i18nPipe], }) export class MarketplaceDepItemComponent { @Input({ required: true }) diff --git a/web/projects/marketplace/src/pages/show/flavors.component.ts b/web/projects/marketplace/src/pages/show/flavors.component.ts index d3d33486f..c5c5f3004 100644 --- a/web/projects/marketplace/src/pages/show/flavors.component.ts +++ b/web/projects/marketplace/src/pages/show/flavors.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { RouterLink } from '@angular/router' -import { SharedPipesModule } from '@start9labs/shared' +import { i18nPipe, SharedPipesModule } from '@start9labs/shared' import { TuiTitle } from '@taiga-ui/core' import { TuiAvatar } from '@taiga-ui/kit' import { TuiCell } from '@taiga-ui/layout' @@ -11,7 +11,9 @@ import { MarketplacePkg } from '../../types' template: `
-

Alternative Implementations

+

+ {{ 'Alternative Implementations' | i18n }} +

@for (pkg of pkgs; track $index) { - {{ label }} + {{ label || '' }} {{ data }} @@ -38,7 +39,7 @@ import { TuiFade } from '@taiga-ui/kit' }) export class MarketplaceItemComponent { @Input({ required: true }) - label!: string + label!: i18nKey | null @Input({ required: true }) icon!: string diff --git a/web/projects/marketplace/src/pages/show/link.component.ts b/web/projects/marketplace/src/pages/show/link.component.ts index d09eee11c..f0da8de19 100644 --- a/web/projects/marketplace/src/pages/show/link.component.ts +++ b/web/projects/marketplace/src/pages/show/link.component.ts @@ -1,5 +1,6 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { MarketplaceItemComponent } from './item.component' +import { i18nKey } from '@start9labs/shared' @Component({ selector: 'marketplace-link', @@ -13,7 +14,7 @@ import { MarketplaceItemComponent } from './item.component' }) export class MarketplaceLinkComponent { @Input({ required: true }) - label!: string + label!: i18nKey @Input({ required: true }) icon!: string diff --git a/web/projects/marketplace/src/pages/show/links.component.ts b/web/projects/marketplace/src/pages/show/links.component.ts index be5c90dd4..d551e897b 100644 --- a/web/projects/marketplace/src/pages/show/links.component.ts +++ b/web/projects/marketplace/src/pages/show/links.component.ts @@ -5,7 +5,7 @@ import { input, } from '@angular/core' import { ActivatedRoute } from '@angular/router' -import { CopyService } from '@start9labs/shared' +import { CopyService, i18nPipe } from '@start9labs/shared' import { MarketplacePkgBase } from '../../types' import { MarketplaceLinkComponent } from './link.component' @@ -18,13 +18,13 @@ import { MarketplaceLinkComponent } from './link.component'
@@ -34,7 +34,7 @@ import { MarketplaceLinkComponent } from './link.component'
-

Links

+

{{ 'Links' | i18n }}

-

Versions

+

{{ 'Versions' | i18n }}

{ const origin = inject(WA_LOCATION).origin const module_or_path = new URL('/assets/argon2_bg.wasm', origin) diff --git a/web/projects/setup-wizard/src/app/components/cifs.component.ts b/web/projects/setup-wizard/src/app/components/cifs.component.ts index b5b01bbba..e59d6052a 100644 --- a/web/projects/setup-wizard/src/app/components/cifs.component.ts +++ b/web/projects/setup-wizard/src/app/components/cifs.component.ts @@ -14,9 +14,13 @@ import { TuiDialogContext, TuiDialogService, TuiError, + TuiTextfield, } from '@taiga-ui/core' -import { TUI_VALIDATION_ERRORS, TuiFieldErrorPipe } from '@taiga-ui/kit' -import { TuiInputModule, TuiInputPasswordModule } from '@taiga-ui/legacy' +import { + TUI_VALIDATION_ERRORS, + TuiFieldErrorPipe, + TuiPassword, +} from '@taiga-ui/kit' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { SERVERS, ServersResponse } from 'src/app/components/servers.component' import { ApiService } from 'src/app/services/api.service' @@ -30,39 +34,47 @@ export interface CifsResponse { @Component({ template: `
- - Hostname * + + - + + /> - - Path * - - - + + + + + - - Username * - - + + + + + /> - - Password - + + + + +
`, styles: ` :host { text-align: right; - grid-area: 1 / 2 / 3 / 3; + grid-area: 1 / 2 / 4 / 3; place-content: center; white-space: nowrap; } @@ -110,13 +114,11 @@ import { InterfaceComponent } from './interface.component' } } `, - imports: [TuiButton, TuiDropdown, TuiDataList, i18nPipe], + imports: [TuiButton, TuiDropdown, TuiDataList, i18nPipe, TuiTextfield], providers: [tuiButtonOptionsProvider({ appearance: 'icon' })], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class InterfaceActionsComponent { - private readonly document = inject(DOCUMENT) - +export class AddressActionsComponent { readonly isMobile = inject(TUI_IS_MOBILE) readonly dialog = inject(DialogService) readonly copyService = inject(CopyService) @@ -125,6 +127,10 @@ export class InterfaceActionsComponent { readonly href = input.required() readonly disabled = input.required() + readonly onDetails = output() + + open = false + showQR() { this.dialog .openComponent(new PolymorpheusComponent(QRModal), { @@ -134,8 +140,4 @@ export class InterfaceActionsComponent { }) .subscribe() } - - openUI() { - this.document.defaultView?.open(this.href(), '_blank', 'noreferrer') - } } diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/addresses.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/addresses.component.ts new file mode 100644 index 000000000..a4841dea7 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/addresses.component.ts @@ -0,0 +1,110 @@ +import { ChangeDetectionStrategy, Component, input } from '@angular/core' +import { i18nPipe } from '@start9labs/shared' +import { TuiAccordion } from '@taiga-ui/experimental' +import { TuiSkeleton } from '@taiga-ui/kit' +import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component' +import { TableComponent } from 'src/app/routes/portal/components/table.component' + +import { MappedServiceInterface } from '../interface.service' +import { InterfaceAddressItemComponent } from './item.component' + +@Component({ + selector: 'section[addresses]', + template: ` +
{{ 'Addresses' | i18n }}
+ + @for (address of addresses()?.common; track $index) { + + } @empty { + @if (addresses()) { + + + + } @else { + @for (_ of [0, 1]; track $index) { + + + + } + } + } +
+ + {{ 'No addresses' | i18n }} + +
+
{{ 'Loading' | i18n }}
+
+ @if (addresses()?.uncommon?.length) { + + +
+ + @for (address of addresses()?.uncommon; track $index) { + + } +
+
+ +
+ } + `, + styles: ` + tui-accordion { + border-radius: 0; + } + + [tuiAccordion], + tui-expand { + box-shadow: none; + padding: 0; + } + + [tuiAccordion] { + justify-content: center; + height: 3rem; + border-radius: 0 0 var(--tui-radius-m) var(--tui-radius-m) !important; + } + + hr { + margin: 0; + height: 0.25rem; + border-radius: 1rem; + } + + :host-context(tui-root._mobile) { + [tuiAccordion] { + margin: 0.5rem 0; + border-radius: var(--tui-radius-m) !important; + } + } + `, + host: { class: 'g-card' }, + imports: [ + TableComponent, + PlaceholderComponent, + i18nPipe, + InterfaceAddressItemComponent, + TuiAccordion, + TuiSkeleton, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class InterfaceAddressesComponent { + readonly addresses = input.required< + MappedServiceInterface['addresses'] | undefined + >() + readonly isRunning = input.required() + + uncommon = false +} diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/item.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/item.component.ts new file mode 100644 index 000000000..ccd363eaa --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/item.component.ts @@ -0,0 +1,89 @@ +import { + ChangeDetectionStrategy, + Component, + input, + inject, +} from '@angular/core' +import { DialogService, i18nKey, i18nPipe } from '@start9labs/shared' +import { TuiButton } from '@taiga-ui/core' +import { DisplayAddress } from '../interface.service' +import { AddressActionsComponent } from './actions.component' +import { TuiBadge } from '@taiga-ui/kit' + +@Component({ + selector: 'tr[address]', + template: ` + @if (address(); as address) { + + + + {{ address.type }} + + @if (address.access === 'public') { + + {{ 'public' | i18n }} + + } @else if (address.access === 'private') { + + {{ 'private' | i18n }} + + } @else { + - + } + + + {{ address.gatewayName || '-' }} + + {{ address.url }} + + } + `, + styles: ` + :host-context(tui-root._mobile) { + td { + width: auto !important; + } + + td:first-child { + display: none; + } + + td:nth-child(2) { + font: var(--tui-font-text-m); + font-weight: bold; + color: var(--tui-text-primary); + } + } + `, + imports: [i18nPipe, AddressActionsComponent, TuiButton, TuiBadge], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class InterfaceAddressItemComponent { + readonly address = input.required() + readonly isRunning = input.required() + readonly dialog = inject(DialogService) + + viewDetails(bullets: string[]) { + this.dialog + .openAlert( + `
    ${bullets.map(b => `
  • ${b}
  • `).join('')}
` as i18nKey, + { + label: 'About this address' as i18nKey, + }, + ) + .subscribe() + } +} diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/clearnet.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/clearnet.component.ts deleted file mode 100644 index 4a96649a2..000000000 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/clearnet.component.ts +++ /dev/null @@ -1,301 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - inject, - input, -} from '@angular/core' -import { toSignal } from '@angular/core/rxjs-interop' -import { - DialogService, - DocsLinkDirective, - ErrorService, - i18nPipe, - LoadingService, -} from '@start9labs/shared' -import { ISB, utils } from '@start9labs/start-sdk' -import { - TuiAppearance, - TuiButton, - TuiDataList, - TuiIcon, - TuiLink, - TuiNotification, -} from '@taiga-ui/core' -import { TuiTooltip } from '@taiga-ui/kit' -import { PatchDB } from 'patch-db-client' -import { defaultIfEmpty, firstValueFrom, map } from 'rxjs' -import { - FormComponent, - FormContext, -} from 'src/app/routes/portal/components/form.component' -import { AcmePipe } from 'src/app/routes/portal/components/interfaces/acme.pipe' -import { InterfaceComponent } from 'src/app/routes/portal/components/interfaces/interface.component' -import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component' -import { TableComponent } from 'src/app/routes/portal/components/table.component' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { toAcmeName } from 'src/app/utils/acme' -import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' -import { InterfaceActionsComponent } from './actions.component' -import { ClearnetAddress } from './interface.utils' -import { MaskPipe } from './mask.pipe' - -type ClearnetForm = { - domain: string - acme: string -} - -@Component({ - selector: 'section[clearnet]', - template: ` -
- Clearnet - - - {{ - 'Add a clearnet address to expose this interface on the Internet. Clearnet addresses are fully public and not anonymous.' - | i18n - }} - - {{ 'Learn more' | i18n }} - - - @if (clearnet().length) { - - } -
- @if (clearnet().length) { - @if (!isPublic()) { - - {{ - 'To publish clearnet domains, you must click "Make Public", above.' - | i18n - }} - - } - - @for (address of clearnet(); track $index) { - - - - - - } -
- {{ interface.value().addSsl ? (address.acme | acme) : '-' }} - {{ address.url | mask }} - @if (address.isDomain) { - - } - @if (address.isDomain) { - - } -
- } @else { - - {{ 'No public addresses' | i18n }} - - - } - `, - styles: ` - :host-context(tui-root._mobile) { - td { - font-weight: bold; - color: var(--tui-text-primary); - - &:first-child { - font-weight: normal; - color: var(--tui-text-secondary); - } - } - } - `, - host: { class: 'g-card' }, - imports: [ - TuiButton, - TuiIcon, - TuiTooltip, - TuiLink, - TuiDataList, - TuiAppearance, - PlaceholderComponent, - TableComponent, - MaskPipe, - AcmePipe, - InterfaceActionsComponent, - i18nPipe, - DocsLinkDirective, - TuiNotification, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class InterfaceClearnetComponent { - private readonly dialog = inject(DialogService) - private readonly formDialog = inject(FormDialogService) - private readonly loader = inject(LoadingService) - private readonly errorService = inject(ErrorService) - private readonly api = inject(ApiService) - - readonly interface = inject(InterfaceComponent) - - readonly clearnet = input.required() - readonly isRunning = input.required() - readonly isPublic = input.required() - - readonly acme = toSignal( - inject>(PatchDB) - .watch$('serverInfo', 'network', 'acme') - .pipe(map(acme => Object.keys(acme))), - { initialValue: [] }, - ) - - async remove({ url }: ClearnetAddress) { - const confirm = await firstValueFrom( - this.dialog - .openConfirm({ - label: 'Confirm', - size: 's', - data: { - yes: 'Delete', - no: 'Cancel', - content: 'Are you sure you want to delete this address?', - }, - }) - .pipe(defaultIfEmpty(false)), - ) - - if (!confirm) { - return - } - - const loader = this.loader.open('Removing').subscribe() - - if (!/^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(url)) { - url = 'http://' + url - } - - const params = { domain: new URL(url).hostname } - - try { - if (this.interface.packageId()) { - await this.api.pkgRemoveDomain({ - ...params, - package: this.interface.packageId(), - host: this.interface.value().addressInfo.hostId, - }) - } else { - await this.api.serverRemoveDomain(params) - } - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } - - async add() { - const domain = ISB.Value.text({ - name: 'Domain', - description: 'The domain or subdomain you want to use', - placeholder: `e.g. 'mydomain.com' or 'sub.mydomain.com'`, - required: true, - default: null, - patterns: [utils.Patterns.domain], - }) - const acme = ISB.Value.select({ - name: 'ACME Provider', - description: - 'Select which ACME provider to use for obtaining your SSL certificate. Add new ACME providers in the System tab. Optionally use your system Root CA. Note: only devices that have trusted your Root CA will be able to access the domain without security warnings.', - values: this.acme().reduce( - (obj, url) => ({ - ...obj, - [url]: toAcmeName(url), - }), - { none: 'None (use system Root CA)' } as Record, - ), - default: '', - }) - - this.formDialog.open>(FormComponent, { - label: 'Select Domain', - data: { - spec: await configBuilderToSpec( - ISB.InputSpec.of( - this.interface.value().addSsl ? { domain, acme } : { domain }, - ), - ), - buttons: [ - { - text: 'Save', - handler: async value => this.save(value), - }, - ], - }, - }) - } - - private async save(domainInfo: ClearnetForm): Promise { - const loader = this.loader.open('Saving').subscribe() - - const { domain, acme } = domainInfo - - const params = { - domain, - acme: acme === 'none' ? null : acme, - private: false, - } - - try { - if (this.interface.packageId()) { - await this.api.pkgAddDomain({ - ...params, - package: this.interface.packageId(), - host: this.interface.value().addressInfo.hostId, - }) - } else { - await this.api.serverAddDomain(params) - } - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } -} diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/gateways.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/gateways.component.ts new file mode 100644 index 000000000..077fa87bd --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/gateways.component.ts @@ -0,0 +1,114 @@ +import { CommonModule } from '@angular/common' +import { + ChangeDetectionStrategy, + Component, + input, + inject, +} from '@angular/core' +import { TuiIcon, TuiTitle } from '@taiga-ui/core' +import { TuiSkeleton, TuiSwitch, TuiTooltip } from '@taiga-ui/kit' +import { FormsModule } from '@angular/forms' +import { i18nPipe, LoadingService, ErrorService } from '@start9labs/shared' +import { TuiCell } from '@taiga-ui/layout' +import { InterfaceGateway } from './interface.service' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { InterfaceComponent } from './interface.component' + +@Component({ + selector: 'section[gateways]', + template: ` +
{{ 'Gateways' | i18n }}
+ @for (gateway of gateways(); track $index) { + + } @empty { + @for (_ of [0, 1]; track $index) { + + } + } + `, + styles: ` + :host { + grid-column: span 3; + } + + [tuiCell]:has([tuiTooltip]) { + background: none !important; + } + + :host-context(tui-root:not(._mobile)) { + &:has(+ section table) header { + background: transparent; + } + } + `, + host: { class: 'g-card' }, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + CommonModule, + FormsModule, + TuiSwitch, + i18nPipe, + TuiCell, + TuiTitle, + TuiSkeleton, + TuiIcon, + TuiTooltip, + ], +}) +export class InterfaceGatewaysComponent { + private readonly loader = inject(LoadingService) + private readonly errorService = inject(ErrorService) + private readonly api = inject(ApiService) + readonly interface = inject(InterfaceComponent) + + readonly gateways = input.required() + + async onToggle(gateway: InterfaceGateway) { + const addressInfo = this.interface.value()!.addressInfo + const pkgId = this.interface.packageId() + + const loader = this.loader.open().subscribe() + + try { + if (pkgId) { + await this.api.pkgBindingToggleGateway({ + gateway: gateway.id, + enabled: !gateway.enabled, + internalPort: addressInfo.internalPort, + host: addressInfo.hostId, + package: pkgId, + }) + } else { + await this.api.serverBindingToggleGateway({ + gateway: gateway.id, + enabled: !gateway.enabled, + internalPort: 80, + }) + } + } catch (e: any) { + this.errorService.handleError(e) + } finally { + loader.unsubscribe() + } + } +} diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/interface.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/interface.component.ts index 7486bfea8..58d3d950b 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/interface.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/interface.component.ts @@ -1,101 +1,62 @@ -import { - ChangeDetectionStrategy, - Component, - inject, - input, -} from '@angular/core' -import { ErrorService, i18nPipe, LoadingService } from '@start9labs/shared' -import { TuiButton, tuiButtonOptionsProvider } from '@taiga-ui/core' -import { InterfaceClearnetComponent } from 'src/app/routes/portal/components/interfaces/clearnet.component' -import { InterfaceLocalComponent } from 'src/app/routes/portal/components/interfaces/local.component' -import { InterfaceTorComponent } from 'src/app/routes/portal/components/interfaces/tor.component' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { MappedServiceInterface } from './interface.utils' +import { ChangeDetectionStrategy, Component, input } from '@angular/core' +import { tuiButtonOptionsProvider } from '@taiga-ui/core' +import { MappedServiceInterface } from './interface.service' +import { InterfaceGatewaysComponent } from './gateways.component' +import { InterfaceTorDomainsComponent } from './tor-domains.component' +import { PublicDomainsComponent } from './public-domains/pd.component' +import { InterfacePrivateDomainsComponent } from './private-domains.component' +import { InterfaceAddressesComponent } from './addresses/addresses.component' @Component({ - selector: 'app-interface', + selector: 'service-interface', template: ` - -
-
-
+
+
+
+
+
+
+
+
`, styles: ` :host { - max-width: 56rem; display: flex; flex-direction: column; gap: 1rem; color: var(--tui-text-secondary); font: var(--tui-font-text-l); - ::ng-deep td { - overflow-wrap: anywhere; + div { + display: grid; + grid-template-columns: repeat(10, 1fr); + gap: inherit; + flex-direction: column; + } + + ::ng-deep [tuiSkeleton] { + width: 100%; + height: 1rem; + border-radius: var(--tui-radius-s); } } - button { - margin: -0.5rem auto 0 0; + :host-context(tui-root._mobile) div { + display: flex; } `, - providers: [tuiButtonOptionsProvider({ size: 'xs' })], changeDetection: ChangeDetectionStrategy.OnPush, + providers: [tuiButtonOptionsProvider({ size: 'xs' })], imports: [ - InterfaceClearnetComponent, - InterfaceTorComponent, - InterfaceLocalComponent, - TuiButton, - i18nPipe, + InterfaceGatewaysComponent, + InterfaceTorDomainsComponent, + PublicDomainsComponent, + InterfacePrivateDomainsComponent, + InterfaceAddressesComponent, ], }) export class InterfaceComponent { - private readonly loader = inject(LoadingService) - private readonly errorService = inject(ErrorService) - private readonly api = inject(ApiService) - readonly packageId = input('') - readonly value = input.required() + readonly value = input.required() readonly isRunning = input.required() - - async toggle() { - const loader = this.loader - .open(`Making ${this.value().public ? 'private' : 'public'}`) - .subscribe() - - const params = { - internalPort: this.value().addressInfo.internalPort, - public: !this.value().public, - } - - try { - if (this.packageId()) { - await this.api.pkgBindingSetPubic({ - ...params, - host: this.value().addressInfo.hostId, - package: this.packageId(), - }) - } else { - await this.api.serverBindingSetPubic(params) - } - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } } diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/interface.service.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/interface.service.ts new file mode 100644 index 000000000..de73f8546 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/interface.service.ts @@ -0,0 +1,520 @@ +import { inject, Injectable } from '@angular/core' +import { T, utils } from '@start9labs/start-sdk' +import { ConfigService } from 'src/app/services/config.service' +import { GatewayPlus } from 'src/app/services/gateway.service' +import { PublicDomain } from './public-domains/pd.service' +import { i18nKey, i18nPipe } from '@start9labs/shared' + +type AddressWithInfo = { + url: string + info: T.HostnameInfo + gateway?: GatewayPlus +} + +function cmpWithRankedPredicates( + a: T, + b: T, + preds: ((x: T) => boolean)[], +): -1 | 0 | 1 { + for (const pred of preds) { + for (let [x, y, sign] of [[a, b, 1] as const, [b, a, -1] as const]) { + if (pred(y) && !pred(x)) return sign + } + } + return 0 +} + +type TorAddress = AddressWithInfo & { info: { kind: 'onion' } } +function filterTor(a: AddressWithInfo): a is TorAddress { + return a.info.kind === 'onion' +} +function cmpTor(a: TorAddress, b: TorAddress): -1 | 0 | 1 { + for (let [x, y, sign] of [[a, b, 1] as const, [b, a, -1] as const]) { + if (y.url.startsWith('http:') && x.url.startsWith('https:')) return sign + } + return 0 +} + +type LanAddress = AddressWithInfo & { info: { kind: 'ip'; public: false } } +function filterLan(a: AddressWithInfo): a is LanAddress { + return a.info.kind === 'ip' && !a.info.public +} +function cmpLan(host: T.Host, a: LanAddress, b: LanAddress): -1 | 0 | 1 { + return cmpWithRankedPredicates(a, b, [ + x => + x.info.hostname.kind === 'domain' && + !!host.privateDomains.find(d => d === x.info.hostname.value), // private domain + x => x.info.hostname.kind === 'local', // .local + x => x.info.hostname.kind === 'ipv4', // ipv4 + x => x.info.hostname.kind === 'ipv6', // ipv6 + // remainder: public domains accessible privately + ]) +} + +type VpnAddress = AddressWithInfo & { + info: { + kind: 'ip' + public: false + hostname: { kind: 'ipv4' | 'ipv6' | 'domain' } + } +} +function filterVpn(a: AddressWithInfo): a is VpnAddress { + return ( + a.info.kind === 'ip' && !a.info.public && a.info.hostname.kind !== 'local' + ) +} +function cmpVpn(host: T.Host, a: VpnAddress, b: VpnAddress): -1 | 0 | 1 { + return cmpWithRankedPredicates(a, b, [ + x => + x.info.hostname.kind === 'domain' && + !!host.privateDomains.find(d => d === x.info.hostname.value), // private domain + x => x.info.hostname.kind === 'ipv4', // ipv4 + x => x.info.hostname.kind === 'ipv6', // ipv6 + // remainder: public domains accessible privately + ]) +} + +type ClearnetAddress = AddressWithInfo & { + info: { + kind: 'ip' + public: true + hostname: { kind: 'ipv4' | 'ipv6' | 'domain' } + } +} +function filterClearnet(a: AddressWithInfo): a is ClearnetAddress { + return a.info.kind === 'ip' && a.info.public +} +function cmpClearnet( + host: T.Host, + a: ClearnetAddress, + b: ClearnetAddress, +): -1 | 0 | 1 { + return cmpWithRankedPredicates(a, b, [ + x => + x.info.hostname.kind === 'domain' && + x.info.gatewayId === host.publicDomains[x.info.hostname.value]?.gateway, // public domain for this gateway + x => x.gateway?.public ?? false, // public gateway + x => x.info.hostname.kind === 'ipv4', // ipv4 + x => x.info.hostname.kind === 'ipv6', // ipv6 + // remainder: private domains / domains public on other gateways + ]) +} + +export function getPublicDomains( + publicDomains: Record, + gateways: GatewayPlus[], +): PublicDomain[] { + return Object.entries(publicDomains).map(([fqdn, info]) => ({ + fqdn, + acme: info.acme, + gateway: gateways.find(g => g.id === info.gateway) || null, + })) +} + +@Injectable({ + providedIn: 'root', +}) +export class InterfaceService { + private readonly config = inject(ConfigService) + private readonly i18n = inject(i18nPipe) + + getAddresses( + serviceInterface: T.ServiceInterface, + host: T.Host, + gateways: GatewayPlus[], + ): MappedServiceInterface['addresses'] { + const hostnamesInfos = this.hostnameInfo(serviceInterface, host) + + const addresses = { + common: [], + uncommon: [], + } + + if (!hostnamesInfos.length) return addresses + + const allAddressesWithInfo: AddressWithInfo[] = hostnamesInfos.flatMap(h => + utils.addressHostToUrl(serviceInterface.addressInfo, h).map(url => ({ + url, + info: h, + gateway: gateways.find(g => h.kind === 'ip' && h.gatewayId === g.id), + })), + ) + + const torAddrs = allAddressesWithInfo.filter(filterTor).sort(cmpTor) + const lanAddrs = allAddressesWithInfo + .filter(filterLan) + .sort((a, b) => cmpLan(host, a, b)) + const vpnAddrs = allAddressesWithInfo + .filter(filterVpn) + .sort((a, b) => cmpVpn(host, a, b)) + const clearnetAddrs = allAddressesWithInfo + .filter(filterClearnet) + .sort((a, b) => cmpClearnet(host, a, b)) + + let bestAddrs = [ + (clearnetAddrs[0]?.gateway?.public || + clearnetAddrs[0]?.info.hostname.kind === 'domain') && + clearnetAddrs[0], + lanAddrs[0], + vpnAddrs[0], + torAddrs[0], + ] + .filter(a => !!a) + .reduce((acc, x) => { + if (!acc.includes(x)) acc.push(x) + return acc + }, [] as AddressWithInfo[]) + + return { + common: bestAddrs.map(a => + this.toDisplayAddress(a, gateways, host.publicDomains), + ), + uncommon: allAddressesWithInfo + .filter(a => !bestAddrs.includes(a)) + .map(a => this.toDisplayAddress(a, gateways, host.publicDomains)), + } + } + + /** ${scheme}://${username}@${host}:${externalPort}${suffix} */ + launchableAddress(ui: T.ServiceInterface, host: T.Host): string { + const hostnameInfos = this.hostnameInfo(ui, host) + + if (!hostnameInfos.length) return '' + + const addressInfo = ui.addressInfo + const username = addressInfo.username ? addressInfo.username + '@' : '' + const suffix = addressInfo.suffix || '' + const url = new URL(`https://${username}placeholder${suffix}`) + const use = (hostname: { + value: string + port: number | null + sslPort: number | null + }) => { + url.hostname = hostname.value + const useSsl = + hostname.port && hostname.sslPort + ? this.config.isHttps() + : !!hostname.sslPort + url.protocol = useSsl + ? `${addressInfo.sslScheme || 'https'}:` + : `${addressInfo.scheme || 'http'}:` + const port = useSsl ? hostname.sslPort : hostname.port + const omitPort = useSsl + ? ui.addressInfo.sslScheme === 'https' && port === 443 + : ui.addressInfo.scheme === 'http' && port === 80 + if (!omitPort && port) url.port = String(port) + } + const useFirst = ( + hostnames: ( + | { + value: string + port: number | null + sslPort: number | null + } + | undefined + )[], + ) => { + const first = hostnames.find(h => h) + if (first) { + use(first) + } + return !!first + } + + const ipHostnames = hostnameInfos + .filter(h => h.kind === 'ip') + .map(h => h.hostname) as T.IpHostname[] + const domainHostname = ipHostnames + .filter(h => h.kind === 'domain') + .map(h => h as T.IpHostname & { kind: 'domain' }) + .map(h => ({ + value: h.value, + sslPort: h.sslPort, + port: h.port, + }))[0] + const wanIpHostname = hostnameInfos + .filter(h => h.kind === 'ip' && h.public && h.hostname.kind !== 'domain') + .map(h => h.hostname as Exclude) + .map(h => ({ + value: h.value, + sslPort: h.sslPort, + port: h.port, + }))[0] + const onionHostname = hostnameInfos + .filter(h => h.kind === 'onion') + .map(h => h as T.HostnameInfo & { kind: 'onion' }) + .map(h => ({ + value: h.hostname.value, + sslPort: h.hostname.sslPort, + port: h.hostname.port, + }))[0] + const localHostname = ipHostnames + .filter(h => h.kind === 'local') + .map(h => h as T.IpHostname & { kind: 'local' }) + .map(h => ({ value: h.value, sslPort: h.sslPort, port: h.port }))[0] + + if (this.config.isClearnet()) { + if ( + !useFirst([domainHostname, wanIpHostname, onionHostname, localHostname]) + ) { + return '' + } + } else if (this.config.isTor()) { + if ( + !useFirst([onionHostname, domainHostname, wanIpHostname, localHostname]) + ) { + return '' + } + } else if (this.config.isIpv6()) { + const ipv6Hostname = ipHostnames.find(h => h.kind === 'ipv6') as { + kind: 'ipv6' + value: string + scopeId: number + port: number | null + sslPort: number | null + } + + if (!useFirst([ipv6Hostname, localHostname])) { + return '' + } + } else { + // ipv4 or .local or localhost + + if (!localHostname) return '' + + use({ + value: this.config.hostname, + port: localHostname.port, + sslPort: localHostname.sslPort, + }) + } + + return url.href + } + + private hostnameInfo( + serviceInterface: T.ServiceInterface, + host: T.Host, + ): T.HostnameInfo[] { + let hostnameInfo = + host.hostnameInfo[serviceInterface.addressInfo.internalPort] + return ( + hostnameInfo?.filter( + h => + this.config.isLocalhost() || + !( + h.kind === 'ip' && + ((h.hostname.kind === 'ipv6' && + utils.IPV6_LINK_LOCAL.contains(h.hostname.value)) || + h.gatewayId === 'lo') + ), + ) || [] + ) + } + + private toDisplayAddress( + { info, url, gateway }: AddressWithInfo, + gateways: GatewayPlus[], + publicDomains: Record, + ): DisplayAddress { + let access: DisplayAddress['access'] + let gatewayName: DisplayAddress['gatewayName'] + let type: DisplayAddress['type'] + let bullets: any[] + // let bullets: DisplayAddress['bullets'] + + const rootCaRequired = this.i18n.transform( + "Requires trusting your server's Root CA", + ) + + // ** Tor ** + if (info.kind === 'onion') { + access = null + gatewayName = null + type = 'Tor' + bullets = [ + this.i18n.transform('Connections can be slow or unreliable at times'), + this.i18n.transform( + 'Public if you share the address publicly, otherwise private', + ), + this.i18n.transform('Requires using a Tor-enabled device or browser'), + ] + // Tor (HTTPS) + if (url.startsWith('https:')) { + type = `${type} (HTTPS)` + bullets = [ + this.i18n.transform('Only useful for clients that enforce HTTPS'), + rootCaRequired, + ...bullets, + ] + // Tor (HTTP) + } else { + bullets.unshift( + this.i18n.transform( + 'Ideal for anonymous, censorship-resistant hosting and remote access', + ), + ) + type = `${type} (HTTP)` + } + // ** Not Tor ** + } else { + const port = info.hostname.sslPort || info.hostname.port + const gateway = gateways.find(g => g.id === info.gatewayId)! + gatewayName = gateway.name + + const gatewayLanIpv4 = gateway.lanIpv4[0] + const isWireguard = gateway.ipInfo.deviceType === 'wireguard' + + const localIdeal = this.i18n.transform('Ideal for local access') + const lanRequired = this.i18n.transform( + 'Requires being connected to the same Local Area Network (LAN) as your server, either physically or via VPN', + ) + const staticRequired = `${this.i18n.transform('Requires setting a static IP address for')} ${gatewayLanIpv4} ${this.i18n.transform('in your gateway')}` + const vpnAccess = this.i18n.transform('Ideal for VPN access via') + const routerWireguard = this.i18n.transform( + "your router's Wireguard server", + ) + const portForwarding = this.i18n.transform( + 'Requires port forwarding in gateway', + ) + const dnsFor = this.i18n.transform('Requires a DNS record for') + const resolvesTo = this.i18n.transform('that resolves to') + + // * Local * + if (info.hostname.kind === 'local') { + type = this.i18n.transform('Local') + access = 'private' + bullets = [ + localIdeal, + this.i18n.transform( + 'Not recommended for VPN access. VPNs do not support ".local" domains without advanced configuration', + ), + lanRequired, + rootCaRequired, + ] + // * IPv4 * + } else if (info.hostname.kind === 'ipv4') { + type = 'IPv4' + if (info.public) { + access = 'public' + bullets = [ + this.i18n.transform('Can be used for clearnet access'), + this.i18n.transform( + 'Not recommended in most cases. Public domains are preferred', + ), + rootCaRequired, + ] + if (!gateway.public) { + bullets.push( + `${portForwarding} "${gatewayName}": ${port} -> ${gateway.subnets.find(s => s.isIpv4())?.address}:${port}`, + ) + } + } else { + access = 'private' + if (isWireguard) { + bullets = [`${vpnAccess} StartTunnel`, rootCaRequired] + } else { + bullets = [ + localIdeal, + `${vpnAccess} ${routerWireguard}`, + lanRequired, + rootCaRequired, + staticRequired, + ] + } + } + // * IPv6 * + } else if (info.hostname.kind === 'ipv6') { + type = 'IPv6' + access = 'private' + bullets = [ + this.i18n.transform('Can be used for local access'), + lanRequired, + rootCaRequired, + ] + // * Domain * + } else { + type = this.i18n.transform('Domain') + if (info.public) { + access = 'public' + bullets = [ + `${dnsFor} ${info.hostname.value} ${resolvesTo} ${gateway.ipInfo.wanIp}`, + ] + + if (!gateway.public) { + bullets.push( + `${portForwarding} "${gatewayName}": ${port} -> ${gateway.subnets.find(s => s.isIpv4())?.address}:${port === 443 ? 5443 : port}`, + ) + } + + if (publicDomains[info.hostname.value]?.acme) { + bullets.unshift( + this.i18n.transform('Ideal for public access via the Internet'), + ) + } else { + bullets = [ + this.i18n.transform( + 'Can be used for personal access via the public Internet. VPN is more private and secure', + ), + rootCaRequired, + ...bullets, + ] + } + } else { + access = 'private' + const ipPortBad = this.i18n.transform( + 'when using IP addresses and ports is undesirable', + ) + const customDnsRequired = `${dnsFor} ${info.hostname.value} ${resolvesTo} ${gatewayLanIpv4}` + if (isWireguard) { + bullets = [ + `${vpnAccess} StartTunnel ${ipPortBad}`, + customDnsRequired, + rootCaRequired, + ] + } else { + bullets = [ + `${localIdeal} ${ipPortBad}`, + `${vpnAccess} ${routerWireguard} ${ipPortBad}`, + customDnsRequired, + rootCaRequired, + lanRequired, + staticRequired, + ] + } + } + } + } + + return { + url, + access, + gatewayName, + type, + bullets, + } + } +} + +export type MappedServiceInterface = T.ServiceInterface & { + gateways: InterfaceGateway[] + torDomains: string[] + publicDomains: PublicDomain[] + privateDomains: string[] + addresses: { + common: DisplayAddress[] + uncommon: DisplayAddress[] + } +} + +export type InterfaceGateway = GatewayPlus & { + enabled: boolean +} + +export type DisplayAddress = { + type: string + access: 'public' | 'private' | null + gatewayName: string | null + url: string + bullets: i18nKey[] +} diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/interface.utils.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/interface.utils.ts deleted file mode 100644 index 7d366df10..000000000 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/interface.utils.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { T, utils } from '@start9labs/start-sdk' -import { ConfigService } from 'src/app/services/config.service' - -export abstract class AddressesService { - abstract static: boolean - abstract add(): Promise - abstract remove(): Promise -} - -export function getAddresses( - serviceInterface: T.ServiceInterface, - host: T.Host, - config: ConfigService, -): { - clearnet: ClearnetAddress[] - local: LocalAddress[] - tor: TorAddress[] -} { - const addressInfo = serviceInterface.addressInfo - const hostnames = - host.hostnameInfo[addressInfo.internalPort]?.filter( - h => - config.isLocalhost() || - h.kind !== 'ip' || - h.hostname.kind !== 'ipv6' || - !h.hostname.value.startsWith('fe80::'), - ) || [] - - if (config.isLocalhost()) { - const local = hostnames.find( - h => h.kind === 'ip' && h.hostname.kind === 'local', - ) - - if (local) { - hostnames.unshift({ - kind: 'ip', - networkInterfaceId: 'lo', - public: false, - hostname: { - kind: 'local', - port: local.hostname.port, - sslPort: local.hostname.sslPort, - value: 'localhost', - }, - }) - } - } - - const clearnet: ClearnetAddress[] = [] - const local: LocalAddress[] = [] - const tor: TorAddress[] = [] - - hostnames.forEach(h => { - const addresses = utils.addressHostToUrl(addressInfo, h) - - addresses.forEach(url => { - if (h.kind === 'onion') { - tor.push({ - protocol: /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(url) - ? new URL(url).protocol.replace(':', '').toUpperCase() - : null, - url, - }) - } else { - const hostnameKind = h.hostname.kind - - if ( - h.public || - (hostnameKind === 'domain' && host.domains[h.hostname.domain]?.public) - ) { - clearnet.push({ - url, - disabled: !h.public, - isDomain: hostnameKind == 'domain', - acme: - hostnameKind == 'domain' - ? host.domains[h.hostname.domain]?.acme || null - : null, - }) - } else { - local.push({ - nid: - hostnameKind === 'local' - ? 'Local' - : `${h.networkInterfaceId} (${hostnameKind})`, - url, - }) - } - } - }) - }) - - return { - clearnet: clearnet.filter( - (value, index, self) => - index === self.findIndex(t => t.url === value.url), - ), - local: local.filter( - (value, index, self) => - index === self.findIndex(t => t.url === value.url), - ), - tor: tor.filter( - (value, index, self) => - index === self.findIndex(t => t.url === value.url), - ), - } -} - -export type MappedServiceInterface = T.ServiceInterface & { - addSsl?: T.AddSslOptions | null - public: boolean - addresses: { - clearnet: ClearnetAddress[] - local: LocalAddress[] - tor: TorAddress[] - } -} - -export type ClearnetAddress = { - url: string - acme: string | null - isDomain: boolean - disabled: boolean -} - -export type LocalAddress = { - url: string - nid: string -} - -export type TorAddress = { - url: string - protocol: string | null -} diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/local.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/local.component.ts deleted file mode 100644 index 10edaeda9..000000000 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/local.component.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ChangeDetectionStrategy, Component, input } from '@angular/core' -import { TuiIcon, TuiLink } from '@taiga-ui/core' -import { TuiTooltip } from '@taiga-ui/kit' -import { TableComponent } from 'src/app/routes/portal/components/table.component' -import { InterfaceActionsComponent } from './actions.component' -import { LocalAddress } from './interface.utils' -import { MaskPipe } from './mask.pipe' -import { DocsLinkDirective, i18nPipe } from '@start9labs/shared' - -@Component({ - selector: 'section[local]', - template: ` -
- {{ 'Local' | i18n }} - - - {{ - 'Local addresses can only be accessed by devices connected to the same LAN as your server, either directly or using a VPN.' - | i18n - }} - - {{ 'Learn More' | i18n }} - - -
- - @for (address of local(); track $index) { - - - - - - } -
{{ address.nid }}{{ address.url | mask }}
- `, - host: { class: 'g-card' }, - imports: [ - TuiIcon, - TuiTooltip, - TuiLink, - TableComponent, - InterfaceActionsComponent, - MaskPipe, - i18nPipe, - DocsLinkDirective, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class InterfaceLocalComponent { - readonly local = input.required() - readonly isRunning = input.required() -} diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/mask.pipe.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/mask.pipe.ts index ab2e25d04..5b906e0ce 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/mask.pipe.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/mask.pipe.ts @@ -8,7 +8,7 @@ export class MaskPipe implements PipeTransform { private readonly interface = inject(InterfaceComponent) transform(value: string): string { - return this.interface.value().masked + return this.interface.value()?.masked ? '•'.repeat(Math.min(32, value.length)) : value } diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/private-domains.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/private-domains.component.ts new file mode 100644 index 000000000..fdf73b6a6 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/private-domains.component.ts @@ -0,0 +1,183 @@ +import { + ChangeDetectionStrategy, + Component, + inject, + input, +} from '@angular/core' +import { + DialogService, + DocsLinkDirective, + ErrorService, + i18nPipe, + LoadingService, +} from '@start9labs/shared' +import { ISB, utils } from '@start9labs/start-sdk' +import { TuiButton, TuiTitle } from '@taiga-ui/core' +import { TuiSkeleton } from '@taiga-ui/kit' +import { TuiCell } from '@taiga-ui/layout' +import { filter } from 'rxjs' +import { + FormComponent, + FormContext, +} from 'src/app/routes/portal/components/form.component' +import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { FormDialogService } from 'src/app/services/form-dialog.service' +import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' +import { InterfaceComponent } from './interface.component' + +@Component({ + selector: 'section[privateDomains]', + template: ` +
+ {{ 'Private Domains' | i18n }} + + {{ 'Documentation' | i18n }} + + +
+ @for (domain of privateDomains(); track $index) { +
+ {{ domain }} + +
+ } @empty { + @if (privateDomains()) { + + {{ 'No private domains' | i18n }} + + } @else { + @for (_ of [0, 1]; track $index) { + + } + } + } + `, + styles: ` + :host { + grid-column: span 4; + overflow-wrap: break-word; + } + `, + host: { class: 'g-card' }, + imports: [ + TuiCell, + TuiTitle, + TuiButton, + PlaceholderComponent, + i18nPipe, + DocsLinkDirective, + TuiSkeleton, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class InterfacePrivateDomainsComponent { + private readonly dialog = inject(DialogService) + private readonly formDialog = inject(FormDialogService) + private readonly loader = inject(LoadingService) + private readonly errorService = inject(ErrorService) + private readonly api = inject(ApiService) + private readonly interface = inject(InterfaceComponent) + private readonly i18n = inject(i18nPipe) + + readonly privateDomains = input.required() + + async add() { + this.formDialog.open>(FormComponent, { + label: 'New private domain', + data: { + spec: await configBuilderToSpec( + ISB.InputSpec.of({ + fqdn: ISB.Value.text({ + name: this.i18n.transform('Domain'), + description: this.i18n.transform( + 'Enter a fully qualified domain name. Since the domain is for private use, it can be any domain you want, even one you do not control.', + ), + required: true, + default: null, + patterns: [utils.Patterns.domain], + }), + }), + ), + buttons: [ + { + text: this.i18n.transform('Save')!, + handler: async value => this.save(value.fqdn), + }, + ], + }, + }) + } + + async remove(fqdn: string) { + this.dialog + .openConfirm({ label: 'Are you sure?', size: 's' }) + .pipe(filter(Boolean)) + .subscribe(async () => { + const loader = this.loader.open('Removing').subscribe() + + try { + if (this.interface.packageId()) { + await this.api.pkgRemovePrivateDomain({ + fqdn, + package: this.interface.packageId(), + host: this.interface.value()?.addressInfo.hostId || '', + }) + } else { + await this.api.osUiRemovePrivateDomain({ fqdn }) + } + return true + } catch (e: any) { + this.errorService.handleError(e) + return false + } finally { + loader.unsubscribe() + } + }) + } + + private async save(fqdn: string): Promise { + const loader = this.loader.open('Saving').subscribe() + + try { + if (this.interface.packageId) { + await this.api.pkgAddPrivateDomain({ + fqdn, + package: this.interface.packageId(), + host: this.interface.value()?.addressInfo.hostId || '', + }) + } else { + await this.api.osUiAddPrivateDomain({ fqdn }) + } + return true + } catch (e: any) { + this.errorService.handleError(e) + return false + } finally { + loader.unsubscribe() + } + } +} diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/dns.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/dns.component.ts new file mode 100644 index 000000000..e11135b07 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/dns.component.ts @@ -0,0 +1,167 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + inject, + signal, +} from '@angular/core' +import { FormsModule } from '@angular/forms' +import { ErrorService, i18nPipe } from '@start9labs/shared' +import { TuiButton, TuiDialogContext, TuiIcon } from '@taiga-ui/core' +import { + TuiButtonLoading, + TuiSwitch, + tuiSwitchOptionsProvider, +} from '@taiga-ui/kit' +import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' +import { TableComponent } from 'src/app/routes/portal/components/table.component' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { parse } from 'tldts' +import { GatewayWithId } from './pd.service' + +@Component({ + selector: 'dns', + template: ` +

{{ context.data.message }}

+ + @let wanIp = context.data.gateway.ipInfo.wanIp || ('Error' | i18n); + + @if (context.data.gateway.ipInfo.deviceType !== 'wireguard') { + + } + + + @for (row of rows(); track $index) { + + + + + + + } +
+ @if (pass() === true) { + + } @else if (pass() === false) { + + } + {{ ddns ? 'ALIAS' : 'A' }} + {{ row.host }}{{ ddns ? '[DDNS Address]' : wanIp }}{{ row.purpose }}
+ +
+ +
+ `, + styles: ` + label { + display: flex; + gap: 0.75rem; + align-items: center; + margin: 1rem 0; + } + + tui-icon { + font-size: 1rem; + vertical-align: text-bottom; + } + `, + providers: [ + tuiSwitchOptionsProvider({ + appearance: () => 'primary', + icon: () => '', + }), + ], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + TuiButton, + i18nPipe, + TableComponent, + TuiSwitch, + FormsModule, + TuiButtonLoading, + TuiIcon, + ], +}) +export class DnsComponent { + private readonly errorService = inject(ErrorService) + private readonly api = inject(ApiService) + private readonly i18n = inject(i18nPipe) + + readonly ddns = false + + readonly context = + injectContext< + TuiDialogContext< + void, + { fqdn: string; gateway: GatewayWithId; message: string } + > + >() + + readonly loading = signal(false) + readonly pass = signal(undefined) + + readonly rows = computed<{ host: string; purpose: string }[]>(() => { + const { domain, subdomain } = parse(this.context.data.fqdn) + + if (!subdomain) { + return [ + { + host: '@', + purpose: domain!, + }, + ] + } + + const segments = subdomain.split('.').slice(1) + + const subdomains = this.i18n.transform('all subdomains of') + + return [ + { + host: subdomain, + purpose: `only ${subdomain}`, + }, + ...segments.map((_, i) => { + const parent = segments.slice(i).join('.') + return { + host: `*.${parent}`, + purpose: `${subdomains} ${parent}`, + } + }), + { + host: '*', + purpose: `${subdomains} ${domain}`, + }, + ] + }) + + async testDns() { + this.pass.set(undefined) + this.loading.set(true) + + try { + const ip = await this.api.queryDns({ + fqdn: this.context.data.fqdn, + }) + + this.pass.set(ip === this.context.data.gateway.ipInfo.wanIp) + } catch (e: any) { + this.errorService.handleError(e) + } finally { + this.loading.set(false) + } + } +} + +export const DNS = new PolymorpheusComponent(DnsComponent) diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.component.ts new file mode 100644 index 000000000..8ffc4df5c --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.component.ts @@ -0,0 +1,82 @@ +import { + ChangeDetectionStrategy, + Component, + inject, + input, +} from '@angular/core' +import { DocsLinkDirective, i18nPipe } from '@start9labs/shared' +import { TuiButton } from '@taiga-ui/core' +import { TuiSkeleton } from '@taiga-ui/kit' +import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component' +import { TableComponent } from 'src/app/routes/portal/components/table.component' +import { PublicDomainsItemComponent } from './pd.item.component' +import { PublicDomain, PublicDomainService } from './pd.service' + +@Component({ + selector: 'section[publicDomains]', + template: ` +
+ {{ 'Public Domains' | i18n }} + + {{ 'Documentation' | i18n }} + + @if (service.data()) { + + } +
+ @if (publicDomains()?.length === 0) { + + {{ 'No public domains' | i18n }} + + } @else { + + @for (domain of publicDomains(); track $index) { + + } @empty { + @for (_ of [0]; track $index) { + + + + } + } +
+
{{ 'Loading' | i18n }}
+
+ } + `, + styles: ` + :host { + grid-column: span 7; + } + `, + host: { class: 'g-card' }, + providers: [PublicDomainService], + imports: [ + TuiButton, + TableComponent, + PlaceholderComponent, + i18nPipe, + DocsLinkDirective, + PublicDomainsItemComponent, + TuiSkeleton, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PublicDomainsComponent { + readonly service = inject(PublicDomainService) + + readonly publicDomains = input.required() +} diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.item.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.item.component.ts new file mode 100644 index 000000000..2385441f2 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.item.component.ts @@ -0,0 +1,116 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + inject, + input, +} from '@angular/core' +import { i18nPipe, i18nKey } from '@start9labs/shared' +import { + TuiButton, + TuiDataList, + TuiDropdown, + TuiTextfield, +} from '@taiga-ui/core' +import { PublicDomain, PublicDomainService } from './pd.service' +import { toAuthorityName } from 'src/app/utils/acme' + +@Component({ + selector: 'tr[publicDomain]', + template: ` + {{ publicDomain().fqdn }} + {{ publicDomain().gateway?.name }} + {{ authority() }} + + + + + + + + + + + `, + styles: ` + :host { + grid-template-columns: min-content 1fr min-content; + } + + td:nth-child(2) { + order: -1; + grid-column: span 2; + } + + td:last-child { + grid-area: 1 / 3 / 3; + align-self: center; + text-align: right; + } + + :host-context(tui-root._mobile) { + .authority { + grid-column: span 2; + } + tui-badge { + vertical-align: bottom; + margin-inline-start: 0.25rem; + } + } + `, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [TuiButton, TuiDataList, TuiDropdown, i18nPipe, TuiTextfield], +}) +export class PublicDomainsItemComponent { + protected readonly service = inject(PublicDomainService) + + open = false + + readonly publicDomain = input.required() + + readonly authority = computed(() => toAuthorityName(this.publicDomain().acme)) + readonly dnsMessage = computed( + () => + `Create one of the DNS records below to cause ${this.publicDomain().fqdn} to resolve to ${this.publicDomain().gateway?.ipInfo.wanIp}` as i18nKey, + ) +} diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.service.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.service.ts new file mode 100644 index 000000000..a0099c0b8 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.service.ts @@ -0,0 +1,261 @@ +import { inject, Injectable } from '@angular/core' +import { + DialogService, + ErrorService, + i18nKey, + LoadingService, + i18nPipe, +} from '@start9labs/shared' +import { toSignal } from '@angular/core/rxjs-interop' +import { ISB, T, utils } from '@start9labs/start-sdk' +import { filter, map } from 'rxjs' +import { FormComponent } from 'src/app/routes/portal/components/form.component' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { FormDialogService } from 'src/app/services/form-dialog.service' +import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' +import { PatchDB } from 'patch-db-client' +import { DataModel } from 'src/app/services/patch-db/data-model' +import { toAuthorityName } from 'src/app/utils/acme' +import { InterfaceComponent } from '../interface.component' +import { DNS } from './dns.component' + +export type PublicDomain = { + fqdn: string + gateway: GatewayWithId | null + acme: string | null +} + +export type GatewayWithId = T.NetworkInterfaceInfo & { + id: string + ipInfo: T.IpInfo +} + +@Injectable() +export class PublicDomainService { + private readonly patch = inject>(PatchDB) + private readonly loader = inject(LoadingService) + private readonly errorService = inject(ErrorService) + private readonly api = inject(ApiService) + private readonly formDialog = inject(FormDialogService) + private readonly dialog = inject(DialogService) + private readonly interface = inject(InterfaceComponent) + private readonly i18n = inject(i18nPipe) + + readonly data = toSignal( + this.patch.watch$('serverInfo', 'network').pipe( + map(({ gateways, acme }) => ({ + gateways: Object.entries(gateways) + .filter(([_, g]) => g.ipInfo) + .map(([id, g]) => ({ id, ...g })) as GatewayWithId[], + authorities: Object.keys(acme).reduce>( + (obj, url) => ({ + ...obj, + [url]: toAuthorityName(url), + }), + { local: toAuthorityName(null) }, + ), + })), + ), + ) + + async add() { + const addSpec = ISB.InputSpec.of({ + fqdn: ISB.Value.text({ + name: this.i18n.transform('Domain'), + description: this.i18n.transform( + 'Enter a fully qualified domain name. For example, if you control domain.com, you could enter domain.com or subdomain.domain.com or another.subdomain.domain.com.', + ), + required: true, + default: null, + patterns: [utils.Patterns.domain], + }), + ...this.gatewayAndAuthoritySpec(), + }) + + this.formDialog.open(FormComponent, { + label: 'Add public domain', + data: { + spec: await configBuilderToSpec(addSpec), + buttons: [ + { + text: 'Save', + handler: (input: typeof addSpec._TYPE) => + this.save(input.fqdn, input.gateway, input.authority), + }, + ], + }, + }) + } + + async edit(domain: PublicDomain) { + const editSpec = ISB.InputSpec.of({ + ...this.gatewayAndAuthoritySpec(), + }) + + this.formDialog.open(FormComponent, { + label: 'Edit public domain', + data: { + spec: await configBuilderToSpec(editSpec), + buttons: [ + { + text: 'Save', + handler: ({ gateway, authority }: typeof editSpec._TYPE) => + this.save(domain.fqdn, gateway, authority), + }, + ], + value: { + gateway: domain.gateway!.id, + authority: domain.acme, + }, + }, + }) + } + + remove(fqdn: string) { + this.dialog + .openConfirm({ label: 'Are you sure?', size: 's' }) + .pipe(filter(Boolean)) + .subscribe(async () => { + const loader = this.loader.open('Deleting').subscribe() + + try { + if (this.interface.packageId()) { + await this.api.pkgRemovePublicDomain({ + fqdn, + package: this.interface.packageId(), + host: this.interface.value()?.addressInfo.hostId || '', + }) + } else { + await this.api.osUiRemovePublicDomain({ fqdn }) + } + return true + } catch (e: any) { + this.errorService.handleError(e) + return false + } finally { + loader.unsubscribe() + } + }) + } + + showDns(fqdn: string, gateway: GatewayWithId, message: i18nKey) { + this.dialog + .openComponent(DNS, { + label: 'DNS Records', + size: 'l', + data: { + fqdn, + gateway, + message, + }, + }) + .subscribe() + } + + private async save( + fqdn: string, + gatewayId: string, + authority: 'local' | string, + ) { + const gateway = this.data()!.gateways.find(g => g.id === gatewayId)! + + const loader = this.loader.open('Saving').subscribe() + const params = { + fqdn, + gateway: gatewayId, + acme: authority === 'local' ? null : authority, + } + try { + let ip: string | null + if (this.interface.packageId()) { + ip = await this.api.pkgAddPublicDomain({ + ...params, + package: this.interface.packageId(), + host: this.interface.value()?.addressInfo.hostId || '', + }) + } else { + ip = await this.api.osUiAddPublicDomain(params) + } + + const wanIp = gateway.ipInfo.wanIp + + let message = this.i18n.transform( + 'Create one of the DNS records below.', + ) as i18nKey + + if (!ip) { + setTimeout( + () => + this.showDns( + fqdn, + gateway, + `${this.i18n.transform('No DNS record detected for')} ${fqdn}. ${message}` as i18nKey, + ), + 250, + ) + } else if (ip !== wanIp) { + setTimeout( + () => + this.showDns( + fqdn, + gateway, + `${this.i18n.transform('Invalid DNS record')}. ${fqdn} ${this.i18n.transform('resolves to')} ${ip}. ${message}` as i18nKey, + ), + 250, + ) + } else { + setTimeout( + () => + this.dialog + .openAlert( + `${fqdn} ${this.i18n.transform('resolves to')} ${wanIp}` as i18nKey, + { label: 'DNS record detected!', appearance: 'positive' }, + ) + .subscribe(), + 250, + ) + } + + return true + } catch (e: any) { + this.errorService.handleError(e) + return false + } finally { + loader.unsubscribe() + } + } + + private gatewayAndAuthoritySpec() { + const data = this.data()! + + return { + gateway: ISB.Value.dynamicSelect(() => ({ + name: this.i18n.transform('Gateway'), + description: this.i18n.transform( + 'Select a gateway to use for this domain.', + ), + values: data.gateways.reduce>( + (obj, gateway) => ({ + ...obj, + [gateway.id]: gateway.ipInfo!.name, + }), + {}, + ), + default: '', + disabled: data.gateways + .filter( + g => !g.ipInfo?.wanIp || utils.CGNAT.contains(g.ipInfo?.wanIp), + ) + .map(g => g.id), + })), + authority: ISB.Value.select({ + name: this.i18n.transform('Certificate Authority'), + description: this.i18n.transform( + 'Select a Certificate Authority to issue SSL/TLS certificates for this domain', + ), + values: data.authorities, + default: '', + }), + } + } +} diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/status.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/status.component.ts deleted file mode 100644 index 7c69d6958..000000000 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/status.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ChangeDetectionStrategy, Component, input } from '@angular/core' -import { i18nPipe } from '@start9labs/shared' -import { TuiBadge } from '@taiga-ui/kit' - -@Component({ - selector: 'interface-status', - template: ` - - {{ public() ? ('Public' | i18n) : ('Private' | i18n) }} - - `, - styles: ` - :host { - display: inline-flex; - } - `, - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [TuiBadge, i18nPipe], -}) -export class InterfaceStatusComponent { - readonly public = input(false) -} diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/tor-domains.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/tor-domains.component.ts new file mode 100644 index 000000000..c0861e075 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/tor-domains.component.ts @@ -0,0 +1,192 @@ +import { + ChangeDetectionStrategy, + Component, + inject, + input, +} from '@angular/core' +import { + DialogService, + DocsLinkDirective, + ErrorService, + i18nPipe, + LoadingService, +} from '@start9labs/shared' +import { ISB, utils } from '@start9labs/start-sdk' +import { TuiButton, TuiTitle } from '@taiga-ui/core' +import { TuiSkeleton } from '@taiga-ui/kit' +import { TuiCell } from '@taiga-ui/layout' +import { filter } from 'rxjs' +import { + FormComponent, + FormContext, +} from 'src/app/routes/portal/components/form.component' +import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { FormDialogService } from 'src/app/services/form-dialog.service' +import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' + +import { InterfaceComponent } from './interface.component' + +type OnionForm = { + key: string +} + +@Component({ + selector: 'section[torDomains]', + template: ` +
+ Tor Domains + + {{ 'Documentation' | i18n }} + + +
+ @for (domain of torDomains(); track $index) { +
+ {{ domain }} + +
+ } @empty { + @if (torDomains()) { + + {{ 'No Tor domains' | i18n }} + + } @else { + @for (_ of [0, 1]; track $index) { + + } + } + } + `, + styles: ` + :host { + grid-column: span 6; + overflow-wrap: break-word; + } + `, + host: { class: 'g-card' }, + imports: [ + TuiCell, + TuiTitle, + TuiButton, + PlaceholderComponent, + i18nPipe, + DocsLinkDirective, + TuiSkeleton, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class InterfaceTorDomainsComponent { + private readonly dialog = inject(DialogService) + private readonly formDialog = inject(FormDialogService) + private readonly loader = inject(LoadingService) + private readonly errorService = inject(ErrorService) + private readonly api = inject(ApiService) + private readonly interface = inject(InterfaceComponent) + private readonly i18n = inject(i18nPipe) + + readonly torDomains = input.required() + + async remove(onion: string) { + this.dialog + .openConfirm({ label: 'Are you sure?', size: 's' }) + .pipe(filter(Boolean)) + .subscribe(async () => { + const loader = this.loader.open('Removing').subscribe() + const params = { onion } + + try { + if (this.interface.packageId()) { + await this.api.pkgRemoveOnion({ + ...params, + package: this.interface.packageId(), + host: this.interface.value()?.addressInfo.hostId || '', + }) + } else { + await this.api.serverRemoveOnion(params) + } + return true + } catch (e: any) { + this.errorService.handleError(e) + return false + } finally { + loader.unsubscribe() + } + }) + } + + async add() { + this.formDialog.open>(FormComponent, { + label: 'New Tor domain', + data: { + spec: await configBuilderToSpec( + ISB.InputSpec.of({ + key: ISB.Value.text({ + name: this.i18n.transform('Private Key (optional)')!, + description: this.i18n.transform( + 'Optionally provide a base64-encoded ed25519 private key for generating the Tor V3 (.onion) domain. If not provided, a random key will be generated.', + ), + required: false, + default: null, + patterns: [utils.Patterns.base64], + }), + }), + ), + buttons: [ + { + text: this.i18n.transform('Save')!, + handler: async value => this.save(value.key), + }, + ], + }, + }) + } + + private async save(key?: string): Promise { + const loader = this.loader.open('Saving').subscribe() + + try { + const onion = key + ? await this.api.addTorKey({ key }) + : await this.api.generateTorKey({}) + + if (this.interface.packageId) { + await this.api.pkgAddOnion({ + onion, + package: this.interface.packageId(), + host: this.interface.value()?.addressInfo.hostId || '', + }) + } else { + await this.api.serverAddOnion({ onion }) + } + return true + } catch (e: any) { + this.errorService.handleError(e) + return false + } finally { + loader.unsubscribe() + } + } +} diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/tor.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/tor.component.ts deleted file mode 100644 index 1ae620b98..000000000 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/tor.component.ts +++ /dev/null @@ -1,233 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - inject, - input, -} from '@angular/core' -import { - DialogService, - DocsLinkDirective, - ErrorService, - i18nPipe, - LoadingService, -} from '@start9labs/shared' -import { ISB, utils } from '@start9labs/start-sdk' -import { - TuiAppearance, - TuiButton, - TuiIcon, - TuiLink, - TuiOption, -} from '@taiga-ui/core' -import { TuiTooltip } from '@taiga-ui/kit' -import { defaultIfEmpty, firstValueFrom } from 'rxjs' -import { - FormComponent, - FormContext, -} from 'src/app/routes/portal/components/form.component' -import { InterfaceComponent } from 'src/app/routes/portal/components/interfaces/interface.component' -import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component' -import { TableComponent } from 'src/app/routes/portal/components/table.component' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' -import { InterfaceActionsComponent } from './actions.component' -import { TorAddress } from './interface.utils' -import { MaskPipe } from './mask.pipe' - -type OnionForm = { - key: string -} - -@Component({ - selector: 'section[tor]', - template: ` -
- Tor - - - {{ - 'Add an onion address to anonymously expose this interface on the darknet. Onion addresses can only be reached over the Tor network.' - | i18n - }} - - {{ 'Learn More' | i18n }} - - - @if (tor().length) { - - } -
- @if (tor().length) { - - @for (address of tor(); track $index) { - - - - - - } -
{{ address.protocol || '-' }}{{ address.url | mask }} - - -
- } @else { - - {{ 'No onion addresses' | i18n }} - - - } - `, - styles: ` - [tuiFade] { - white-space: nowrap; - max-width: 30rem; - } - `, - host: { class: 'g-card' }, - imports: [ - TuiButton, - TuiIcon, - TuiTooltip, - TuiLink, - TuiAppearance, - TuiOption, - TableComponent, - PlaceholderComponent, - MaskPipe, - InterfaceActionsComponent, - i18nPipe, - DocsLinkDirective, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class InterfaceTorComponent { - private readonly dialog = inject(DialogService) - private readonly formDialog = inject(FormDialogService) - private readonly loader = inject(LoadingService) - private readonly errorService = inject(ErrorService) - private readonly api = inject(ApiService) - private readonly interface = inject(InterfaceComponent) - private readonly i18n = inject(i18nPipe) - - readonly tor = input.required() - readonly isRunning = input.required() - - async remove({ url }: TorAddress) { - const confirm = await firstValueFrom( - this.dialog - .openConfirm({ - label: 'Confirm', - size: 's', - data: { - yes: 'Delete', - no: 'Cancel', - content: 'Are you sure you want to delete this address?', - }, - }) - .pipe(defaultIfEmpty(false)), - ) - - if (!confirm) { - return - } - - const loader = this.loader.open('Removing').subscribe() - const params = { onion: new URL(url).hostname } - - try { - if (this.interface.packageId()) { - await this.api.pkgRemoveOnion({ - ...params, - package: this.interface.packageId(), - host: this.interface.value().addressInfo.hostId, - }) - } else { - await this.api.serverRemoveOnion(params) - } - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } - - async add() { - this.formDialog.open>(FormComponent, { - label: 'New Onion Address', - data: { - spec: await configBuilderToSpec( - ISB.InputSpec.of({ - key: ISB.Value.text({ - name: this.i18n.transform('Private Key (optional)')!, - description: this.i18n.transform( - 'Optionally provide a base64-encoded ed25519 private key for generating the Tor V3 (.onion) address. If not provided, a random key will be generated and used.', - ), - required: false, - default: null, - patterns: [utils.Patterns.base64], - }), - }), - ), - buttons: [ - { - text: this.i18n.transform('Save')!, - handler: async value => this.save(value), - }, - ], - }, - }) - } - - private async save(form: OnionForm): Promise { - const loader = this.loader.open('Saving').subscribe() - - try { - let onion = form.key - ? await this.api.addTorKey({ key: form.key }) - : await this.api.generateTorKey({}) - onion = `${onion}.onion` - - if (this.interface.packageId) { - await this.api.pkgAddOnion({ - onion, - package: this.interface.packageId(), - host: this.interface.value().addressInfo.hostId, - }) - } else { - await this.api.serverAddOnion({ onion }) - } - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } -} diff --git a/web/projects/ui/src/app/routes/portal/components/table.component.ts b/web/projects/ui/src/app/routes/portal/components/table.component.ts index bbec56f15..5516ed73e 100644 --- a/web/projects/ui/src/app/routes/portal/components/table.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/table.component.ts @@ -24,5 +24,5 @@ import { i18nKey, i18nPipe } from '@start9labs/shared' imports: [i18nPipe], }) export class TableComponent { - readonly appTable = input.required>() + readonly appTable = input.required>() } diff --git a/web/projects/ui/src/app/routes/portal/components/tabs.component.ts b/web/projects/ui/src/app/routes/portal/components/tabs.component.ts index 9171b29b0..20d965fc2 100644 --- a/web/projects/ui/src/app/routes/portal/components/tabs.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/tabs.component.ts @@ -14,7 +14,7 @@ import { TuiBadgeNotification } from '@taiga-ui/kit' import { BadgeService } from 'src/app/services/badge.service' import { getMenu } from 'src/app/utils/system-utilities' -const FILTER = ['/services', '/system', '/marketplace'] +const FILTER = ['services', 'system', 'marketplace'] @Component({ selector: 'app-tabs', @@ -133,7 +133,7 @@ export class TabsComponent { ) more(content: TemplateRef) { - this.dialogs.open(content, { label: 'Start OS' }).subscribe({ + this.dialogs.open(content, { label: 'StartOS' }).subscribe({ complete: () => this.update(), }) } diff --git a/web/projects/ui/src/app/routes/portal/routes/backups/modals/edit.component.ts b/web/projects/ui/src/app/routes/portal/routes/backups/modals/edit.component.ts index 840eca2c4..fc55acf35 100644 --- a/web/projects/ui/src/app/routes/portal/routes/backups/modals/edit.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/backups/modals/edit.component.ts @@ -2,13 +2,13 @@ import { Component, inject } from '@angular/core' import { toSignal } from '@angular/core/rxjs-interop' import { FormsModule } from '@angular/forms' import { ErrorService, LoadingService } from '@start9labs/shared' -import { TuiButton, TuiDialogContext, TuiDialogService } from '@taiga-ui/core' -import { TuiBadge, TuiSwitch } from '@taiga-ui/kit' import { - TuiInputModule, - TuiInputNumberModule, - TuiWrapperModule, -} from '@taiga-ui/legacy' + TuiButton, + TuiDialogContext, + TuiDialogService, + TuiTextfield, +} from '@taiga-ui/core' +import { TuiBadge, TuiSwitch } from '@taiga-ui/kit' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { from, map } from 'rxjs' import { BackupJob, BackupTarget } from 'src/app/services/api/api.types' @@ -21,10 +21,15 @@ import { TARGET, TARGET_CREATE } from './target.component' @Component({ template: `
- - Job Name - - + + + + - - Schedule - - + + + + @if (job.cron | toHumanCron; as human) {
{{ human.message }}
} @@ -98,10 +108,8 @@ import { TARGET, TARGET_CREATE } from './target.component' `, imports: [ FormsModule, - TuiInputModule, - TuiInputNumberModule, + TuiTextfield, TuiSwitch, - TuiWrapperModule, TuiButton, TuiBadge, ToHumanCronPipe, diff --git a/web/projects/ui/src/app/routes/portal/routes/backups/modals/jobs.component.ts b/web/projects/ui/src/app/routes/portal/routes/backups/modals/jobs.component.ts index 84be0326b..72c67e8d9 100644 --- a/web/projects/ui/src/app/routes/portal/routes/backups/modals/jobs.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/backups/modals/jobs.component.ts @@ -26,7 +26,7 @@ import { DocsLinkDirective } from 'projects/shared/src/public-api' Scheduling automatic backups is an excellent way to ensure your StartOS data is safely backed up. StartOS will issue a notification whenever one of your scheduled backups succeeds or fails. - View instructions + View instructions

Saved Jobs diff --git a/web/projects/ui/src/app/routes/portal/routes/backups/modals/targets.component.ts b/web/projects/ui/src/app/routes/portal/routes/backups/modals/targets.component.ts index c390247b7..07f96b917 100644 --- a/web/projects/ui/src/app/routes/portal/routes/backups/modals/targets.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/backups/modals/targets.component.ts @@ -31,7 +31,7 @@ import { DocsLinkDirective } from 'projects/shared/src/public-api' backups. They can be physical drives plugged into your server, shared folders on your Local Area Network (LAN), or third party clouds such as Dropbox or Google Drive. - View instructions + View instructions

Unknown Physical Drives diff --git a/web/projects/ui/src/app/routes/portal/routes/logs/logs.routes.ts b/web/projects/ui/src/app/routes/portal/routes/logs/logs.routes.ts index 281683fcd..c42623ea9 100644 --- a/web/projects/ui/src/app/routes/portal/routes/logs/logs.routes.ts +++ b/web/projects/ui/src/app/routes/portal/routes/logs/logs.routes.ts @@ -13,10 +13,6 @@ export const ROUTES: Routes = [ path: 'os', loadComponent: () => import('./routes/os.component'), }, - { - path: 'tor', - loadComponent: () => import('./routes/tor.component'), - }, ] export default ROUTES diff --git a/web/projects/ui/src/app/routes/portal/routes/logs/routes/outlet.component.ts b/web/projects/ui/src/app/routes/portal/routes/logs/routes/outlet.component.ts index d04a2047f..524a997da 100644 --- a/web/projects/ui/src/app/routes/portal/routes/logs/routes/outlet.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/logs/routes/outlet.component.ts @@ -85,11 +85,5 @@ export default class SystemLogsComponent { subtitle: 'Diagnostics for drivers and other kernel processes', icon: '@tui.square-chevron-right', }, - { - link: 'tor', - title: 'Tor Logs', - subtitle: 'Diagnostic logs for the Tor daemon on StartOS', - icon: '@tui.globe', - }, ] as const } diff --git a/web/projects/ui/src/app/routes/portal/routes/logs/routes/tor.component.ts b/web/projects/ui/src/app/routes/portal/routes/logs/routes/tor.component.ts deleted file mode 100644 index e03e091b6..000000000 --- a/web/projects/ui/src/app/routes/portal/routes/logs/routes/tor.component.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ChangeDetectionStrategy, Component, inject } from '@angular/core' -import { i18nPipe } from '@start9labs/shared' -import { LogsComponent } from 'src/app/routes/portal/components/logs/logs.component' -import { LogsHeaderComponent } from 'src/app/routes/portal/routes/logs/components/header.component' -import { RR } from 'src/app/services/api/api.types' -import { ApiService } from 'src/app/services/api/embassy-api.service' - -@Component({ - template: ` - - {{ 'Diagnostic logs for the Tor daemon on StartOS' | i18n }} - - - `, - styles: ` - :host { - padding: 1rem; - } - `, - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [LogsComponent, LogsHeaderComponent, i18nPipe], - host: { class: 'g-page' }, -}) -export default class SystemOSComponent { - private readonly api = inject(ApiService) - - protected readonly follow = (params: RR.FollowServerLogsReq) => - this.api.followTorLogs(params) - - protected readonly fetch = (params: RR.GetServerLogsReq) => - this.api.getTorLogs(params) -} diff --git a/web/projects/ui/src/app/routes/portal/routes/marketplace/components/controls.component.ts b/web/projects/ui/src/app/routes/portal/routes/marketplace/components/controls.component.ts index bafc87c23..a72f53feb 100644 --- a/web/projects/ui/src/app/routes/portal/routes/marketplace/components/controls.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/marketplace/components/controls.component.ts @@ -221,8 +221,8 @@ export class MarketplaceControlsComponent { const loader = this.loader.open('Starting upload').subscribe() try { - const { upload } = await this.api.sideloadPackage() - this.api.uploadPackage(upload, file).catch(console.error) + const res = await this.api.sideloadPackage() + this.api.uploadFile(res.upload, file).catch(console.error) } catch (e: any) { this.errorService.handleError(e) } finally { diff --git a/web/projects/ui/src/app/routes/portal/routes/marketplace/components/tile.component.ts b/web/projects/ui/src/app/routes/portal/routes/marketplace/components/tile.component.ts index f6669a321..617fdf98e 100644 --- a/web/projects/ui/src/app/routes/portal/routes/marketplace/components/tile.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/marketplace/components/tile.component.ts @@ -52,7 +52,7 @@ import { MarketplaceSidebarService } from '../services/sidebar.service' :host { cursor: pointer; - animation: animateIn 400ms calc(var(--animation-order) * 200ms) both; + animation: animateIn 400ms calc(var(--animation-order) * 50ms) both; } tui-drawer { diff --git a/web/projects/ui/src/app/routes/portal/routes/marketplace/modals/preview.component.ts b/web/projects/ui/src/app/routes/portal/routes/marketplace/modals/preview.component.ts index 3f4be59e0..18ccf434d 100644 --- a/web/projects/ui/src/app/routes/portal/routes/marketplace/modals/preview.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/marketplace/modals/preview.component.ts @@ -82,7 +82,7 @@ import { MarketplaceControlsComponent } from '../components/controls.component' .outer-container { display: flex; flex-direction: column; - height: calc(100vh - var(--portal-header-height) - var(--bumper)); + height: calc(100dvh - var(--portal-header-height) - var(--bumper)); } .inner-container { @@ -177,7 +177,8 @@ export class MarketplacePreviewComponent { ) open(id: string) { - this.router.navigate([], { queryParams: { id } }) + // @TODO re-engage when button can link to root with search QP + // this.router.navigate([], { queryParams: { id } }) } onStatic() { diff --git a/web/projects/ui/src/app/routes/portal/routes/marketplace/services/alerts.service.ts b/web/projects/ui/src/app/routes/portal/routes/marketplace/services/alerts.service.ts index 3ef3cacb1..d08eb4001 100644 --- a/web/projects/ui/src/app/routes/portal/routes/marketplace/services/alerts.service.ts +++ b/web/projects/ui/src/app/routes/portal/routes/marketplace/services/alerts.service.ts @@ -25,7 +25,7 @@ export class MarketplaceAlertsService { return new Promise(async resolve => { this.dialog - .openConfirm({ + .openConfirm({ label: 'Warning', size: 's', data: { @@ -48,7 +48,7 @@ export class MarketplaceAlertsService { return new Promise(async resolve => { this.dialog - .openConfirm({ + .openConfirm({ label: 'Warning', size: 's', data: { @@ -68,7 +68,7 @@ export class MarketplaceAlertsService { (!!content && new Promise(resolve => { this.dialog - .openConfirm({ + .openConfirm({ label: 'Alert', size: 's', data: { diff --git a/web/projects/ui/src/app/routes/portal/routes/metrics/time.component.ts b/web/projects/ui/src/app/routes/portal/routes/metrics/time.component.ts index 00d97e853..73cae2664 100644 --- a/web/projects/ui/src/app/routes/portal/routes/metrics/time.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/metrics/time.component.ts @@ -49,7 +49,8 @@ import { TimeService } from 'src/app/services/time.service' docsLink iconEnd="@tui.external-link" appearance="" - href="/help/common-issues.html#clock-sync-failure" + path="/help/common-issues.html" + fragment="#clock-sync-failure" [pseudo]="true" [textContent]="'the docs' | i18n" > diff --git a/web/projects/ui/src/app/routes/portal/routes/notifications/item.component.ts b/web/projects/ui/src/app/routes/portal/routes/notifications/item.component.ts index 3917410b7..68e005598 100644 --- a/web/projects/ui/src/app/routes/portal/routes/notifications/item.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/notifications/item.component.ts @@ -87,6 +87,7 @@ import { i18nPipe } from '@start9labs/shared' tui-icon { vertical-align: text-top; + align-self: center; } button { diff --git a/web/projects/ui/src/app/routes/portal/routes/notifications/table.component.ts b/web/projects/ui/src/app/routes/portal/routes/notifications/table.component.ts index a8c1fb633..f6a41a8a6 100644 --- a/web/projects/ui/src/app/routes/portal/routes/notifications/table.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/notifications/table.component.ts @@ -41,7 +41,10 @@ import { i18nPipe } from '@start9labs/shared' @for (notification of notifications; track $index) { @@ -119,13 +122,13 @@ export class NotificationsTableComponent implements OnChanges { this.selected.set((selected && this.notifications) || []) } - handleToggle(notification: ServerNotification) { - const selected = this.selected() + onToggle(notification: ServerNotification, event?: Event) { + event?.stopPropagation() - if (selected.some(s => s.id === notification.id)) { - this.selected.set(selected.filter(s => s.id !== notification.id)) + if (this.selected().some(s => s.id === notification.id)) { + this.selected.update(value => value.filter(s => s.id !== notification.id)) } else { - this.selected.set([...selected, notification]) + this.selected.update(value => [...value, notification]) } } } diff --git a/web/projects/ui/src/app/routes/portal/routes/services/components/dependencies.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/components/dependencies.component.ts index 66a4caa24..1af458564 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/components/dependencies.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/components/dependencies.component.ts @@ -1,5 +1,5 @@ import { KeyValuePipe } from '@angular/common' -import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { ChangeDetectionStrategy, Component, input } from '@angular/core' import { RouterLink } from '@angular/router' import { i18nKey, i18nPipe } from '@start9labs/shared' import { TuiIcon, TuiTitle } from '@taiga-ui/core' @@ -8,26 +8,39 @@ import { TuiCell } from '@taiga-ui/layout' import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component' import { PkgDependencyErrors } from 'src/app/services/dep-error.service' import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { ToManifestPipe } from '../../../pipes/to-manifest' @Component({ selector: 'service-dependencies', template: `
{{ 'Dependencies' | i18n }}
- @for (d of pkg.currentDependencies | keyvalue; track $index) { + + @let services = this.services(); + + @for (d of pkg().currentDependencies | keyvalue; track $index) { + - {{ d.value.title || d.key }} + {{ + services[d.key] + ? (services[d.key]! | toManifest).title + : d.value.title || d.key + }} @if (getError(d.key); as error) { {{ error | i18n }} @@ -69,20 +82,16 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model' TuiIcon, PlaceholderComponent, i18nPipe, + ToManifestPipe, ], }) export class ServiceDependenciesComponent { - @Input({ required: true }) - pkg!: PackageDataEntry - - @Input({ required: true }) - services: Record = {} - - @Input({ required: true }) - errors: PkgDependencyErrors = {} + pkg = input.required() + services = input.required>() + errors = input.required() getError(id: string): i18nKey | undefined { - const depError = this.errors[id] + const depError = this.errors()[id] if (!depError) { return undefined @@ -95,7 +104,7 @@ export class ServiceDependenciesComponent { return 'Incorrect version' case 'notRunning': return 'Not running' - case 'actionRequired': + case 'taskRequired': return 'Task Required' case 'healthChecksFailed': return 'Required health check not passing' @@ -107,7 +116,7 @@ export class ServiceDependenciesComponent { } getHealthCheckName(id: string) { - const depError = this.errors[id] + const depError = this.errors()[id] return depError?.type === 'healthChecksFailed' ? depError.check.name : undefined diff --git a/web/projects/ui/src/app/routes/portal/routes/services/components/health-checks.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/components/health-checks.component.ts index 70a457a4d..4697c3cc0 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/components/health-checks.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/components/health-checks.component.ts @@ -31,7 +31,7 @@ import { i18nPipe } from '@start9labs/shared' styles: ` :host { min-height: 12rem; - grid-column: span 3; + grid-column: span 4; } `, host: { class: 'g-card' }, diff --git a/web/projects/ui/src/app/routes/portal/routes/services/components/interface-item.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/components/interface-item.component.ts index a29f2f268..0569c8b56 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/components/interface-item.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/components/interface-item.component.ts @@ -3,54 +3,48 @@ import { Component, inject, Input, - DOCUMENT, } from '@angular/core' -import { RouterLink } from '@angular/router' import { T } from '@start9labs/start-sdk' -import { TuiButton, TuiIcon } from '@taiga-ui/core' +import { TuiButton } from '@taiga-ui/core' import { TuiBadge } from '@taiga-ui/kit' -import { ConfigService } from 'src/app/services/config.service' +import { InterfaceService } from 'src/app/routes/portal/components/interfaces/interface.service' import { PackageDataEntry } from 'src/app/services/patch-db/data-model' @Component({ selector: 'tr[serviceInterface]', template: ` - - {{ info.name }} - + {{ info.type }} - - @if (info.public) { - - } @else { - - } - - + {{ info.description }} @if (info.type === 'ui') { - + target="_blank" + rel="noopener noreferrer" + [attr.href]="disabled ? null : href" + (click.stop)="(0)" + > } - `, styles: ` - strong { + :host { + clip-path: inset(0 round 0.75rem); + cursor: pointer; + + &:hover { + background: var(--tui-background-neutral-1); + } + } + + td:first-child { white-space: nowrap; } @@ -64,7 +58,7 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model' } td:last-child { - grid-area: 3 / span 4; + grid-area: 1 / 3 / span 2 / 3; white-space: nowrap; text-align: right; flex-direction: row-reverse; @@ -74,29 +68,24 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model' :host-context(tui-root._mobile) { display: grid; - grid-template-columns: repeat(3, min-content) 1fr; + grid-template-columns: min-content; align-items: center; padding: 1rem 0.5rem; gap: 0.5rem; td { - display: flex; padding: 0; } } `, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [TuiButton, TuiBadge, TuiIcon, RouterLink], + imports: [TuiButton, TuiBadge], }) export class ServiceInterfaceItemComponent { - private readonly config = inject(ConfigService) - private readonly document = inject(DOCUMENT) + private readonly interfaceService = inject(InterfaceService) @Input({ required: true }) - info!: T.ServiceInterface & { - public: boolean - routerLink: string - } + info!: T.ServiceInterface @Input({ required: true }) pkg!: PackageDataEntry @@ -116,10 +105,10 @@ export class ServiceInterfaceItemComponent { } get href() { - return this.config.launchableAddress(this.info, this.pkg.hosts) - } + const host = this.pkg.hosts[this.info.addressInfo.hostId] - openUI() { - this.document.defaultView?.open(this.href, '_blank', 'noreferrer') + return host + ? this.interfaceService.launchableAddress(this.info, host) + : null } } diff --git a/web/projects/ui/src/app/routes/portal/routes/services/components/interfaces.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/components/interfaces.component.ts index e64792fee..56cc9223b 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/components/interfaces.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/components/interfaces.component.ts @@ -2,27 +2,25 @@ import { ChangeDetectionStrategy, Component, computed, - inject, input, } from '@angular/core' +import { RouterLink } from '@angular/router' import { TuiTable } from '@taiga-ui/addon-table' import { tuiDefaultSort } from '@taiga-ui/cdk' -import { ConfigService } from 'src/app/services/config.service' import { PackageDataEntry } from 'src/app/services/patch-db/data-model' -import { getAddresses } from '../../../components/interfaces/interface.utils' import { ServiceInterfaceItemComponent } from './interface-item.component' import { i18nPipe } from '@start9labs/shared' +import { PlaceholderComponent } from '../../../components/placeholder.component' @Component({ selector: 'service-interfaces', template: ` -
{{ 'Interfaces' | i18n }}
+
{{ 'Service Interfaces' | i18n }}
- @@ -30,42 +28,50 @@ import { i18nPipe } from '@start9labs/shared' @for (info of interfaces(); track $index) { + [routerLink]="info.routerLink" + > + + {{ info.name }} + + + } @empty { + + {{ 'No service interfaces' | i18n }} + }
{{ 'Name' | i18n }} {{ 'Type' | i18n }}{{ 'Hosting' | i18n }} {{ 'Description' | i18n }}
`, styles: ` :host { - grid-column: span 6; + grid-column: span 7; } `, host: { class: 'g-card' }, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [ServiceInterfaceItemComponent, TuiTable, i18nPipe], + imports: [ + ServiceInterfaceItemComponent, + TuiTable, + i18nPipe, + PlaceholderComponent, + RouterLink, + ], }) export class ServiceInterfacesComponent { - private readonly config = inject(ConfigService) - readonly pkg = input.required() readonly disabled = input(false) - readonly interfaces = computed(({ serviceInterfaces, hosts } = this.pkg()) => + readonly interfaces = computed(({ serviceInterfaces } = this.pkg()) => Object.entries(serviceInterfaces) .sort((a, b) => tuiDefaultSort(a[1], b[1])) .map(([id, value]) => { - const host = hosts[value.addressInfo.hostId] - const port = value.addressInfo.internalPort - return { ...value, - addSsl: host?.bindings[port]?.options.addSsl, - public: !!host?.bindings[port]?.net.public, - addresses: host ? getAddresses(value, host, this.config) : {}, routerLink: `./interface/${id}`, } }), diff --git a/web/projects/ui/src/app/routes/portal/routes/services/components/progress.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/components/progress.component.ts index 0c3bc6a02..2e860ce9e 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/components/progress.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/components/progress.component.ts @@ -50,7 +50,7 @@ import { getManifest } from 'src/app/utils/get-package-data' size="m" [max]="100" [class.g-positive]="phase.progress === true" - [value]="isIndeterminate(phase.progress) ? undefined : percent" + [attr.value]="isIndeterminate(phase.progress) ? undefined : percent" >

} diff --git a/web/projects/ui/src/app/routes/portal/routes/services/components/status.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/components/status.component.ts index fbdcf30d0..499302b33 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/components/status.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/components/status.component.ts @@ -43,7 +43,7 @@ import { `, styles: ` :host { - grid-column: span 2; + grid-column: span 3; min-height: 12rem; } @@ -78,14 +78,14 @@ import { :host-context(tui-root._mobile) { :host { - min-height: 0; - } + min-height: 0; + } - div { - display: grid; - grid-template-columns: 1fr max-content; - padding: 0.5rem 0; - } + div { + display: grid; + grid-template-columns: 1fr max-content; + padding: 0.5rem 0; + } h3 { text-align: left; diff --git a/web/projects/ui/src/app/routes/portal/routes/services/components/task.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/components/task.component.ts index d0e81b3bc..c36547b57 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/components/task.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/components/task.component.ts @@ -15,6 +15,7 @@ import { T } from '@start9labs/start-sdk' import { TuiButton } from '@taiga-ui/core' import { TuiAvatar, TuiFade } from '@taiga-ui/kit' import { filter } from 'rxjs' +import { ServiceTasksComponent } from 'src/app/routes/portal/routes/services/components/tasks.component' import { ActionService } from 'src/app/services/action.service' import { ApiService } from 'src/app/services/api/embassy-api.service' import { PackageDataEntry } from 'src/app/services/patch-db/data-model' @@ -23,49 +24,52 @@ import { getManifest } from 'src/app/utils/get-package-data' @Component({ selector: 'tr[task]', template: ` - - - {{ pkgTitle() }} + + + + + {{ title() || fallback()?.title }} - - {{ pkg()?.actions?.[task().actionId]?.name }} + + + {{ + pkg()?.actions?.[task().actionId]?.name || ('Not installed' | i18n) + }} + - + @if (task().severity === 'critical') { - - {{ 'Required' | i18n }} - + {{ 'Required' | i18n }} } @else if (task().severity === 'important') { - - {{ 'Recommended' | i18n }} - + {{ 'Recommended' | i18n }} } @else { - - {{ 'Optional' | i18n }} - + {{ 'Optional' | i18n }} } - + {{ task().reason || ('No reason provided' | i18n) }} - + @if (task().severity !== 'critical') { + > + {{ 'Dismiss' | i18n }} + } + > + {{ 'Run' | i18n }} + `, styles: ` @@ -76,24 +80,25 @@ import { getManifest } from 'src/app/utils/get-package-data' } td:last-child { - grid-area: 3 / span 4; white-space: nowrap; text-align: right; - flex-direction: row-reverse; - justify-content: flex-end; - gap: 0.5rem; + justify-content: end; } span { margin-inline-start: 0.5rem; + line-height: 1.5rem; vertical-align: middle; } :host-context(tui-root._mobile) { display: grid; - align-items: center; - padding: 1rem 0rem 1rem 0.5rem; - gap: 0.5rem; + grid-template-columns: 1fr min-content; + padding: 1rem 0.5rem; + + .row { + margin-bottom: 1rem; + } td { display: flex; @@ -102,6 +107,7 @@ import { getManifest } from 'src/app/utils/get-package-data' } } `, + host: { '[style.opacity]': 'pkg() ? null : "var(--tui-disabled-opacity)"' }, changeDetection: ChangeDetectionStrategy.OnPush, imports: [TuiButton, TuiAvatar, i18nPipe, TuiFade], }) @@ -111,34 +117,28 @@ export class ServiceTaskComponent { private readonly api = inject(ApiService) private readonly errorService = inject(ErrorService) private readonly loader = inject(LoadingService) + private readonly tasks = inject(ServiceTasksComponent) readonly task = input.required() readonly services = input.required>() readonly pkg = computed(() => this.services()[this.task().packageId]) - readonly pkgTitle = computed( - (pkg = this.pkg()) => pkg && getManifest(pkg).title, + readonly title = computed((pkg = this.pkg()) => pkg && getManifest(pkg).title) + + readonly fallback = computed( + () => this.tasks.pkg().currentDependencies[this.task().packageId], ) async dismiss() { + const { packageId, replayId } = this.task() + this.dialog - .openConfirm({ - label: 'Confirm', - size: 's', - data: { - content: 'Are you sure you want to dismiss this task?', - yes: 'Dismiss', - no: 'Cancel', - }, - }) + .openConfirm(DISMISS) .pipe(filter(Boolean)) .subscribe(async () => { const loader = this.loader.open().subscribe() try { - await this.api.clearTask({ - packageId: this.task().packageId, - replayId: this.task().replayId, - }) + await this.api.clearTask({ packageId, replayId }) } catch (e: any) { this.errorService.handleError(e) } finally { @@ -148,26 +148,31 @@ export class ServiceTaskComponent { } async handle() { - const title = this.pkgTitle() + const title = this.title() const pkg = this.pkg() const metadata = pkg?.actions[this.task().actionId] - if (!title || !pkg || !metadata) { - return + if (title && pkg && metadata) { + this.actionService.present({ + pkgInfo: { + id: this.task().packageId, + title, + mainStatus: pkg.status.main, + icon: pkg.icon, + }, + actionInfo: { id: this.task().actionId, metadata }, + requestInfo: this.task(), + }) } - - this.actionService.present({ - pkgInfo: { - id: this.task().packageId, - title, - mainStatus: pkg.status.main, - icon: pkg.icon, - }, - actionInfo: { - id: this.task().actionId, - metadata, - }, - requestInfo: this.task(), - }) } } + +const DISMISS = { + label: 'Confirm', + size: 's', + data: { + content: 'Are you sure you want to dismiss this task?', + yes: 'Dismiss', + no: 'Cancel', + }, +} as const diff --git a/web/projects/ui/src/app/routes/portal/routes/services/components/tasks.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/components/tasks.component.ts index 1cabb9c16..fecf255da 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/components/tasks.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/components/tasks.component.ts @@ -20,7 +20,7 @@ import { i18nPipe } from '@start9labs/shared' {{ 'Service' | i18n }} {{ 'Action' }} {{ 'Severity' }} - {{ 'Description' | i18n }} + {{ 'Reason' | i18n }} @@ -39,7 +39,7 @@ import { i18nPipe } from '@start9labs/shared' styles: ` :host { min-height: 12rem; - grid-column: span 6; + grid-column: span 10; } `, host: { class: 'g-card' }, @@ -56,11 +56,7 @@ export class ServiceTasksComponent { ...entry, task: { ...entry.task, replayId }, })) - .filter( - t => - this.services()[t.task.packageId]?.actions[t.task.actionId] && - t.active, - ) + .filter(t => t.active) .sort((a, b) => a.task.severity.localeCompare(b.task.severity)), ) } diff --git a/web/projects/ui/src/app/routes/portal/routes/services/components/uptime.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/components/uptime.component.ts index 39e611d17..6f11fc799 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/components/uptime.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/components/uptime.component.ts @@ -32,7 +32,7 @@ import { distinctUntilChanged } from 'rxjs/operators' styles: [ ` :host { - grid-column: span 4; + grid-column: span 3; } h3 { @@ -55,11 +55,13 @@ import { distinctUntilChanged } from 'rxjs/operators' text-transform: uppercase; color: var(--tui-text-secondary); font: var(--tui-font-text-ui-xs); + font-size: min(4cqw, 0.75rem); } label { display: block; font-size: min(6vw, 2.5rem); + font-size: min(20cqw, 2.5rem); margin: 1rem 0; color: var(--tui-text-primary); } diff --git a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/dashboard.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/dashboard.component.ts index bf29d6979..dd838391c 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/dashboard.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/dashboard.component.ts @@ -1,17 +1,22 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { toSignal } from '@angular/core/rxjs-interop' import { RouterLink } from '@angular/router' +import { i18nPipe } from '@start9labs/shared' import { TuiComparator, TuiTable } from '@taiga-ui/addon-table' import { TuiButton, TuiLoader } from '@taiga-ui/core' +import { PatchDB } from 'patch-db-client' +import { map, shareReplay } from 'rxjs' import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest' import { DepErrorService } from 'src/app/services/dep-error.service' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { + DataModel, + PackageDataEntry, +} from 'src/app/services/patch-db/data-model' import { getInstalledPrimaryStatus } from 'src/app/services/pkg-status-rendering.service' import { TitleDirective } from 'src/app/services/title.service' import { getManifest } from 'src/app/utils/get-package-data' + import { ServiceComponent } from './service.component' -import { ServicesService } from './services.service' -import { i18nPipe } from '@start9labs/shared' @Component({ template: ` @@ -27,6 +32,9 @@ import { i18nPipe } from '@start9labs/shared' {{ 'Name' | i18n }} + + {{ 'Status' | i18n }} + {{ 'Version' | i18n }} {{ 'Uptime' | i18n }} - - {{ 'Status' | i18n }} - @@ -123,13 +128,17 @@ import { i18nPipe } from '@start9labs/shared' changeDetection: ChangeDetectionStrategy.OnPush, }) export default class DashboardComponent { - readonly services = toSignal(inject(ServicesService)) readonly errors = toSignal(inject(DepErrorService).depErrors$) + readonly services = toSignal( + inject>(PatchDB) + .watch$('packageData') + .pipe( + map(pkgs => Object.values(pkgs).sort(byName)), + shareReplay(1), + ), + ) - readonly name: TuiComparator = (a, b) => - getManifest(b).title.toLowerCase() > getManifest(a).title.toLowerCase() - ? -1 - : 1 + readonly name: TuiComparator = byName readonly status: TuiComparator = (a, b) => getInstalledPrimaryStatus(b) > getInstalledPrimaryStatus(a) ? -1 : 1 @@ -139,3 +148,9 @@ export default class DashboardComponent { sorter = this.name } + +function byName(a: PackageDataEntry, b: PackageDataEntry) { + return getManifest(b).title.toLowerCase() > getManifest(a).title.toLowerCase() + ? -1 + : 1 +} diff --git a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/service.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/service.component.ts index 965a641fa..5f90890b9 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/service.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/service.component.ts @@ -26,6 +26,12 @@ import { StatusComponent } from './status.component' {{ manifest.title }} + {{ manifest.version }} @if ($any(pkg.status)?.started; as started) { @@ -35,12 +41,6 @@ import { StatusComponent } from './status.component' - } -
{ - private readonly services$ = inject>(PatchDB) - .watch$('packageData') - .pipe( - map(pkgs => - Object.values(pkgs).sort((a, b) => - getManifest(b).title.toLowerCase() > - getManifest(a).title.toLowerCase() - ? -1 - : 1, - ), - ), - shareReplay(1), - ) - - constructor() { - super(subscriber => this.services$.subscribe(subscriber)) - } -} diff --git a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/status.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/status.component.ts index f6fbf26b3..d97e4736e 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/status.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/status.component.ts @@ -52,7 +52,7 @@ export class StatusComponent { const { primary, health } = this.getStatus(this.pkg) return ( !this.hasDepErrors && - primary !== 'actionRequired' && + primary !== 'taskRequired' && primary !== 'error' && health !== 'failure' ) @@ -77,7 +77,7 @@ export class StatusComponent { return 'Running' case 'stopped': return 'Stopped' - case 'actionRequired': + case 'taskRequired': return 'Task Required' case 'updating': return 'Updating' @@ -118,7 +118,7 @@ export class StatusComponent { switch (this.getStatus(this.pkg).primary) { case 'running': return 'var(--tui-status-positive)' - case 'actionRequired': + case 'taskRequired': return 'var(--tui-status-warning)' case 'error': return 'var(--tui-status-negative)' diff --git a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/ui-launch.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/ui-launch.component.ts index 513b47edb..13257af83 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/ui-launch.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/ui-launch.component.ts @@ -9,8 +9,8 @@ import { i18nPipe } from '@start9labs/shared' import { T } from '@start9labs/start-sdk' import { tuiPure } from '@taiga-ui/cdk' import { TuiDataList, TuiDropdown, TuiButton } from '@taiga-ui/core' -import { ConfigService } from 'src/app/services/config.service' import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { InterfaceService } from '../../../components/interfaces/interface.service' @Component({ selector: 'app-ui-launch', @@ -59,7 +59,7 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model' imports: [TuiButton, TuiDropdown, TuiDataList, i18nPipe], }) export class UILaunchComponent { - private readonly config = inject(ConfigService) + private readonly interfaceService = inject(InterfaceService) private readonly document = inject(DOCUMENT) @Input() @@ -86,7 +86,9 @@ export class UILaunchComponent { } getHref(ui: T.ServiceInterface): string { - return this.config.launchableAddress(ui, this.pkg.hosts) + const host = this.pkg.hosts[ui.addressInfo.hostId] + if (!host) return '' + return this.interfaceService.launchableAddress(ui, host) } openUI(ui: T.ServiceInterface) { diff --git a/web/projects/ui/src/app/routes/portal/routes/services/modals/action-input.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/modals/action-input.component.ts index 8c8820d24..b8dbcff71 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/modals/action-input.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/modals/action-input.component.ts @@ -22,12 +22,12 @@ import { ActionButton, FormComponent, } from 'src/app/routes/portal/components/form.component' +import { InvalidService } from 'src/app/routes/portal/components/form/containers/control.directive' import { TaskInfoComponent } from 'src/app/routes/portal/modals/config-dep.component' import { ActionService } from 'src/app/services/action.service' import { ApiService } from 'src/app/services/api/embassy-api.service' import { DataModel } from 'src/app/services/patch-db/data-model' import { getAllPackages, getManifest } from 'src/app/utils/get-package-data' -import { InvalidService } from '../../../components/form/invalid.service' export type PackageActionData = { pkgInfo: { @@ -133,6 +133,7 @@ export class ActionInputModal { readonly warning = this.context.data.actionInfo.metadata.warning readonly pkgInfo = this.context.data.pkgInfo readonly requestInfo = this.context.data.requestInfo + eventId: string | null = null buttons: ActionButton[] = [ { @@ -151,6 +152,7 @@ export class ActionInputModal { ).pipe( map(res => { const originalValue = res.value || {} + this.eventId = res.eventId return { spec: res.spec, @@ -174,7 +176,12 @@ export class ActionInputModal { async execute(input: object) { if (await this.checkConflicts(input)) { - await this.actionService.execute(this.pkgInfo.id, this.actionId, input) + await this.actionService.execute( + this.pkgInfo.id, + this.eventId, + this.actionId, + input, + ) this.context.$implicit.complete() } } @@ -209,7 +216,7 @@ export class ActionInputModal { return firstValueFrom( this.dialog - .openConfirm({ + .openConfirm({ label: 'Warning', data: { content, yes: 'Continue', no: 'Cancel' }, }) diff --git a/web/projects/ui/src/app/routes/portal/routes/services/modals/action-success/action-success-member.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/modals/action-success/action-success-member.component.ts index a08265c66..6f923eb88 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/modals/action-success/action-success-member.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/modals/action-success/action-success-member.component.ts @@ -10,35 +10,26 @@ import { import { FormsModule } from '@angular/forms' import { DialogService, i18nPipe } from '@start9labs/shared' import { T } from '@start9labs/start-sdk' -import { TuiButton, TuiTitle } from '@taiga-ui/core' import { - TuiInputModule, - TuiTextfieldComponent, - TuiTextfieldControllerModule, -} from '@taiga-ui/legacy' -import { QrCodeModule } from 'ng-qrcode' + TuiButton, + TuiTextfield, + TuiTextfieldDirective, + TuiTitle, +} from '@taiga-ui/core' +import { QrCodeComponent } from 'ng-qrcode' @Component({ selector: 'app-action-success-member', template: ` - - {{ member.name }} + + - - @if (member.description) { - - } - @if (member.masked) { +
} diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/acme/acme.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/acme/acme.component.ts deleted file mode 100644 index 0e0f66f98..000000000 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/acme/acme.component.ts +++ /dev/null @@ -1,290 +0,0 @@ -import { ChangeDetectionStrategy, Component, inject } from '@angular/core' -import { toSignal } from '@angular/core/rxjs-interop' -import { RouterLink } from '@angular/router' -import { - DocsLinkDirective, - ErrorService, - i18nPipe, - LoadingService, -} from '@start9labs/shared' -import { ISB, utils } from '@start9labs/start-sdk' -import { TuiButton, TuiLink, TuiLoader, TuiTitle } from '@taiga-ui/core' -import { TuiCell, TuiHeader } from '@taiga-ui/layout' -import { PatchDB } from 'patch-db-client' -import { map } from 'rxjs' -import { FormComponent } from 'src/app/routes/portal/components/form.component' -import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { TitleDirective } from 'src/app/services/title.service' -import { knownACME, toAcmeName } from 'src/app/utils/acme' -import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' - -@Component({ - template: ` - - - {{ 'Back' | i18n }} - - ACME - -
-
-

ACME

-

- {{ - 'Add ACME providers in order to generate SSL (https) certificates for clearnet access.' - | i18n - }} - -

-
-
-
-
- {{ 'Saved Providers' | i18n }} - @if (acme(); as value) { - - } -
- @if (acme(); as value) { - @for (provider of value; track $index) { -
- - {{ toAcmeName(provider.url) }} - - {{ 'Contact' | i18n }}: {{ provider.contactString }} - - - - -
- } @empty { - - {{ 'No saved providers' | i18n }} - - } - } @else { - - } -
- `, - styles: ` - :host { - max-width: 36rem; - } - `, - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [ - TuiButton, - TuiLoader, - TuiCell, - TuiTitle, - TuiHeader, - TuiLink, - RouterLink, - TitleDirective, - i18nPipe, - DocsLinkDirective, - PlaceholderComponent, - ], -}) -export default class SystemAcmeComponent { - private readonly formDialog = inject(FormDialogService) - private readonly loader = inject(LoadingService) - private readonly errorService = inject(ErrorService) - private readonly patch = inject>(PatchDB) - private readonly api = inject(ApiService) - private readonly i18n = inject(i18nPipe) - - acme = toSignal( - this.patch.watch$('serverInfo', 'network', 'acme').pipe( - map(acme => - Object.keys(acme).map(url => { - const contact = - acme[url]?.contact.map(mailto => mailto.replace('mailto:', '')) || - [] - return { - url, - contact, - contactString: contact.join(', '), - } - }), - ), - ), - ) - - toAcmeName = toAcmeName - - async addAcme( - providers: { - url: string - contact: string[] - contactString: string - }[], - ) { - this.formDialog.open(FormComponent, { - label: 'Add ACME Provider', - data: { - spec: await configBuilderToSpec( - this.addAcmeSpec(providers.map(p => p.url)), - ), - buttons: [ - { - text: this.i18n.transform('Save'), - handler: async ( - val: ReturnType['_TYPE'], - ) => { - const providerUrl = - val.provider.selection === 'other' - ? val.provider.value.url - : val.provider.selection - - return this.saveAcme(providerUrl, val.contact) - }, - }, - ], - }, - }) - } - - async editAcme(provider: string, contact: string[]) { - this.formDialog.open(FormComponent, { - label: 'Edit ACME Provider', - data: { - spec: await configBuilderToSpec(this.editAcmeSpec()), - buttons: [ - { - text: this.i18n.transform('Save'), - handler: async ( - val: ReturnType['_TYPE'], - ) => this.saveAcme(provider, val.contact), - }, - ], - value: { contact }, - }, - }) - } - - async removeAcme(provider: string) { - const loader = this.loader.open('Removing').subscribe() - - try { - await this.api.removeAcme({ provider }) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - private async saveAcme(providerUrl: string, contact: string[]) { - const loader = this.loader.open('Saving').subscribe() - - try { - await this.api.initAcme({ - provider: new URL(providerUrl).href, - contact: contact.map(address => `mailto:${address}`), - }) - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } - - private addAcmeSpec(providers: string[]) { - const availableAcme = knownACME.filter( - acme => !providers.includes(acme.url), - ) - - return ISB.InputSpec.of({ - provider: ISB.Value.union({ - name: 'Provider', - default: (availableAcme[0]?.url as any) || 'other', - variants: ISB.Variants.of({ - ...availableAcme.reduce( - (obj, curr) => ({ - ...obj, - [curr.url]: { - name: curr.name, - spec: ISB.InputSpec.of({}), - }, - }), - {}, - ), - other: { - name: 'Other', - spec: ISB.InputSpec.of({ - url: ISB.Value.text({ - name: 'URL', - default: null, - required: true, - inputmode: 'url', - patterns: [utils.Patterns.url], - }), - }), - }, - }), - }), - contact: this.emailListSpec(), - }) - } - - private editAcmeSpec() { - return ISB.InputSpec.of({ - contact: this.emailListSpec(), - }) - } - - private emailListSpec() { - return ISB.Value.list( - ISB.List.text( - { - name: this.i18n.transform('Contact Emails')!, - description: this.i18n.transform( - 'Needed to obtain a certificate from a Certificate Authority', - ), - minLength: 1, - }, - { - inputmode: 'email', - patterns: [utils.Patterns.email], - }, - ), - ) - } -} diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authorities.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authorities.component.ts new file mode 100644 index 000000000..0b56e05de --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authorities.component.ts @@ -0,0 +1,63 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core' +import { RouterLink } from '@angular/router' +import { DocsLinkDirective, i18nPipe } from '@start9labs/shared' +import { TuiButton } from '@taiga-ui/core' +import { TitleDirective } from 'src/app/services/title.service' +import { AuthorityService } from './authority.service' +import { AuthoritiesTableComponent } from './table.component' + +@Component({ + template: ` + + + {{ 'Back' | i18n }} + + {{ 'Certificate Authorities' | i18n }} + +
+
+ {{ 'Certificate Authorities' | i18n }} + + {{ 'Documentation' | i18n }} + + @if (authorityService.authorities(); as authorities) { + + } +
+ +
+ `, + styles: ` + :host { + max-width: 64rem; + } + `, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + TuiButton, + RouterLink, + TitleDirective, + i18nPipe, + DocsLinkDirective, + AuthoritiesTableComponent, + ], + providers: [AuthorityService], +}) +export default class SystemAuthoritiesComponent { + protected readonly authorityService = inject(AuthorityService) +} diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authority.service.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authority.service.ts new file mode 100644 index 000000000..c24b4b286 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authority.service.ts @@ -0,0 +1,181 @@ +import { inject, Injectable } from '@angular/core' +import { + DialogService, + ErrorService, + i18nPipe, + LoadingService, +} from '@start9labs/shared' +import { toSignal } from '@angular/core/rxjs-interop' +import { ISB, utils } from '@start9labs/start-sdk' +import { filter, map } from 'rxjs' +import { FormComponent } from 'src/app/routes/portal/components/form.component' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { FormDialogService } from 'src/app/services/form-dialog.service' +import { PatchDB } from 'patch-db-client' +import { DataModel } from 'src/app/services/patch-db/data-model' +import { knownAuthorities, toAuthorityName } from 'src/app/utils/acme' +import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' + +export type Authority = { + name: string + url?: string | null + contact?: readonly string[] | null +} + +export type RemoteAuthority = Authority & { url: string } + +@Injectable() +export class AuthorityService { + private readonly patch = inject>(PatchDB) + private readonly loader = inject(LoadingService) + private readonly errorService = inject(ErrorService) + private readonly api = inject(ApiService) + private readonly formDialog = inject(FormDialogService) + private readonly i18n = inject(i18nPipe) + private readonly dialog = inject(DialogService) + + readonly authorities = toSignal( + this.patch.watch$('serverInfo', 'network', 'acme').pipe( + map(acme => [ + ...Object.keys(acme).map(url => ({ + url, + name: toAuthorityName(url), + contact: + acme[url]?.contact.map(mailto => mailto.replace('mailto:', '')) || + null, + })), + ]), + ), + ) + + async add(authorities: Authority[]) { + const availableAuthorities = knownAuthorities.filter( + ca => !authorities.map(a => a.url).includes(ca.url), + ) + + const addSpec = ISB.InputSpec.of({ + provider: ISB.Value.union({ + name: this.i18n.transform('Provider'), + default: (availableAuthorities[0]?.url as any) || 'other', + variants: ISB.Variants.of({ + ...availableAuthorities.reduce( + (obj, curr) => ({ + ...obj, + [curr.url]: { + name: curr.name, + spec: ISB.InputSpec.of({}), + }, + }), + {}, + ), + other: { + name: this.i18n.transform('Other'), + spec: ISB.InputSpec.of({ + url: ISB.Value.text({ + name: 'URL', + default: null, + required: true, + inputmode: 'url', + patterns: [utils.Patterns.url], + }), + }), + }, + }), + }), + contact: this.emailListSpec(), + }) + + this.formDialog.open(FormComponent, { + label: 'Add Certificate Authority', + data: { + spec: await configBuilderToSpec(addSpec), + buttons: [ + { + text: this.i18n.transform('Save'), + handler: async (val: typeof addSpec._TYPE) => { + const providerUrl = + val.provider.selection === 'other' + ? val.provider.value.url + : val.provider.selection + + return this.save(providerUrl, val.contact) + }, + }, + ], + }, + }) + } + + async edit({ url, contact }: RemoteAuthority) { + const editSpec = ISB.InputSpec.of({ + contact: this.emailListSpec(), + }) + + this.formDialog.open(FormComponent, { + label: 'Edit contact info', + data: { + spec: await configBuilderToSpec(editSpec), + buttons: [ + { + text: this.i18n.transform('Save'), + handler: async (val: typeof editSpec._TYPE) => + this.save(url, val.contact), + }, + ], + value: { contact }, + }, + }) + } + + remove({ url }: RemoteAuthority) { + this.dialog + .openConfirm({ label: 'Are you sure?', size: 's' }) + .pipe(filter(Boolean)) + .subscribe(async () => { + const loader = this.loader.open('Removing').subscribe() + + try { + await this.api.removeAcme({ provider: url }) + } catch (e: any) { + this.errorService.handleError(e) + } finally { + loader.unsubscribe() + } + }) + } + + private async save(url: string, contact: readonly string[]) { + const loader = this.loader.open('Saving').subscribe() + + try { + await this.api.initAcme({ + provider: new URL(url).href, + contact: contact.map(address => `mailto:${address}`), + }) + return true + } catch (e: any) { + this.errorService.handleError(e) + return false + } finally { + loader.unsubscribe() + } + } + + private emailListSpec() { + return ISB.Value.list( + ISB.List.text( + { + name: this.i18n.transform('Contact Emails')!, + description: this.i18n.transform( + 'Needed to obtain a certificate from a Certificate Authority', + ), + minLength: 1, + }, + { + inputmode: 'email', + patterns: [utils.Patterns.email], + }, + ), + ) + } +} diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/item.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/item.component.ts new file mode 100644 index 000000000..ab5628887 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/item.component.ts @@ -0,0 +1,99 @@ +import { + ChangeDetectionStrategy, + Component, + inject, + input, +} from '@angular/core' +import { i18nPipe } from '@start9labs/shared' +import { + TuiButton, + TuiDataList, + TuiDropdown, + TuiTextfield, +} from '@taiga-ui/core' +import { Authority, AuthorityService } from './authority.service' + +@Component({ + selector: 'tr[authority]', + template: ` + @if (authority(); as authority) { + {{ authority.name }} + {{ authority.url || '-' }} + {{ authority.contact?.join(', ') || '-' }} + + + + + + + } @else { + + + {{ 'Download' | i18n }} + + + } + + + + } + `, + styles: ` + td:last-child { + grid-area: 1 / 2 / 4; + align-self: center; + text-align: right; + } + + :host-context(tui-root._mobile) { + grid-template-columns: 1fr min-content; + + .hidden { + display: none; + } + } + `, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [TuiButton, i18nPipe, TuiDropdown, TuiDataList, TuiTextfield], +}) +export class AuthorityItemComponent { + protected readonly service = inject(AuthorityService) + + readonly authority = input.required() + + open = false +} diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/table.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/table.component.ts new file mode 100644 index 000000000..abcf3ee24 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/table.component.ts @@ -0,0 +1,27 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core' +import { i18nPipe } from '@start9labs/shared' +import { TuiSkeleton } from '@taiga-ui/kit' +import { TableComponent } from 'src/app/routes/portal/components/table.component' +import { AuthorityItemComponent } from './item.component' +import { AuthorityService } from './authority.service' + +@Component({ + selector: 'authorities-table', + template: ` + + + @for (authority of authorityService.authorities(); track $index) { + + } @empty { + + } +
+
{{ 'Loading' | i18n }}
+
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [TuiSkeleton, i18nPipe, TableComponent, AuthorityItemComponent], +}) +export class AuthoritiesTableComponent { + protected readonly authorityService = inject(AuthorityService) +} diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/backup.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/backup.component.ts index 52f0cef71..4e692abed 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/backup.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/backup.component.ts @@ -128,7 +128,7 @@ export class BackupsBackupComponent { this.dialog .openPrompt({ - label: 'Master Password Needed', + label: 'Master password needed', data: { message: 'Enter your master password to encrypt this backup.', label: 'Master Password', @@ -169,7 +169,7 @@ export class BackupsBackupComponent { this.dialog .openPrompt({ - label: 'Original Password Needed', + label: 'Original password needed', data: { message: 'This backup was created with a different password. Enter the original password that was used to encrypt this backup.', diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/backups.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/backups.component.ts index 68f75d848..179699140 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/backups.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/backups.component.ts @@ -66,7 +66,7 @@ import { BACKUP_RESTORE } from './restore.component' - + + + {{ target.entry.label || target.entry.logicalname }} @@ -82,8 +84,7 @@ import { BackupStatusComponent } from './status.component' :host-context(tui-root._mobile) { tr { - max-width: 18rem; - grid-template-columns: min-content 2rem; + grid-template-columns: min-content 1fr 4rem; white-space: nowrap; } diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/restore.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/restore.component.ts index b5ce3a857..90f6498fb 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/restore.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/restore.component.ts @@ -55,7 +55,7 @@ export class BackupRestoreComponent { onClick(serverId: string, { passwordHash }: StartOSDiskInfo) { this.dialog .openPrompt({ - label: 'Password Required', + label: 'Password required', data: { message: 'Enter the master password that was used to encrypt this backup. On the next screen, you will select the individual services you want to restore.', diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/status.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/status.component.ts index 888449ed3..20e26faf8 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/status.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/backups/status.component.ts @@ -12,14 +12,17 @@ import { TuiIcon } from '@taiga-ui/core' selector: '[backupStatus]', template: ` @if (type === 'create') { - + {{ 'Available for backup' | i18n }} } @else { @if (backupStatus()) { - + {{ 'StartOS backups detected' | i18n }} } @else { - + {{ 'No StartOS backups detected' | i18n }} } } @@ -46,4 +49,5 @@ import { TuiIcon } from '@taiga-ui/core' export class BackupStatusComponent { readonly type = inject(ActivatedRoute).snapshot.data['type'] readonly backupStatus = input(false) + readonly physical = input(false) } diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/dns/dns.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/dns/dns.component.ts new file mode 100644 index 000000000..63a3928c6 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/dns/dns.component.ts @@ -0,0 +1,215 @@ +import { CommonModule } from '@angular/common' +import { ChangeDetectionStrategy, Component, inject } from '@angular/core' +import { toSignal } from '@angular/core/rxjs-interop' +import { FormsModule, ReactiveFormsModule } from '@angular/forms' +import { RouterLink } from '@angular/router' +import { + DocsLinkDirective, + ErrorService, + i18nPipe, + LoadingService, +} from '@start9labs/shared' +import { ISB } from '@start9labs/start-sdk' +import { TuiButton, TuiTitle } from '@taiga-ui/core' +import { TuiHeader } from '@taiga-ui/layout' +import { PatchDB } from 'patch-db-client' +import { combineLatest, first, switchMap } from 'rxjs' +import { FormGroupComponent } from 'src/app/routes/portal/components/form/containers/group.component' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { FormService } from 'src/app/services/form.service' +import { DataModel } from 'src/app/services/patch-db/data-model' +import { TitleDirective } from 'src/app/services/title.service' +import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' + +@Component({ + template: ` + + + {{ 'Back' | i18n }} + + {{ 'DNS Servers' | i18n }} + + @if (data(); as d) { + +
+

+ + {{ 'DNS Servers' | i18n }} + + {{ 'Documentation' | i18n }} + + +

+
+ + + + @for (warn of d.warn; track $index) { +

{{ warn }}

+ } + +
+ +
+ + } + `, + styles: ` + :host { + max-width: 36rem; + } + + form header, + form footer { + margin: 1rem 0; + display: flex; + gap: 1rem; + } + + footer { + justify-content: flex-end; + } + `, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + FormGroupComponent, + TuiButton, + TuiHeader, + TuiTitle, + RouterLink, + TitleDirective, + i18nPipe, + DocsLinkDirective, + ], +}) +export default class SystemDnsComponent { + private readonly loader = inject(LoadingService) + private readonly errorService = inject(ErrorService) + private readonly formService = inject(FormService) + private readonly patch = inject>(PatchDB) + private readonly api = inject(ApiService) + private readonly i18n = inject(i18nPipe) + + private readonly dnsSpec = ISB.InputSpec.of({ + strategy: ISB.Value.union({ + name: 'strategy', + default: 'dhcp', + variants: ISB.Variants.of({ + dhcp: { + name: 'DHCP', + spec: ISB.InputSpec.of({ + servers: ISB.Value.dynamicText(() => ({ + name: this.i18n.transform('DHCP Servers'), + default: null, + required: true, + disabled: this.i18n.transform('Cannot edit DHCP servers'), + })), + }), + }, + static: { + name: this.i18n.transform('Static'), + spec: ISB.InputSpec.of({ + servers: ISB.Value.list( + ISB.List.text( + { + name: this.i18n.transform('Static Servers'), + minLength: 1, + maxLength: 3, + }, + { placeholder: '1.1.1.1' }, + ), + ), + }), + }, + }), + }), + }) + + readonly data = toSignal( + combineLatest([ + this.patch.watch$('packageData').pipe(first()), + this.patch.watch$('serverInfo', 'network'), + ]).pipe( + switchMap(async ([pkgs, { gateways, dns }]) => { + const spec = await configBuilderToSpec(this.dnsSpec) + + const dhcpServers = { servers: dns.dhcpServers.join(', ') } + + const current: (typeof this.dnsSpec._TYPE)['strategy'] = + dns.staticServers + ? { + selection: 'static', + value: { servers: dns.staticServers || [] }, + other: { + dhcp: dhcpServers, + }, + } + : { + selection: 'dhcp', + value: dhcpServers, + } + + const form = this.formService.createForm(spec, { strategy: current }) + + let warn: string[] = [] + + if ( + Object.values(pkgs).some(p => + Object.values(p.hosts).some(h => h?.privateDomains.length), + ) + ) { + Object.values(gateways) + .filter(g => + (dns.staticServers || dns.dhcpServers).some(d => + g.ipInfo?.lanIp.includes(d), + ), + ) + .map( + g => + `${this.i18n.transform('Warning. StartOS is currently using the following gateway for DNS')}: ${g.ipInfo!.name}. ${this.i18n.transform('If you intend to use this gateway for private domain resolution, set alternative static DNS servers using the form above.')}`, + ) + } + + return { + spec, + form, + warn, + } + }), + ), + ) + + async save(value: typeof this.dnsSpec._TYPE): Promise { + const loader = this.loader.open('Saving').subscribe() + + try { + await this.api.setDns({ + servers: + value.strategy.selection === 'dhcp' + ? null + : value.strategy.value.servers, + }) + } catch (e: any) { + this.errorService.handleError(e) + } finally { + loader.unsubscribe() + } + } +} diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/domains/constants.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/domains/constants.ts deleted file mode 100644 index ee68fa3c6..000000000 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/domains/constants.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { ISB } from '@start9labs/start-sdk' -import { Proxy } from 'src/app/services/patch-db/data-model' -import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' - -const auth = ISB.InputSpec.of({ - username: ISB.Value.text({ - name: 'Username', - required: true, - default: null, - }), - password: ISB.Value.text({ - name: 'Password', - required: true, - default: null, - masked: true, - }), -}) - -function getStrategyUnion(proxies: Proxy[]) { - const inboundProxies: Record = proxies - .filter(p => p.type === 'inbound-outbound') - .reduce( - (prev, curr) => ({ - [curr.id]: curr.name, - ...prev, - }), - {}, - ) - - return ISB.Value.union( - { - name: 'Networking Strategy', - default: 'local', - description: `
Local
Select this option if you do not mind exposing your home/business IP address to the Internet. This option requires configuring router settings, which StartOS can do automatically if you have an OpenWRT router -
Proxy
Select this option is you prefer to hide your home/business IP address from the Internet. This option requires running your own Virtual Private Server (VPS) or paying service provider such as Static Wire -`, - }, - ISB.Variants.of({ - local: { - name: 'Local', - spec: ISB.InputSpec.of({ - ipStrategy: ISB.Value.select({ - name: 'IP Strategy', - description: `
IPv6 Only (recommended)
Requirements:
  1. ISP IPv6 support
  2. OpenWRT (recommended) or Linksys router
Pros: Ready for IPv6 Internet. Enhanced privacy. Run multiple clearnet servers from the same network -Cons: Interfaces using this domain will only be accessible to people whose ISP supports IPv6 -
IPv6 and IPv4
Pros: Ready for IPv6 Internet. Accessible by anyone -Cons: Less private, as IPv4 addresses are closely correlated with geographic areas. Cannot run multiple clearnet servers from the same network -
IPv4 Only
Pros: Accessible by anyone -Cons: Less private, as IPv4 addresses are closely correlated with geographic areas. Cannot run multiple clearnet servers from the same network -`, - default: 'ipv6', - values: { - ipv6: 'IPv6 Only', - ipv4: 'IPv4 Only', - dualstack: 'IPv6 and IPv4', - }, - }), - }), - }, - proxy: { - name: 'Proxy', - spec: ISB.InputSpec.of({ - proxyId: ISB.Value.select({ - name: 'Select Proxy', - default: proxies.filter(p => p.type === 'inbound-outbound')[0].id, - values: inboundProxies, - }), - }), - }, - }), - ) -} - -export function getStart9ToSpec(proxies: Proxy[]) { - return configBuilderToSpec( - ISB.InputSpec.of({ - strategy: getStrategyUnion(proxies), - }), - ) -} - -export function getCustomSpec(proxies: Proxy[]) { - return configBuilderToSpec( - ISB.InputSpec.of({ - hostname: ISB.Value.text({ - name: 'Hostname', - required: true, - default: null, - placeholder: 'yourdomain.com', - }), - provider: ISB.Value.union( - { - name: 'Dynamic DNS Provider', - default: 'start9', - }, - ISB.Variants.of({ - start9: { - name: 'Start9', - spec: ISB.InputSpec.of({}), - }, - njalla: { - name: 'Njalla', - spec: auth, - }, - duckdns: { - name: 'Duck DNS', - spec: auth, - }, - dyn: { - name: 'DynDNS', - spec: auth, - }, - easydns: { - name: 'easyDNS', - spec: auth, - }, - zoneedit: { - name: 'Zoneedit', - spec: auth, - }, - googledomains: { - name: 'Google Domains (IPv4 or IPv6)', - spec: auth, - }, - namecheap: { - name: 'Namecheap (IPv4 only)', - spec: auth, - }, - }), - ), - strategy: getStrategyUnion(proxies), - }), - ) -} diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/domains/domains.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/domains/domains.component.ts deleted file mode 100644 index c6e713587..000000000 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/domains/domains.component.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { TUI_CONFIRM } from '@taiga-ui/kit' -import { CommonModule } from '@angular/common' -import { ChangeDetectionStrategy, Component, inject } from '@angular/core' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { TuiDialogOptions, TuiDialogService, TuiButton } from '@taiga-ui/core' -import { PatchDB } from 'patch-db-client' -import { filter, firstValueFrom, map } from 'rxjs' -import { - FormComponent, - FormContext, -} from 'src/app/routes/portal/components/form.component' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { getCustomSpec, getStart9ToSpec } from './constants' -import { DomainsInfoComponent } from './info.component' -import { DomainsTableComponent } from './table.component' - -@Component({ - template: ` - - @if (domains$ | async; as domains) { -

- Start9.to - @if (!domains.start9To.length) { - - } -

-
-

- Custom Domains - -

-
- } - `, - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [ - CommonModule, - TuiButton, - DomainsTableComponent, - DomainsInfoComponent, - ], -}) -export default class SystemDomainsComponent { - private readonly loader = inject(LoadingService) - private readonly errorService = inject(ErrorService) - private readonly formDialog = inject(FormDialogService) - private readonly patch = inject>(PatchDB) - private readonly api = inject(ApiService) - private readonly dialogs = inject(TuiDialogService) - - private readonly start9To$ = this.patch.watch$( - 'serverInfo', - 'network', - 'start9To', - ) - - readonly domains$ = this.patch.watch$('serverInfo', 'network', 'domains') - - delete(hostname?: string) { - this.dialogs - .open(TUI_CONFIRM, { - label: 'Confirm', - size: 's', - data: { - content: `Delete ${hostname || 'start9.to'} domain?`, - yes: 'Delete', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.deleteDomain(hostname)) - } - - async add() { - const proxies = await firstValueFrom( - this.patch.watch$('serverInfo', 'network', 'proxies'), - ) - - const options: Partial>> = { - label: 'Custom Domain', - data: { - spec: await getCustomSpec(proxies), - buttons: [ - { - text: 'Manage proxies', - link: '/system/proxies', - }, - { - text: 'Save', - handler: async value => this.save(value), - }, - ], - }, - } - - this.formDialog.open(FormComponent, options) - } - - async claim() { - const proxies = await firstValueFrom( - this.patch.watch$('serverInfo', 'network', 'proxies'), - ) - - const options: Partial>> = { - label: 'start9.to', - data: { - spec: await getStart9ToSpec(proxies), - buttons: [ - { - text: 'Manage proxies', - link: '/system/proxies', - }, - { - text: 'Save', - handler: async value => this.claimDomain(value), - }, - ], - }, - } - - this.formDialog.open(FormComponent, options) - } - // @TODO 041 figure out how to get types here - private getNetworkStrategy(strategy: any) { - return strategy.selection === 'local' - ? { ipStrategy: strategy.value.ipStrategy } - : { proxy: strategy.value.proxyId } - } - - private async deleteDomain(hostname?: string) { - const loader = this.loader.open('Deleting').subscribe() - - try { - if (hostname) { - await this.api.deleteDomain({ hostname }) - } else { - await this.api.deleteStart9ToDomain({}) - } - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - // @TODO 041 figure out how to get types here - private async claimDomain({ strategy }: any): Promise { - const loader = this.loader.open('Saving').subscribe() - const networkStrategy = this.getNetworkStrategy(strategy) - - try { - await this.api.claimStart9ToDomain({ networkStrategy }) - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } - // @TODO 041 figure out how to get types here - private async save({ provider, strategy, hostname }: any): Promise { - const loader = this.loader.open('Saving').subscribe() - const name = provider.selection - - try { - await this.api.addDomain({ - hostname, - networkStrategy: this.getNetworkStrategy(strategy), - provider: { - name, - username: name === 'start9' ? null : provider.value.username, - password: name === 'start9' ? null : provider.value.password, - }, - }) - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } -} diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/domains/info.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/domains/info.component.ts deleted file mode 100644 index b21dd143b..000000000 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/domains/info.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' -import { TuiLink, TuiNotification } from '@taiga-ui/core' -import { DocsLinkDirective } from 'projects/shared/src/public-api' - -@Component({ - selector: 'domains-info', - template: ` - - Adding domains permits accessing your server and services over clearnet. - View instructions - - `, - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [TuiNotification, TuiLink, DocsLinkDirective], -}) -export class DomainsInfoComponent {} diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/domains/table.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/domains/table.component.ts deleted file mode 100644 index e8f136e99..000000000 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/domains/table.component.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - EventEmitter, - inject, - Input, - Output, -} from '@angular/core' -import { TuiDialogService, TuiLink, TuiButton } from '@taiga-ui/core' -import { Domain } from 'src/app/services/patch-db/data-model' - -@Component({ - selector: 'table[domains]', - template: ` - - - Domain - DDNS Provider - Network Strategy - Used By - - - - - @for (domain of domains; track $index) { - - {{ domain.value }} - {{ domain.provider }} - {{ getStrategy(domain) }} - - @if (domain.usedBy.length; as qty) { - - } @else { - N/A - } - - - - - - } @empty { - No domains - } - - `, - styles: ` - :host-context(tui-root._mobile) { - tr { - grid-template-columns: 2fr 1fr; - } - - td:only-child { - grid-column: span 2; - } - - .title { - order: 1; - font-weight: bold; - } - - .actions { - order: 2; - padding: 0; - text-align: right; - } - - .strategy { - order: 3; - grid-column: span 2; - - &::before { - content: 'Strategy: '; - color: var(--tui-text-secondary); - } - } - - .provider { - order: 4; - - &::before { - content: 'DDNS: '; - color: var(--tui-text-secondary); - } - } - - .used { - order: 5; - text-align: right; - - &:not(:has(button)) { - display: none; - } - } - } - `, - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [TuiButton, TuiLink], -}) -export class DomainsTableComponent { - private readonly dialogs = inject(TuiDialogService) - - @Input() - domains: readonly Domain[] = [] - - @Output() - readonly delete = new EventEmitter() - - getStrategy(domain: any) { - return domain.networkStrategy.ipStrategy || domain.networkStrategy.proxy - } - - onUsedBy({ value, usedBy }: Domain) { - const interfaces = usedBy.map(u => - u.interfaces.map(i => `
  • ${u.service.title} - ${i.title}
  • `), - ) - - this.dialogs - .open(`${value} is currently being used by:
      ${interfaces}
    `, { - label: 'Used by', - size: 's', - }) - .subscribe() - } -} diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/email/email.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/email/email.component.ts index 0b1d67384..1c2d15bdd 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/email/email.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/email/email.component.ts @@ -11,12 +11,11 @@ import { LoadingService, } from '@start9labs/shared' import { inputSpec, IST } from '@start9labs/start-sdk' -import { TuiButton, TuiLink, TuiTitle } from '@taiga-ui/core' +import { TuiButton, TuiTextfield, TuiTitle } from '@taiga-ui/core' import { TuiHeader } from '@taiga-ui/layout' -import { TuiInputModule } from '@taiga-ui/legacy' import { PatchDB } from 'patch-db-client' import { switchMap, tap } from 'rxjs' -import { FormModule } from 'src/app/routes/portal/components/form/form.module' +import { FormGroupComponent } from 'src/app/routes/portal/components/form/containers/group.component' import { ApiService } from 'src/app/services/api/embassy-api.service' import { FormService } from 'src/app/services/form.service' import { DataModel } from 'src/app/services/patch-db/data-model' @@ -31,31 +30,23 @@ import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' {{ 'Email' | i18n }} -
    -
    -

    {{ 'Email' | i18n }}

    -

    - {{ - 'Connecting an external SMTP server allows StartOS and your installed services to send you emails.' - | i18n - }} - -

    -
    -
    @if (form$ | async; as form) {

    - {{ 'SMTP Credentials' | i18n }} + + {{ 'SMTP Credentials' | i18n }} + + {{ 'Documentation' | i18n }} + +

    @if (spec | async; as resolved) { @@ -88,13 +79,15 @@ import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' {{ 'Send test email' | i18n }} - - Name Lastname <email@example.com> - - + + + +