pax_global_header00006660000000000000000000000064142512617040014514gustar00rootroot0000000000000052 comment=ffda29af4045e543e55b25564d265af37f92fc41 kel-agent-0.4.6/000077500000000000000000000000001425126170400133725ustar00rootroot00000000000000kel-agent-0.4.6/.github/000077500000000000000000000000001425126170400147325ustar00rootroot00000000000000kel-agent-0.4.6/.github/auto_assign.yml000066400000000000000000000012721425126170400177730ustar00rootroot00000000000000# Set to true to add reviewers to pull requests addReviewers: true # Set to true to add assignees to pull requests addAssignees: author # A list of reviewers to be added to pull requests (GitHub user name) reviewers: - xylo04 # A number of reviewers added to the pull request # Set 0 to add all the reviewers (default: 0) numberOfReviewers: 0 # A list of assignees, overrides reviewers if set # assignees: # - assigneeA # A number of assignees to add to the pull request # Set to 0 to add all of the assignees. # Uses numberOfReviewers if unset. # numberOfAssignees: 2 # A list of keywords to be skipped the process that add reviewers if pull requests include it # skipKeywords: # - wip kel-agent-0.4.6/.github/renovate.json000066400000000000000000000011671425126170400174550ustar00rootroot00000000000000{ "extends": [ ":separateMajorReleases", ":combinePatchMinorReleases", ":ignoreUnstable", ":prImmediately", ":semanticPrefixFixDepsChoreOthers", ":automergeDisabled", ":ignoreModulesAndTests", ":autodetectPinVersions", ":prHourlyLimit2", ":prConcurrentLimit20", "group:monorepos", "group:recommended", "helpers:disableTypesNodeMajor" ], "postUpdateOptions": [ "gomodTidy" ], "schedule": ["after 3am and before 7am"], "timezone": "America/Denver", "packageRules": [ { "matchUpdateTypes": ["patch", "pin", "digest"], "automerge": true } ] } kel-agent-0.4.6/.github/workflows/000077500000000000000000000000001425126170400167675ustar00rootroot00000000000000kel-agent-0.4.6/.github/workflows/assign_pr.yml000066400000000000000000000002431425126170400214760ustar00rootroot00000000000000name: 'Auto Assign' on: pull_request: types: [opened, ready_for_review] jobs: add-reviewers: uses: k0swe/.github/.github/workflows/assign_pr.yml@main kel-agent-0.4.6/.github/workflows/release.yml000066400000000000000000000101371425126170400211340ustar00rootroot00000000000000name: Release on: push: tags: - 'v*' jobs: release-linux: name: Build the Linux release artifact runs-on: [ self-hosted, linux, x64 ] # Trust that the runner has an sbuild chroot # sudo apt install autorevision sbuild-debian-developer-setup debhelper dh-golang # newgrp sbuild # sudo sbuild-debian-developer-setup --suite=stable steps: - name: Remove old release artifacts run: rm -f ../kel-agent_* - name: Checkout code uses: actions/checkout@v3 - name: Test run: make test - name: Build Debian package run: make deb-package - name: Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: ../*.deb env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} release-rpi: name: Build the Raspberry Pi release artifact runs-on: [ self-hosted, linux, ARM ] # Trust that the runner has an sbuild chroot: # sudo apt install autorevision sbuild-debian-developer-setup debhelper dh-golang # newgrp sbuild # sudo sbuild-debian-developer-setup --suite=stable steps: - name: Remove old release artifacts run: rm -f ../kel-agent_* - name: Checkout code uses: actions/checkout@v3 - name: Test run: make test - name: Build Debian package run: make deb-package - name: Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: ../*.deb env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} release-flatpak: name: Build the Flatpak release artifact runs-on: ubuntu-latest container: image: bilelmoussaoui/flatpak-github-actions:freedesktop-20.08 options: --privileged steps: - name: Checkout code uses: actions/checkout@v3 - name: Build Flatpak uses: bilelmoussaoui/flatpak-github-actions/flatpak-builder@v4 with: bundle: kel-agent.flatpak manifest-path: flatpak/radio.k0swe.Kel_Agent.yml branch: main cache-key: flatpak-builder-${{ github.sha }} - name: Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: kel-agent.flatpak env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} release-macos: name: Build the MacOS release artifact runs-on: macos-latest steps: - name: Install WhiteBox Packages run: | curl "http://s.sudre.free.fr/Software/files/Packages.dmg" --output ${HOME}/Packages.dmg sudo hdiutil attach ${HOME}/Packages.dmg find /Volumes -name "Install Packages.pkg" | xargs -I {} sudo installer -pkg "{}" -target / - name: Create keychain with signing certificate uses: apple-actions/import-codesign-certs@v1 with: keychain: k0swe p12-file-base64: ${{ secrets.CERTIFICATES_P12 }} p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }} - name: Checkout code uses: actions/checkout@v3 - name: Test run: make test - name: Build Mac package run: make mac-package - name: Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: kel-agent_mac.pkg env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} release-windows: name: Build the Windows release artifact runs-on: windows-latest steps: - name: Install Go uses: actions/setup-go@v3 with: go-version: 1.15.x - name: Checkout code uses: actions/checkout@v3 - name: Test run: make test - name: Build Windows package run: | $env:PATH += ";$env:WIX\bin" make win-package - name: Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: win/kel-agent.msi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} kel-agent-0.4.6/.github/workflows/test.yml000066400000000000000000000010071425126170400204670ustar00rootroot00000000000000name: Test on: push: branches: - '*' tags-ignore: - 'v*' pull_request: branches: - '*' jobs: test: name: Run unit tests strategy: matrix: go-version: [ 1.15.x ] os: [ ubuntu-latest ] runs-on: ${{ matrix.os }} steps: - name: Install Go uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} - name: Checkout code uses: actions/checkout@v3 - name: Test run: make test kel-agent-0.4.6/.gitignore000066400000000000000000000003471425126170400153660ustar00rootroot00000000000000# Compiled assets kel-agent kel-agent.exe kel-agent.pkg kel-agent_mac.pkg win/kel-agent.msi win/kel-agent.wixobj win/kel-agent.wixpdb autorevision.cache repo/ .flatpak-builder/ kel-agent.flatpak flatpak_app/ .idea/ .pc .DS_Store kel-agent-0.4.6/BUILDING.md000066400000000000000000000023221425126170400151100ustar00rootroot00000000000000# Build requirements Building the basic program should only require a recent version of golang. ```shell make ``` ## Bumping the version 1. `go get -u && go mod tidy` 2. `rm assets/modules.txt && make assets/modules.txt` 3. Cross-reference `assets/modules.txt` with `flatpak/radio.k0swe.Kel_Agent.yml` 4. Cross-reference `go.mod` with `Makefile` 5. Cross-reference `go.mod` with `debian/control` `Build-Depends` 6. Run `make deb-package` on Linux amd64 and Linux arm to make sure `chroot`s are set up 7. Run `make flatpak` on Linux amd64 to make sure that's building 8. Add changelog entries in `debian/changelog` and `assets/radio.k0swe.Kel_Agent.metainfo.xml` 9. Bump versions in `macos/kel-agent.pkgproj` and `win/kel-agent.wxs` ## Packaging for Debian Linux (incl. Raspberry Pi) ```shell sudo apt install build-essential debhelper dh-golang sbuild autorevision export ARCH=$(dpkg --print-architecture) sudo sbuild-createchroot stable /srv/chroot/stable-"$ARCH" http://deb.debian.org/debian make deb-package ``` ## Packaging for Flatpak ```shell sudo apt install flatpak flatpak-builder appstream-util desktop-file-validate flatpak install flathub runtime/org.freedesktop.Sdk.Extension.golang/x86_64/20.08 make flatpak ``` kel-agent-0.4.6/LICENSE000066400000000000000000000261161425126170400144050ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 Chris Keller Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. kel-agent-0.4.6/Makefile000066400000000000000000000067631425126170400150460ustar00rootroot00000000000000VERSION = $(shell < debian/changelog head -1 | egrep -o "[0-9]+\.[0-9]+\.[0-9]+") GITCOMMIT = $(shell git rev-parse --short HEAD 2> /dev/null || true) GENERATED = kel-agent kel-agent_*.pkg win/kel-agent_*.msi win/kel-agent.wixobj autorevision.cache \ ../kel-agent_* ../*.deb flatpak/repo/ flatpak/.flatpak-builder/ flatpak/kel_agent.flatpak \ flatpak/flatpak_app/ flatpak/build-out/ .PHONY: all all: kel-agent .PHONY: test test: go test ./... go vet ./... if command -v appstream-util; then appstream-util validate-relax --nonet assets/radio.k0swe.Kel_Agent.metainfo.xml; fi if command -v desktop-file-validate; then desktop-file-validate assets/radio.k0swe.Kel_Agent.desktop; fi assets/modules.txt: go mod vendor mv vendor/modules.txt assets/ rm -rf vendor kel-agent: test export GITCOMMIT=$(GITCOMMIT) && scripts/build.sh architecture.svg: # apt install graphviz dot -T svg -o architecture.svg < architecture.dot autorevision.cache: autorevision -s VCS_SHORT_HASH -o ./autorevision.cache .PHONY: deb-tarball deb-tarball: autorevision.cache cd .. && tar -cvJf kel-agent_$(VERSION).orig.tar.xz --exclude-vcs kel-agent .PHONY: deb-orig-tarball deb-orig-tarball: autorevision.cache cd .. && tar -cvJf kel-agent_$(VERSION).orig.tar.xz --exclude-vcs --exclude=debian --exclude=.github --exclude=.idea kel-agent # TODO: This target can be removed once the package is in Debian stable and Ubuntu stable ../golang-github-k0swe-wsjtx-go-dev_4.0.1-1_all.deb: wget https://github.com/k0swe/wsjtx-go/releases/download/v4.0.1/golang-github-k0swe-wsjtx-go-dev_4.0.1-1_all.deb \ -O ../golang-github-k0swe-wsjtx-go-dev_4.0.1-1_all.deb # TODO: This target can be removed once the package is in Debian stable and Ubuntu stable ../golang-github-mazznoer-csscolorparser-dev_0.1.2-1_all.deb: wget https://github.com/k0swe/wsjtx-go/releases/download/v4.0.1/golang-github-mazznoer-csscolorparser-dev_0.1.2-1_all.deb \ -O ../golang-github-mazznoer-csscolorparser-dev_0.1.2-1_all.deb # TODO: This target can be removed once the package is in Debian stable and Ubuntu stable ../golang-github-adrg-xdg-dev_0.4.0-1_all.deb: wget http://ftp.us.debian.org/debian/pool/main/g/golang-github-adrg-xdg/golang-github-adrg-xdg-dev_0.4.0-1_all.deb \ -O ../golang-github-adrg-xdg-dev_0.4.0-1_all.deb .PHONY: deb-package deb-package: deb-tarball ../golang-github-k0swe-wsjtx-go-dev_4.0.1-1_all.deb ../golang-github-adrg-xdg-dev_0.4.0-1_all.deb ../golang-github-mazznoer-csscolorparser-dev_0.1.2-1_all.deb # https://wiki.debian.org/sbuild sbuild -d stable \ --extra-package=../golang-github-k0swe-wsjtx-go-dev_4.0.1-1_all.deb \ --extra-package=../golang-github-adrg-xdg-dev_0.4.0-1_all.deb \ --extra-package=../golang-github-mazznoer-csscolorparser-dev_0.1.2-1_all.deb .PHONY: flatpak flatpak: kel-agent cd flatpak && \ flatpak-builder --force-clean build-out radio.k0swe.Kel_Agent.yml --repo=repo && \ flatpak build-bundle repo kel_agent.flatpak radio.k0swe.Kel_Agent main .PHONY: mac-package mac-package: kel-agent # http://s.sudre.free.fr/Software/Packages/about.html packagesbuild --package-version $(VERSION) macos/kel-agent.pkgproj productsign --keychain `security list-keychains | grep k0swe | tr -d \"` \ --sign "Developer ID Installer: Chris Keller (2UK8VD3UP4)" \ kel-agent.pkg kel-agent-signed.pkg mv kel-agent-signed.pkg kel-agent_mac.pkg .PHONY: win-package win-package: kel-agent # https://wixtoolset.org/ cd win && candle kel-agent.wxs && light kel-agent.wixobj .PHONY: clean clean: rm -rf $(GENERATED) kel-agent-0.4.6/README.md000066400000000000000000000026741425126170400146620ustar00rootroot00000000000000[![Go Report Card](https://goreportcard.com/badge/github.com/k0swe/kel-agent)](https://goreportcard.com/report/github.com/k0swe/kel-agent) [![Release](https://github.com/k0swe/kel-agent/workflows/Release/badge.svg)](https://github.com/k0swe/kel-agent/releases/latest) [![Release version](https://img.shields.io/github/v/release/k0swe/kel-agent)](https://github.com/k0swe/kel-agent/releases/latest) # kel-agent logo kel-agent An agent program for translating between various amateur radio installed programs and WebSockets. This was built to support https://github.com/k0swe/forester but can be used by any web application that needs to communicate with amateur radio installed programs. ![Architecture](architecture.svg) This currently supports communication with WSJT-X. Planned support includes `rigctld` and Ham Radio Deluxe for transceiver remote control. To get started using `kel-agent`, download an appropriate executable from the [latest release](https://github.com/k0swe/kel-agent/releases/latest). Windows, Mac, Debian/Ubuntu Linux and Raspberry Pi installers are available. See the [Running documentation](RUNNING.md) for how to configure, execute and serve `kel-agent`. ## Acknowledgements The wire logo for `kel-agent` was created by [Freepik](https://www.flaticon.com/authors/freepik) on [Flaticon](https://www.flaticon.com). kel-agent-0.4.6/RUNNING.md000066400000000000000000000100321425126170400150300ustar00rootroot00000000000000# Running `kel-agent` ## Running on localhost In the simplest case, `kel-agent` is running on the same computer as your radio programs and browser. In this case, you can have `kel-agent` bind to `localhost` which will only allow programs on the same computer to connect. This is straightforward, safe and the default. ``` $ kel-agent 2020/10/10 18:50:32 kel-agent ready to serve at ws://localhost:8081 ``` To use a different port, add the `host` argument. ``` $ kel-agent -host localhost:9988 2020/10/10 20:06:39 kel-agent ready to serve at ws://localhost:9988 ``` ## Running on another machine If you want to run your radio programs and `kel-agent` on one computer and your browser on another, this is possible. There are a couple of approaches. Neither is super easy, which I hope to fix. NOTE: I do *not* recommend serving this in a way that's exposed to the internet because there is *no* authentication. If exposed to the internet, anyone could potentially initiate transmissions with your radio. ### SSH port forwarding This method is relatively simple and quick to execute, but is more brittle than serving secure websockets because there is some setup each time you want to use the agent remotely, and conceptually a little harder. Your remote machine must be running an SSH server for this to work. On the remote machine with your radio software, run `kel-agent` normally. It can be bound to `localhost`. ``` $ kel-agent 2020/10/10 18:50:32 kel-agent ready to serve at ws://localhost:8081 ``` On the machine with your browser, start a command line and establish an SSH tunnel with port forwarding: ``` $ ssh -N -L localhost:8081:localhost:8081 radio-pi ``` The first `localhost:8081` means "on this (browser) machine, bind to port 8081 and only expose to `localhost` so other computers can't use it." The second `localhost:8081` means "once you log into the remote computer, start forwarding traffic to port 8081 on its (remote) `localhost`." Finally, `radio-pi` in my example is the remote hostname which is running `kel-agent` and the SSH server. The command will look like it's not doing anything; just let it run, and the tunnel will stay open. Now your web application can be configured to connect to `localhost`. Traffic bound for `localhost:8081` will get securely forwarded to the remote machine. Both the browser and `kel-agent` think they're talking to local processes, and you won't get mixed content warnings. ### Secure Websocket with TLS This method needs a little more set up ahead of time, but is easier to use once it's set up. First, you'll need `kel-agent` to bind to `0.0.0.0` to allow connections from other computers. Second, due to the mixed content policy which is standard in web browsers, you'll need to specify a TLS certificate and private key. Eventually I hope to make this easy, but for now you'll need to follow https://stackoverflow.com/a/60516812/587091. In short, 1. generate a CA key and root certificate, then 2. a server key and certificate signing request with the server's hostname, 3. sign the request to generate the server certificate, then finally 4. install the root certificate in your browser's trusted authorities. Yeah, I really need to make this easier. ``` $ kel-agent -host 0.0.0.0:8081 -key server.key -cert server.crt 2020/10/10 19:05:39 kel-agent ready to serve at wss://0.0.0.0:8081 ``` Once running this way, your web application can be configured to connect directly to the remote computer. ## Allowed origins As part of the same-origin policy which is standard in web browsers, `kel-agent` will only accept browser connections from certain origins (basically, websites). By default, only the website `https://forester.radio` plus some local developer addresses are allowed to connect to `kel-agent`, but this can be customized if others develop web applications that use `kel-agent`. I'm happy to accept pull requests to expand the default list! ``` $ kel-agent -origins "https://forester.radio,https://someother.nifty.app" 2020/10/10 19:18:52 Allowed origins are [https://forester.radio https://someother.nifty.app] ``` kel-agent-0.4.6/VERSION.go000066400000000000000000000002071425126170400150450ustar00rootroot00000000000000package main // These are filled by `go build --ldflags` var ( Version = "unknown" GitCommit = "unknown" BuildTime = "unknown" ) kel-agent-0.4.6/architecture.dot000066400000000000000000000015771425126170400165760ustar00rootroot00000000000000digraph { Internet [ shape=septagon] subgraph clusterComputer{ label="Computer"; labeljust=l; style=rounded; fontsize=10; subgraph clusterBrowser { label="Web Browser "; labeljust=l; fontsize=10; color=lightgrey; Application [label="Web Application"; shape=box] }; agent [label="kel-agent"; shape=box; style=filled; fillcolor=lightblue] WSJTX [label="WSJT-X"] rigctld [label="hamlib\nrigctld"] HRD; etc [label="..."]; } edge [style=bold; dir=none; fontsize=10;] Internet -> Application Application -> agent [label=" websocket"] agent -> WSJTX [label="UDP"] agent -> rigctld [label="TCP"; style=dotted] agent -> HRD [label="UDP"; style=dotted] agent -> etc [style=dotted] } kel-agent-0.4.6/architecture.svg000066400000000000000000000125671425126170400166100ustar00rootroot00000000000000 %3 clusterComputer Computer clusterBrowser Web Browser                             Internet Internet Application Web Application Internet->Application agent kel-agent Application->agent websocket WSJTX WSJT-X agent->WSJTX UDP rigctld hamlib rigctld agent->rigctld TCP HRD HRD agent->HRD UDP etc ... agent->etc kel-agent-0.4.6/assets/000077500000000000000000000000001425126170400146745ustar00rootroot00000000000000kel-agent-0.4.6/assets/kel-agent.1000066400000000000000000000040411425126170400166240ustar00rootroot00000000000000.TH KEL-AGENT 1 .SH NAME kel-agent \- translates between amateur radio programs and WebSockets .SH SYNOPSIS .B kel-agent [\fB\-host\fR \fIhost:port\fR] [\fB\-cert\fR \fIcertfile\fR \fB\-key\fR \fIkeyfile\fR] [\fB\-origins\fR \fI"http://origin1"\fR[\fI,"http://origin2"\fR...]] [\fB\-v\fR] .SH DESCRIPTION .B kel-agent translates between various amateur radio installed programs and WebSockets. This allows amateur radio web applications to access rig control and digital mode APIs which are only available over TCP, UDP or by watching local files. \fIWSJT-X\fR is currently supported, with planned support for \fIrigctld\fR and \fIHam Radio Deluxe\fR. .PP .B kel-agent was built to support https://github.com/k0swe/forester but can be used by any web application that needs to communicate with amateur radio installed programs. .SH OPTIONS .TP .BR \-host =\fIhost:port\fR Set the websocket server hosting address. Default is \fIlocalhost:8081\fR. .TP .BR \-cert =\fIcertfile\fR \ \fB\-key\fR =\fIkeyfile\fR Set the websocket server TLS certificate and private key. These are only necessary when serving beyond localhost. .TP .BR -origins =\fI"http://origin1","http://origin2"\fR Set which CORS origins (basically, which websites) should be allowed to access the WebSocket server. Default is \fI"https://forester.radio"\fR plus some localhost development origins. .TP .BR -v Enable verbose debug logging. .SH EXAMPLE When running your web application and browser on the same computer as your ham radio programs, no \fBkel-agent\fR options are needed. This is straightforward, safe and the default. .PP $ \fBkel-agent\fR .PP If a different hosting port than 8081 is required, use the \fB-host\fR option. .PP $ \fBkel-agent -host\fR=\fIlocalhost:5001\fR .PP It's also possible to run your ham radio programs and \fBkel-agent\fR on one computer, and your web browser and web app on another. This is more complex to secure. I refer you to the GitHub repository for up-to-date documentation. .SH SEE ALSO \(bu https://github.com/k0swe/kel-agent \(bu https://github.com/k0swe/forester kel-agent-0.4.6/assets/license-32918167.pdf000066400000000000000000001230541425126170400177400ustar00rootroot00000000000000%PDF-1.4 1 0 obj << /Title (License) /Creator (wkhtmltopdf 0.12.5) /Producer (Qt 4.8.7) /CreationDate (D:20210522211737Z) >> endobj 3 0 obj << /Type /ExtGState /SA true /SM 0.02 /ca 1.0 /CA 1.0 /AIS false /SMask /None>> endobj 4 0 obj [/Pattern /DeviceRGB] endobj 6 0 obj << /Type /XObject /Subtype /Image /Width 1588 /Height 234 /BitsPerComponent 8 /ColorSpace /DeviceRGB /Length 7 0 R /Filter /DCTDecode >> stream JFIF     &""&0-0>>T     &""&0-0>>T4"  kd 4!#4A xWI1sUh9 )  ÀB wX/:IǗIAJO/0lB˦tj79t!N͠@jB/xcZ蠥Z_#v3Z8 ÔBvFm@q" VɫhPUU#& 90A !0 @ @ eP.`*)VL j@:!/^DG j͎Iy4ɨ @9Єxd՜&WȦy1)_f # 2K' v'>JN{{Iwiv@3.}s*"[iB{@a,&Y5x/~skUy9*ڠt  }GoSŃ |xbJ@% @@3z'ޚ>IG;oG8A^RM>;RG@ A {sߡ{Q|O>GO{->~迭qc&[7@@@g@='h`y;GͿ}t{_^~r>_uD?~(I!"'6@ A D9Wu>޸~|gGVi/Y~|)@}Ei!'.El@A D @_ߟPBߍ{Yάנ_Bc#@5ǞUW|N1v39Nɐh-i\zd(G[3\yn',:ts@xmF0_W]=V"A o>mO_/-@@|oo?9r3М^s+:o @?o knr^a9 I@<^_?Lχ}!=yᕧo@I@*A=y.-:z4@  3טj.BI  =8Am[Mu rLgZ@-Bҗ A ǟ%s[Vi:l E(@] lUI5h  <ܮmI×Pk9A@k@P\} <.}{) @-@C A=ՙ@b(G;I^z>{Reό $Ҕ(lzf1!@P][Ϫؐ1qEH(P꓎ Hr Aݙ4$A9 ( lA H`B `aW@U  01!2@AQ`a "#PRq6B$5Tprtu%Ubcs34CSd?O{=*7uQ^|CG+=?4ML'.i*QEZZ}~YGw+I{C/jZok$Ļ jA.EhA g9h!J +7-0Ev/_]у4o^Wyi6T˻bʅw=-q/!:MO.2j^ ]Ew\˛Er{8FN2mFp bZ)߃%}M㚤UڬlQ<эO9;Rfe*CYO(Z!Ƶj̝ZMIRn$"JaUM8#hbtLab\ʊ3ĀLMw?bv/PyvpE[ (u1Muݳgn5#h>NC7 ֹDs侤ia`WwF%լ'=62.ۃ:?w!}|ݣՇ[Me&\gXl(P/*!+om, .>%В.Y}GڴƨvƬFDTEx"$/0@%JBH u֩OGKqWTl.x!GI3!*Գ"i!"1o:Ke\ 'R2J˾֒U솞+DŬT_x}fq y,]`,]Lqnygp ^KSUI?$$ە PRGiF RX֎'F/#^1c)_mv;f*k3*ׂBTQm$b X"TZ#ZOʘ}A]MOLh={?4M£?8\1ⰃNG'׍![HC73P$f4Ow?}~h֍&5#,2@֎ԉJz_͎$hye[O!M )#]~CmM})W:y+-Ϥx%YoظeR)"RjWcV( y؞^tO%UGt"isATZS >{ᶔh*Z JE'F-;&j2G5 P_VJO2ta=RV$$RԵ 뷒y֮ )S37-,U w<0j~iK o}q^D4)q;EheE Hx#G1F "Lv\?t,C}e ЭbzO5*{@o^\qco{yjtm[]2,SkyR<ʉ`ɒ'Mܼ"ezZ1!=9:j"[VκUͼ&MJm[?!6Bs-|_'+e(;Yb t->Z7۱i;xn^Jejii6GNҶ*Q]2Tw.!,CqcozK-x,RQ(*Q 7'Ir,(6GV:5KD̲>: M iej.Mm/UIeRG+qjZ(е(pCJzf]@U8eնHQIԧSXy x-ԕo*7;t//Le1cu*)+r "ބQ VQOXr+EЊ7;u+,~J 逴Lg3c9uNh2ņXbXήم^a MJ FhiWIZ&5hi}"փoh.hΨzc1LM@I]D^ူ{ :/SnzEuo۫yg+!01A@Qaq P`p?'ҔJ%G %DTQ(J%D!O'QŽJJ% Q*Q(JJJ%J%pQ(J%DQ(J%G %DQ(q'&oaW 18><|o$Z 7gn0^,- E>LـF1x+,/+W1/4e ,Z1J[|߇$(/9MLS"' X< `#k/1z[q"RA)Vӂ"05j1V,^/L듻؀&$lF^0enXcLsccSh c+T QF&Rlt_ ]̗XNè@|u0r xӞ m^&yU$*?U5`wTmBQUPSbz`3"8LxP~jZJxC$˺ԃ&'-/]V/䲘sFPGKV\kI{#ٷ)f;w?8rLeՃeo3Ѝz`#LW*-ݽMz1Hbe#wEga6:2/ۣ. xK{MK2m}~+z 1A֭MZ`?-(6HkުP*Qv\dÒVvu]\4jCX22OrY/m%ыZ+%mbnhDF i=f64^:PЖ8}Qp4_MqzafF[7#0M\zL MYF:+hSq ?L v]ԕ!)߽aZ@Cnv6q{N, &j PQɶg䝮bHO c-ZS6ܺ1Tڮs˷{jo+UC NlC3HAo"9gN"8g Ǧ(J=^$Įp -H)pR.Y.\|/= f@;{%\;EU]y!Fɱ%3aF4\ݪ߭=DfOàC2ܾxm)L ,ױ)wsLEo2)@N,5W(SIԂ&N8tg=MЋ?^]˗/ w=(MI}~Py Zбn]Ӻo:(&MoR-~!5ěqu*UdYr\~س ca4̴5mr˗/.EBMLeo seo x[o el[-`lLŽe^l[-el7?6 1!0AQ@P2aq"#$R`5r?+ FF$(Qs#[ZFw Ii34NB@/z,5ņBkqNpoU[$j3J)°9v {L}D (6:n!9%5)njĈT,&c! 彣AŠ%[mk:M0{lˑn_ r$C"1x1$p- š#mZqQ5)1wVMٯcӞȉ*[:Ɓ\C1w'Vシ79;>]>)7U!BWU-3aZn%6\ /?RAx4jIJejU>W|9JujM.<h| D`g۞NV?@'iɁo i4lMִvwY6ݔS䪔U^$x[6ލn.6A-nècV͡Bj:tU:l/c9[;E԰1 hh9kNījvqc¡kf6ͲwQs{JkA8qi2Q?cCjo0( E,g ͇Mխ7F)hŊ;F?WFBydaNޯ:DsaǫcUuZ麩%sb˂Z"U5jl pΐ?4`Cn(DKoan,RgHr2watp:uǤ9JANA- =R8`e;P7x芮U,nXb>V2ҥJ,+ XJVf:d(juG;P3ӺTS:MFӕΟV22J>wˠ熧8 q*q>l=IJL$ 8 !01Q@AP"a$q#25`Sr?=ybI VaB'>hn)zr]8^bЯI2}OKyP 2)A<7`b(8JxIS*5h}("Gzo׆MH}4'lglYjk5:-xo4T4O]ރ& L:Ah<@A"'ԚLDlM̹(b{67?Տv^&Y݀Qlr A=:3XmK(aD>*ه퉹o`ݴh\i}%f?5c6FϠĞ^ڛQ+iHq$z[[pGiw!%iE>%a[ÒE{rž̤QJJ[WC3ted%XA@Y۽IN\º`|AfcU ߪ[}.,p[A [ٸ+xtXc\{M ߻hZH3Ig\OV>%UJ;1"h޸0 s1 vI9i[Ԟ'{\GW m!*&29C4]N?ijj|ًpMkU>Cgػo.pm WrZx}z[c^a 2ռXy- :MabV3)Ea2Cij[ſ &: { V',@MJ'sȚ$yx5#٢|j$ ,Nc(oT'jk׆"UYGj(555"Vj$<4dnƂ7jڂD{TTPƼ3Ey(>f***D `q s endstream endobj 7 0 obj 10308 endobj 10 0 obj << /Type /XObject /Subtype /Image /Width 128 /Height 128 /BitsPerComponent 8 /ColorSpace /DeviceGray /Length 11 0 R /Filter /FlateDecode >> stream xmhG71 JKbEiNJ{ hEKC" -j"ՃPU45X*ė;фr%m59mi^. w;=o>7;3;0(DGε>ܻ~»ieT Z]s$nm=IhB'7L_1=!8mη _O拶A ͵?2OHMĶ 趂OYēx@|:_-[)/['Z>"xR}? }(*uk6~M> ^݀b=OT#CM7^4 DMWPgD 8̍Ϲ ubG:JW6TS%/v :\ןRO).CC:+ & kLqĔ5V)Wwk5uݟpjy E/` ~0[6m;]Ùo6Tm…TlaHd6d(<\-W+2s5&u`L!;?[eT$O?y 5Lu7~F'W#a6 ~K*dͨNi?lσsҋ{|;|>T|Γ2V;|>սO6Y"?c9b!;nf!fHWӕG+|m^xM(K%krz\-ʣ/,{|mEG@2e`݋ׁm۪DGv$Aۮ^zx!S%xT_]XW 0ExP4Gtvg>_s3BQ-`YYDm Xw9 1JU:x u:E~=FzR2k=#OieSD(v1igc|;K<ݧ$6(??| endstream endobj 11 0 obj 1289 endobj 12 0 obj << /Type /XObject /Subtype /Image /Width 128 /Height 128 /BitsPerComponent 8 /ColorSpace /DeviceRGB /SMask 10 0 R /Length 13 0 R /Filter /FlateDecode >> stream x]kIJ+Y~ɲ' ð[(eJ 3KB)Bv)Z&[fKζKBf~BOײ+)8'qs{y\IըQ.b/gb69שp^{b+WPoE0Sq }Gv3a...Cܝig\{s+4VDeZfu:͌v0Xh]fƽPE;4{=m=08qlm숙;g0&'k}%E?Q׉V|̚@\ÍiYtNU3 ?tXva^~k"<-C0ҝ$Q5 ׂTs9.//Ǝن -lnjlްq(GO;8uG2awl̅/b!$UL,@;l]X-;sL8`+LA%Kyf((2?hM+#ar#*YcLhQ K[y!G0?MYNmFpėrJWWO-Ѵ2A4#b .YKSY;Ί|^8M 2U&7-IerCs|ȢhL8h _rޤ 'R0JV5 \KH/ۑy?nk{ Ĩc+yޙbvæER9!%'cxA.I2'ԜqڸsO(s4'd 6㈜t12uE[&BH *eڡt >uAɈ¥@ CAn$ r}2!f"oO!Z$5;rV|Mg1 "Pd2rc >( Pd2m7۞Ov2jO :sҴYI@GAGZG5jQ_~co<|{]>WOY C7 :JO@k N{PgGG؍9]nxus%n[<ә'm_{SBkYHLGϾ B`w ȯg!($FT_~. =&4ٚ/>7ICY Dk,o'k@o<"=^B$׼/Y g= d/Hi0קgk kDߺ-$ C^0VCf_--J'KqƯ|1.tc :ߞ'5Q?|ܞE ykWƣxk6vX*rf/~oKUz,AVd,ߦ/*bQ?O]>R|@E!tEy0m]Rݢ-jբ_-do4>G7kzuLz;=iv+]ԗ!h I\+OWCSɠSΪv$vp{20]3$4XhH_ uGvANqgZ6?h_v=EVIQxٚI+"meKt=Ҵ9wi>a8mW_dqp# &2_Q)BwtuTdu}Xp. vASsz#`nhXU0>5%L ]k 8bZsK`E>*-%L#oy3 hU;3v ,ߎk4.?n΃{[Cf=Pc*&B\\dwLO hX+6tnw+YⴟbxHm  39fZXXDn8ǃn|Glp(YvE< %'MG !15`> '$VQ 葼XM̚墷S̱Og𞷉'}|,ϲ6E%;NDzj1g6 l,8b.z?U`a+ANqcFwƒ/ʼɜ5 R$p81@5 vOx-t.")db?AZWui9P ˔`̦|;m8@j:kSߢgTwdy(XV+aL씦(sݶ 0DӢ׎ywJDУ7=vTm !pPCk|dBVc 5j(& endstream endobj 13 0 obj 2769 endobj 14 0 obj << /Type /Annot /Subtype /Link /Rect [226.500000 437.750000 368.250000 455.750000 ] /Border [0 0 0] /A << /Type /Action /S /URI /URI (https://www.flaticon.com/contact) >> >> endobj 5 0 obj << /Type /Page /Parent 2 0 R /Contents 15 0 R /Resources 17 0 R /Annots 18 0 R /MediaBox [0 0 595 842] >> endobj 17 0 obj << /ColorSpace << /PCSp 4 0 R /CSp /DeviceRGB /CSpg /DeviceGray >> /ExtGState << /GSa 3 0 R >> /Pattern << >> /Font << /F8 8 0 R /F9 9 0 R >> /XObject << /Im6 6 0 R /Im12 12 0 R >> >> endobj 18 0 obj [ 14 0 R ] endobj 15 0 obj << /Length 16 0 R /Filter /FlateDecode >> stream x\Ko6йe>E( khpA6͡4V}\-Ihxi8o8Cro_޿yh㟦{7yz}h>vg;tcRx)拞ǯQ8 A;ǓQM>=7=wMsg!!Z p6H5gT0R4O(_(߿pV/tPFxx/g|1+e*+9efu^p5ͧ0$V@V7Kv7 19\tXoV"4Jn6mll$<ĊT'}NF3qe6/c4xsZ` sKmd3q<+c3)NBOX==?q^>{šo<}6:~<>=i^~<ۅֈN1E Z6)z6!n1[؁"Eht:/%و4S8jn#0P0}^E0ٰun&MI['\g3` F}UPE,6TL,;u!PkX,AtV2qu(1 F(5Ǝ h37u p,`/gjdB8ֵgn5v;eLb?4ΗOZJ t쫯}B@T:ZECX2+i+ȰͨTY<i٭C:$*I[֧,IL(~ x`JM6acP&^1fTmVD̚Q*Qg:2Ȼ_0h&7]~,c-\}˘гh(;ka@ %_!wv_E*5űc´YjMDWxNԌm=w޼_*t(\89 ,dGWwwp[m;y5Zi!  7y D6q%ƮfsW*Gd8 S x!ٰQ bLU\hm7n5Z4S60*iu GG5!PkЫV5#*V]}z›b51[q)@˔& ʩ` )$t</8|t*nQ 0|z–Ɇ)$x١ sUxU. ePk4N U/ol/K HFA7M0Lu\)XuP:oZqEq|Z8ٞިa͎T#e?`-5떚ieJB)4h5^=fBQ.nKJ£R2ƩJ=ļ$J'k-ApE:+$O8iz}Kpgէ`Q(8L<n* endstream endobj 16 0 obj 2215 endobj 20 0 obj << /Type /XObject /Subtype /Image /Width 24 /Height 22 /BitsPerComponent 8 /ColorSpace /DeviceGray /Length 21 0 R /Filter /FlateDecode >> stream xС M4 5\H4]%TCP|E}q.E=#XwpaBe[h SqϺ]8{})F9Pi܃9oAy43 f_[jB endstream endobj 21 0 obj 123 endobj 22 0 obj << /Type /XObject /Subtype /Image /Width 24 /Height 22 /BitsPerComponent 8 /ColorSpace /DeviceRGB /SMask 20 0 R /Length 23 0 R /Filter /FlateDecode >> stream x= CLթk\&TQ 8ZldT]I`!@ r"Q!.TDQ4K>y[7׭uNKcw.EF:zǒΊyfsh$ZgY!_3%(.!I8ptve endstream endobj 23 0 obj 177 endobj 24 0 obj << /Type /XObject /Subtype /Image /Width 22 /Height 22 /BitsPerComponent 8 /ColorSpace /DeviceGray /Length 25 0 R /Filter /FlateDecode >> stream xѱ FῢsV` "[ 0s81z\+0x>A5 ]ePL[⣑\Gw@Y={37%YT*ydv~\t endstream endobj 25 0 obj 142 endobj 26 0 obj << /Type /XObject /Subtype /Image /Width 22 /Height 22 /BitsPerComponent 8 /ColorSpace /DeviceRGB /SMask 24 0 R /Length 27 0 R /Filter /FlateDecode >> stream xK D^nrB]t! ()!U`Si& zQw ykGd2-LB?edPqV~A|2o R7<#7gV;/Ao{dޤ9V9j fI%B@YE+Mh D+' g* endstream endobj 27 0 obj 203 endobj 28 0 obj << /Type /XObject /Subtype /Image /Width 24 /Height 24 /BitsPerComponent 8 /ColorSpace /DeviceGray /Length 29 0 R /Filter /FlateDecode >> stream x 7@ T:y"7@ 4@ 1ɾdY0qlRƧRjG3OKU k\dl+tikLDĚw dayHpQ:V, PKTãr1? ֣KΗz K,gd^nx`y > stream xK EݡơKp3}ZZ&cS<^PP)t];S ?8îgPF6c"ȳs<"+`\ZגiBbU/RH>\Ջ )}`MaA<{rBKčmk.(>ף4.RAe endstream endobj 31 0 obj 248 endobj 32 0 obj [1 /XYZ 22.5000000 735.500000 0] endobj 33 0 obj [1 /XYZ 22.5000000 665 0] endobj 34 0 obj [1 /XYZ 37.5000000 525.500000 0] endobj 35 0 obj [1 /XYZ 37.5000000 290.750000 0] endobj 36 0 obj [1 /XYZ 37.5000000 400.250000 0] endobj 37 0 obj << /Type /Annot /Subtype /Link /Rect [22.5000000 139.250000 170.250000 155 ] /Border [0 0 0] /A << /Type /Action /S /URI /URI (https://www.flaticon.com/terms-of-use) >> >> endobj 38 0 obj << /__WKANCHOR_2 32 0 R /__WKANCHOR_4 33 0 R /__WKANCHOR_6 34 0 R /__WKANCHOR_a 35 0 R /__WKANCHOR_8 36 0 R >> endobj 40 0 obj <> endobj 41 0 obj <> endobj 42 0 obj <> endobj 43 0 obj <> endobj 44 0 obj <> endobj 39 0 obj <> endobj 45 0 obj << /Type /Catalog /Pages 2 0 R /Outlines 39 0 R /PageMode /UseOutlines /Dests 38 0 R >> endobj 19 0 obj << /Type /Page /Parent 2 0 R /Contents 46 0 R /Resources 48 0 R /Annots 49 0 R /MediaBox [0 0 595 842] >> endobj 48 0 obj << /ColorSpace << /PCSp 4 0 R /CSp /DeviceRGB /CSpg /DeviceGray >> /ExtGState << /GSa 3 0 R >> /Pattern << >> /Font << /F8 8 0 R /F9 9 0 R >> /XObject << /Im6 6 0 R /Im22 22 0 R /Im26 26 0 R /Im30 30 0 R >> >> endobj 49 0 obj [ 37 0 R ] endobj 46 0 obj << /Length 47 0 R /Filter /FlateDecode >> stream x]Kܸ97,c;H !!fXě8{ߏ(jnղ%xͫi>ϧlT;kw/ӇӇǧ?O%—D1.K54@nx/Xӫ=}e rc Yߍ/'Z=/S~΅?_W]\i!3\ɮen[q] &Ĉ:hm\p6ǾK֑׭@/}dhyKi6ae(`6D}3{0|n$i>|s}Swj:‡yVR[ǔ1s7&Rj56tIў2) uI;B s#nHgOiy33tK=5SĽ|ƽ 50m]-N[޿ʀk !{r+|#3}e)t3O:ytCPmPvy?r<˜ EeKjgEu S6 ps@})"sLPBKR7_0LL,:^0{aHP9QjOJ16U|qTIm U#{4t'P vhGcԋω1򱙣B]C2~pQC M`b=! C8"0%DjAJ]}Oߠ4mXO?RYrT9s#_*SNVs#j]b b4o qТjF |A΢`eM~dh=. CuӾ6pؒIe^$_׃K/#x;ߞw?g>%g;W;mfVFٺU =h=Ǜ"/l݊l:O67lw.KbW|PT9E ([\MeSb6zMy6uo(SlrP)7lͦ"XK+"b`NÆMՓ$T @P.xm 1lnjdl巣n$Pω R+4K+CoGQ~1WV)2]6oc^ k`ܮl<ഄHRrӮQ;J47 HBJ{[5vRƣa,X\ 8lTJ A A*-JL6$FAݱ%(3n ;|G\N]N~%!ToPwkXmܚja4_7ţ4gQ ,•6풡+*oPpo(SKV1916oc6 "ވ&*,RLuVFLc+П{ZU-ʐ;%W@8VKtNJrωTXQ^ڥXQƱs"c+j-c@c(ʱP;! #2Mق唨U}(74vg~Gcpst?4~FqU Ħ+<6 K*}ƴ J{ 1#+}f:&&c`ȵjX;p#HGIҩ+5U|/zACAw@;6f/{ְ#sUGx1)15WƧ*0yq$Lgvq"7|~@:Z ̟dnse[*ֽ!]|WlRƩ)"ꮯUql& n`09uL /TgL.̖9ƙ7D_̩8e|g5Wv**5c gS?2l&~o`U֚(g*[ituT+_I0WUTK^ܮPNa!q+bX,pB ƘbN/b)f<,ްQ~S?B B̂;/u#Fe]{\5 3 JY(f&nC`_0m.>PPuTX7Tr#2 7=;H*'#'|hH;y-=cK#_<"i8=zӥ -~Pʎ;tpE;"/GscK;\tϱ3a6~dsEuXQU7lPOsfm2qk>lu{AFхbCcvUDD|K{;oD^ӽ^K{;oDY~S?""0$L ܈¬0dZ3Rwr})A޹G8E:Q_A|(R;+[΅y}q]U:*nT:ovDso(S*K*7sҹ7litx@a`yT>H#,s ď=de>},lǡD;ӊ)mFϸC:M!`T\X3 \ +[׽?.-I۶uxHo"i<̯$(!`w2kܣ[P{*@L:Di ֬.^v~~%XRc(!T2ʼn~[m\65d/K>W]͈4L]8SWeN.ḧ́<~|Uh᪠cxna2`ܰulq{`Yt)vAj**pݷרzv̫°T)uUIKZ=qe̻ݕ)`jU]IZ/L=LzjƷ'?@ߘ΄#ȲGW7Nmp |>N Nj"&~S\(FʜK6)|+?:]gzOfzでM.$)( p OSFu) "g$~ ʧz0aڴ#b-PVĪ.e{+Rv V9Ny/֔Sٍz*eE<,,Ec:;^FVXp~;F0-"v*gPKHUQW*f\籜ڵ.wx7]m܇6I4_}-௄r o>=q &=MזLW r3Shp| Si8L͇FpJ endstream endobj 47 0 obj 4292 endobj 50 0 obj << /Type /FontDescriptor /FontName /QYBAAA+ProximaNova-Bold /Flags 4 /FontBBox [-80.0781250 -199.218750 1041.01562 909.179687 ] /ItalicAngle 0 /Ascent 1079.10156 /Descent -387.695312 /CapHeight 1079.10156 /StemV 87.8906250 /FontFile2 51 0 R >> endobj 51 0 obj << /Length1 6904 /Length 54 0 R /Filter /FlateDecode >> stream xY{xוgf$)˲[cْdId~vcm`[~?C !! la9Nb=W=rA HYzԄTv'j'p=lO3g]'rQ OhuŸ"^Ԣ,(^> PR9B%hCOK@JvE IYLd2噌)8R g4{P 殰%1hU-PdZML WEX#*Li a#:Zc|ŊmG65^<3T:'~TO9-=S) pf佘;JQ#>WZ aǏ\>VԷ{1$`Z$D6($f) >=]mJM5i6vf2ߘPuףR䘂6|9VpT*rgiE3ȼK+h;[0)K76JƩӒI %GD- Cw~at] &cN'FF|"*"8_}g''_b"A X' Q)#5Gk ?V  GDEkԊs%u.ȑz=1mT a?p45Nݿq}YŬi:>4<`C[aOHeZ`ܼhп-]tKEh3'$#݆{z堎yw<Ѷq;sZUݔmƩԱ6KVfi:%nEy~NB<jGft=ݙh5TtyΪxE(Ī3Xn2 "<)yŊMkcI]RIVof,+\pQ w@#fX%pAB.8Ζ":eu mD`L;2aY g966] 31QڛG}٭Et?  z6 A1xT<=2=6c9 –V#0 G-+|h`y)@iyKɐ]SS5>Z]UU=:Ne(C⨏/"=]tV;&"]]ExFƞ]bF UKmq`u-|}nNt v;ڒ:>㮖ֆ$\yMN*gSs3'/^sV"Ds2o\#G1}L<|G!腘Kl[sDո^ɭ9͞ue7OnzF"6Z*:o>i̙q[:LԖ]VT$DER.HHȳg udƒ% 6XY''cnjazO0b܌*ÊG6>zVp-&/^K4,ɺkJ2um)HN^9~99YgfT !Zamx0 ܟ ,٣8v6== X"UTҵ6]RSRR {`dĨ.^blQ'njg͐T,sMMYQd2P])1SR/ I9vt=q@ f?s5waSfn?p ҹ+XDh*S< { O{ k~wuX=}}=p|jj]~}C~z7wXlƏ7@]J8{' =;A1^5I?Cs*UPT2 tN=r7 Ϟ-|cs¨w…޶6%0L+=a^o4L̹#ϭmhXܑs7n}熤S~w55@Ǐ92D8)Ls]]B ҉Dk{ MFSgFFμ>KJz =%%´vc;v~]wY qFٴ8YI1ZDkPIѧŚ$}vFj.3KQ}rfЈ[M#oU:_ {w"0T˖f'RI6>z5S߄rZR+H =B/e\21(0eÐQ2!K?ׄ|EGp^b(t4/BzM@"Kf,ۗ.ɰۤ4p,mmnܒp[Ջhw^`Gg*]{rQIǛ5Hu?Ա1'P+QVI[, X;w _ {OT[l&C^ldPL8'_9sAzvƽybu1?S2wg+n7('A݇7$7  ^yR`{j,ڙ,ɸ!|%bطדlFlڏ|so_˯D<W0[O?nc;W_>=~7G&ޤم](oxvU|c%okӥ58ub$ ?> d~z}`䘰wVNN|qŰ-+)@osF7voSIsE$- =G` k!H%jL^8,dI+Y'i}**.&(8"*"2 iIs3UR| pxJ#maZNAuWC>:r#cc#_),VV^ZV٠-.)) Z/۷kV  ϼfu\-/u+"}ZR'e4k{AU.s&_,aͨ񾖊[5M>эؤWOspSWP*)(+.(țv|ymNͣPJJ:fd ́093yRn5xopXq[7~Y=XV;{ؙ3iFJX7):|L3>R\ q"*\u3=I)=|"*xyØ43b'ӒJߐii,)缏O$KqI $ܻ$^Z\g?FR;>Gpn!cxmow$$r2LN?C+p`^8sٝQ؋:QnwZ+i 4LB!}DɶɎN>}Wۦ8~\pDCXwd Y‘/bT?9̪dCPKH /Oо:İ^:v/L_^C3ȵKiP7ᨗz~$I4X$v6K*vTuf&d`Qlv6F;4UB8\#]MU!<[pu⯕I1Y8c˺a~՘fgY^$Q_(3;pL7](UWc/{wPt>7ό$[fIǴNL,HtfÅ[;\jNjuZlw ltu4cFFVWV@bvet7pLNowj_-rѯnR5v5mV+鵺?l(T`+ ` tE't6O ۰،C).60Гi2 :>-6?4nt )rхtL+o=SGwٱ> /FontDescriptor 50 0 R /CIDToGIDMap /Identity /W [0 [362 519 251 495 548 574 473 254 681 357 335 575 539 500 509 251 569 615 592 566 324 845 574 911 272 577 725 678 637 657 722 759 723 348 712 580 223 575 530 254 595 254 762 575 326 578 295 295 581 452 580 249 581 297 ] ] >> endobj 53 0 obj << /Length 735 >> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 2 beginbfrange <0000> <0000> <0000> <0001> <0035> [<004C> <0069> <0063> <0065> <006E> <0073> <0020> <0043> <0072> <0074> <0061> <0078> <0079> <006C> <006F> <0030> <0034> <0046> <0066> <006D> <0075> <0057> <0049> <0054> <0048> <0041> <0052> <0042> <0055> <004F> <004E> <002A> <0044> <0070> <0027> <0068> <006B> <002C> <0053> <002E> <0077> <002F> <0045> <0028> <0029> <0062> <003F> <0067> <003A> <0064> <002D> ] endbfrange endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 8 0 obj << /Type /Font /Subtype /Type0 /BaseFont /ProximaNova-Bold /Encoding /Identity-H /DescendantFonts [52 0 R] /ToUnicode 53 0 R>> endobj 55 0 obj << /Type /FontDescriptor /FontName /QDCAAA+ProximaNova-Regular /Flags 4 /FontBBox [-84.9609375 -199.218750 1050.78125 907.226562 ] /ItalicAngle 0 /Ascent 1079.10156 /Descent -344.726562 /CapHeight 1079.10156 /StemV 53.7109375 /FontFile2 56 0 R >> endobj 56 0 obj << /Length1 9056 /Length 59 0 R /Filter /FlateDecode >> stream xYyT׹wf$cBf$Bf7'x8Fq8C4''Kmں>9>nN^mɱ}w$a8}o.-~B^h;פ-=pvf,Φ]E("|ea1L8tЗkou3Bx嵏D( w wO訿d{O!$B(:!^[x?!e>/S5x|0Ds2w<ױu q"'d4z/ JrPZHI8|oFh! s^OD7*^ D }>C&4,!Z%11,NN#19Zrs}yYt>E p&(d|_"զ|eRTtF~cpslzdjr})ɮ[_0ZIa\d(؊bF7g ٛG4j6{*rRQ)WlhVMM?1^R\\2>anZ̍p4-Ol }b,`ZL_gp瞺a2ٳtgl!)s1ւhWp,7@ Hu`Br\rz%].'TܲI ?lesEe.::-ՔDq;J #=&ڑ\RVo+(**՗lWYn^W3x388Dor6_x=Gc4utLX 97O7S* HpZ!Kͽffgg45چ惍fsCj\ZSKX~LF !Uh-TsAxtBhO+ =Q3Op?6(NUM9vj~;,alF "?Z]v}.nOKkuҘ?l[?љkv /A+e5mN=9_c_ONJQTu9YajM͒aᚔzsYyKrQW.hx:BK'ߘM75f g48" W1_kt2ix>9\KNߛ 8pbC:f9:l;o-)QJ!펈j.W%M6e]1ؼ6ܒDcp2TVcRćE X {[ 1O*(nmv%QT޾XYQVcHuezuJt1D%тG/p><3e;h 2˃b CvaAwvvff) lll:!>nvg>T~{_>5mPYǮ+Ti0'yu'p؋SX,zen ް## P[0d#ciDᙝFa:]x.qC!\l}yKח%,,Z{mbU{{|I1؝R~4&eծ_^qة#Rp}#F] uD5 A*=Q)oϓxaIQr(zWX3ˣmSُk5Hrmhd_5AreVvc gv32100? E__/_DXsC>sL=Y|@Qhdt4>QJ $k1^wR C^21iK8,& 2bѩg:;gz&?:(:NZz·a]z[C;_x]:mMsؓ>5gf԰!iY ZK+OoI`J=^[䧧&q0~WQQ9:eq~3nW)'NY_snp‚^A/䴵F[Zr pph*o]kU!W곊ImiOo{޹8Hr:mIQyy<6?_m5*\ ,c,ghPBqDDbr6uy%ԃ`џЕKuRR:% щ1]ATZw$F$CcSc;3l9״!PbFa ^Ө#f^+HR > hhf,w*120~lxћ3^b/Wq0z"V|cvϰ1,F<J+8ƲtВCg7Hzh 1S*J=Ɩ.R]?/.T bs¨ `4daA~3wumh 9GpbKsM#1k^Beѯ ڴh%ynJ!e صќ_XPW\_[Te ÝPa{~!h޶׆JUII4F+]C"2Jqpvܲ#Q^_(1^,J3 Q„/Z̍d4,e8(77 E%'BQ8!R:Th@Iݗqe*F]:EB b$Z )N=8n Qv|BQB)s>r:U4];|`uM__b0"uE ' VJEK\&O"*??2 <2å?]Gˣ"#MA+k0NF8w𽛑;&K}I6qG e7N[YA*ib̓S?}yr1Rd֊/fĞum[ic}~0.+ݶXb ׮D8&\#%JR_.=^{dD.y6-:w4{gpFx30C)+: \]>04_%}RWQ!wӊ)T/YZd.G( 8=,.j 0a4Z >R?CIc!XgޏD*fD$rЏyUyVde.CO@_ M0(y2(v PeAFfShهOPE7q4 D-h~e<@hd%Nnc FpރA (%_@ΦY[fr.>;͛7f<<{A`NG/K7~a޳?>)߳I{Oi!ꇜVE]uc5(McnT7i@Si|y(i> iRQEn uȟo7"hzMS3W]1Zn^-nJwfo4,#~M P E{jM{pEԗne9n:Q=AVdfC!g=G3pnhIglph a j(T>=0-OUݏ&K7'l@h0A*@``Cc w1TENI0/hd0 rnf#mj@z \qhtEOTp]&'G7譏c63#=4 ܷyl>"qQS/IIqVx}dRNQgfm`cZHuwKJ4#;cPЧaq;$乮85qwV20&閌o<$[$~`>2f?x0b  'z쒊Ηo(FhS\S9lfjjJMHcSGPv I>8x]cC P,? endstream endobj 59 0 obj 6076 endobj 57 0 obj << /Type /Font /Subtype /CIDFontType2 /BaseFont /ProximaNova-Regular /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor 55 0 R /CIDToGIDMap /Identity /W [0 [362 506 223 491 558 546 461 228 694 567 728 223 523 570 256 292 583 802 486 607 336 569 876 327 545 510 337 505 547 801 228 569 546 581 486 670 603 569 280 523 228 553 586 511 570 652 297 552 585 565 482 702 624 576 586 786 505 223 340 197 237 582 246 468 246 621 759 694 ] ] >> endobj 58 0 obj << /Length 833 >> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 2 beginbfrange <0000> <0000> <0000> <0001> <0043> [<004C> <0069> <0063> <0065> <006E> <0073> <003A> <0044> <006F> <0077> <006C> <0061> <0064> <0020> <0074> <0032> <004D> <0079> <0030> <0031> <0070> <0057> <0072> <0046> <006B> <002A> <0068> <006D> <002E> <0071> <0075> <0053> <0076> <0043> <0052> <0067> <0066> <00E1> <002C> <0034> <0039> <0037> <0062> <0041> <002D> <0033> <0035> <0054> <0078> <004E> <0042> <0038> <0036> <006A> <0022> <0027> <0049> <0050> <0028> <007A> <0029> <0059> <004F> <0055> ] endbfrange endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 9 0 obj << /Type /Font /Subtype /Type0 /BaseFont /ProximaNova-Regular /Encoding /Identity-H /DescendantFonts [57 0 R] /ToUnicode 58 0 R>> endobj 2 0 obj << /Type /Pages /Kids [ 5 0 R 19 0 R ] /Count 2 /ProcSet [/PDF /Text /ImageB /ImageC] >> endobj xref 0 60 0000000000 65535 f 0000000009 00000 n 0000041155 00000 n 0000000171 00000 n 0000000266 00000 n 0000015456 00000 n 0000000303 00000 n 0000010781 00000 n 0000033035 00000 n 0000041010 00000 n 0000010802 00000 n 0000012265 00000 n 0000012286 00000 n 0000015242 00000 n 0000015263 00000 n 0000015804 00000 n 0000018095 00000 n 0000015577 00000 n 0000015777 00000 n 0000021969 00000 n 0000018116 00000 n 0000018411 00000 n 0000018431 00000 n 0000018793 00000 n 0000018813 00000 n 0000019127 00000 n 0000019147 00000 n 0000019535 00000 n 0000019555 00000 n 0000019961 00000 n 0000019981 00000 n 0000020414 00000 n 0000020434 00000 n 0000020486 00000 n 0000020531 00000 n 0000020583 00000 n 0000020635 00000 n 0000020687 00000 n 0000020878 00000 n 0000021802 00000 n 0000021005 00000 n 0000021167 00000 n 0000021332 00000 n 0000021521 00000 n 0000021666 00000 n 0000021865 00000 n 0000022344 00000 n 0000026712 00000 n 0000022091 00000 n 0000022317 00000 n 0000026733 00000 n 0000026999 00000 n 0000031816 00000 n 0000032248 00000 n 0000031795 00000 n 0000033177 00000 n 0000033446 00000 n 0000039634 00000 n 0000040125 00000 n 0000039613 00000 n trailer << /Size 60 /Info 1 0 R /Root 45 0 R >> startxref 41260 %%EOF kel-agent-0.4.6/assets/modules.txt000066400000000000000000000024331425126170400171070ustar00rootroot00000000000000# github.com/adrg/xdg v0.4.0 ## explicit github.com/adrg/xdg github.com/adrg/xdg/internal/pathutil # github.com/davecgh/go-spew v1.1.0 github.com/davecgh/go-spew/spew # github.com/gorilla/websocket v1.5.0 ## explicit github.com/gorilla/websocket # github.com/imdario/mergo v0.3.13 ## explicit github.com/imdario/mergo # github.com/k0swe/wsjtx-go/v4 v4.0.1 ## explicit github.com/k0swe/wsjtx-go/v4 # github.com/leemcloughlin/jdn v0.0.0-20201102080031-6f88db6a6bf2 github.com/leemcloughlin/jdn # github.com/mattn/go-colorable v0.1.12 github.com/mattn/go-colorable # github.com/mattn/go-isatty v0.0.14 github.com/mattn/go-isatty # github.com/mazznoer/csscolorparser v0.1.2 github.com/mazznoer/csscolorparser # github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib/difflib # github.com/rs/zerolog v1.27.0 ## explicit github.com/rs/zerolog github.com/rs/zerolog/internal/cbor github.com/rs/zerolog/internal/json github.com/rs/zerolog/log # github.com/stretchr/testify v1.7.2 ## explicit github.com/stretchr/testify/assert github.com/stretchr/testify/require github.com/stretchr/testify/suite # golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d ## explicit golang.org/x/sys/internal/unsafeheader golang.org/x/sys/unix golang.org/x/sys/windows # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 kel-agent-0.4.6/assets/radio.k0swe.Kel_Agent.desktop000066400000000000000000000002041425126170400222410ustar00rootroot00000000000000[Desktop Entry] Name=kel-agent Exec=kel-agent Type=Application Icon=radio.k0swe.Kel_Agent Categories=HamRadio;Network Terminal=true kel-agent-0.4.6/assets/radio.k0swe.Kel_Agent.metainfo.xml000066400000000000000000000057201425126170400232010ustar00rootroot00000000000000 radio.k0swe.Kel_Agent CC0-1.0 Apache-2.0 kel-agent An amateur radio web agent

An agent program for translating between various amateur radio installed programs and WebSockets. This was built to support https://github.com/k0swe/forester but can be used by any web application that needs to communicate with amateur radio installed programs.

kel-agent https://github.com/k0swe/kel-agent mild kel-agent opening terminal output https://github.com/k0swe/kel-agent/raw/main/assets/screenshot.png

Improvements to websocket and logging

Integration tests

Dependency updates

https://github.com/k0swe/kel-agent/releases/tag/v0.4.6

Added the ability to send client commands to WSJT-X

https://github.com/k0swe/kel-agent/releases/tag/v0.4.4

WSJT-X listening address fixes

Added flag for selecting a custom config file

https://github.com/k0swe/kel-agent/releases/tag/v0.4.3

Add configuration by file

https://github.com/k0swe/kel-agent/releases/tag/v0.4.2

Fix appstream metadata

https://github.com/k0swe/kel-agent/releases/tag/v0.3.5

Fix flatpak app ID

https://github.com/k0swe/kel-agent/releases/tag/v0.3.4
xylo04_AT_gmail.com
kel-agent-0.4.6/assets/radio.k0swe.Kel_Agent.svg000066400000000000000000000073201425126170400213750ustar00rootroot00000000000000 kel-agent-0.4.6/assets/screenshot.png000066400000000000000000001614031425126170400175640ustar00rootroot00000000000000PNG  IHDRx|< zTXtRaw profile type exifxk {Y^c9(A>v{f3@䜾ǖ(Vzοy)ɘ}VQ.f|=gTޫ­ʹ~~s^>gJ~3_??9@iSڿ#k,+5o`sz8sؿ {.SK95ı _~r!kuHH)&WT~\J;?r(xs27ſ}8~]߽>{gu:)_^{ŃZ;]|L=d|w^-{|y;oBl *qoQV5=ouUR7VwNdb痰q( S7MOZAP4O>C/n tAǜ\SWYqXsʤVZ3næ- }v۲{>{m >|r=@ӎ~ƙgPۮ~ǝw]Z~>ߣQ+/R=D9 J >]4sk  U!(%`;?r?p+=߁ rPp"eIMc@ա5eLu$(:ɔ5/g/DeRet#eZ^dQx:g{)ץ!,?ut*p9ua2>P;t(/oC6@xEHF@;E([7dfl;ZEfo$\h >N8 zW̢pK{1Z,'MՆ Hhf&7%Q (Cl<"ZC 39\wa5[@!U3LtCn4CxeyTP!+akM A5R?zaJ7tvixca HB) hb'?nܠ\gzq[#5IC_bS ):F4 ?~GKByñWe } 4 &uOdUS@pc1) 6f@ib3%3i;CO^v4jؠe //*b<WZϿ?ϘiCCPICC profilex}=HPOS";tP,8jP! :GhҐ8 .κ: x_Rh8{B4c tL'b6"^х_f1+I)=Ryߟի-Df6:Ԧmp'tAG+q.,̈IGb+mJF.Ш94N3p:0IzŎm⺥){0dȦJAZB7倁[g՛[@f"e}n4ortWJ iTXtXML:com.adobe.xmp tfbKGD pHYs  tIME6* IDATxiX>{]pA@@ (DsI&.Dh0ѣ19jQ&qǕ#." ?  3x+Wi%.icBthw>u<BWxJ!5M-a%DADDDDDDDDDDDDDDD"""""""""""""""" DDDDDDDDDDDDDDDD<4'xi8N """""""""""""""pADDDDDDDDDDDDDDD8Hq"""""""""""""""" DDDDDDDDDDDDDDDDN sHt ӦMCJJ 1tPan7ydz聢"IÔ)SR7oƚ5kX34hQ4b !"""""""""""""""e(J8x֦æM)kr=$pab?/_fi!'x GAA[Qz$''###CkӑQ5jĚPOC,f""""""""""""""m'w.@Zl\v ;w!Ju&K.< i 2J'x`ppp(X,ЧO̞=O<}}}|"""""""""""""z "f͂}vݻ000@PcРAXre$,o4~p [nիqy?;v@xxx뛦Me˖<%aggObĉۣo߾5tuu . !!gϞ7y___thܸ1+W`޽8u'.BCCggg@$NXXrssUV8p`Ѹq s\p[lիW+߷~-ZTIX<@dd$[D"""""""""""""" U[[[gyo޼^g,zGZZ\ܑI&ɝܡ1c`e> qQ,X{R""" ey$$$`ѢE(..O+++|prrHRȨFǨH$hذ!bbbеk2YXX /pq^DDDDDDDDDDDDDDo 'xdff ?{zz֫!ѳgOʍaaae˖½{G\\:u}6oߎ/"?? CPPXÇ*saii T*e=41b0 !!K.EaaGp\|&L@.]еkW;vL+}۶mۑTns~^Ď;PPPPGBB1m4ǣuֈmNxPUyL0Ν;|3g" ~~~o.'S:rHnn.^|VH +Ǣ"̜9YYY8p lقh;oMȖgINNFFFF'aoogϞ!** wܑKKK 4ŋLP//ﯖ<#G_ddd|9Ə҉O>vH$jժu@ċғ;dR)֯_{<1ur;?𻻻;[/"""""""""""""n߾>GT*zիWcժUڵ+bqcccP,M6Evv6?T@6lPRVZ@'X\jϲH$J&xTÇfffuRƙ3g NʢXZZ""""""""""""""z0++ s_ 8!!!066 ᫯¥K]A__/_Q}ĭ[n6m_Fjj*LLLn^Rhܸ1ݻ|ҿa``֭[eIƕ~_}ںmlll7|5k (( 35kx̘1Wʘ%dQjQFXd0(Y#))I1===&=h:H???m۶YFFfffhժN>]/2W^4\rVMAjj*͛+Vј3g˖ƻVqqqhӦ rrr0wZG)..˱~zaذa6lXmrrr'NԫJόADDDDDDDDDDDDDĵr%FLMM4!͚5CXXy"gϞj(80ydRSS~z@`` BCC~B$4i@˖-ѦMʕ+D"ҥK!h"|8~8Ξ=D,_C Çܗ:Crssi #xt~~~Xf >|X۶m+|Ν:K?fΜ ]ݒlذ֭ѾиqcHRvލ!C+W֭[qM8::}6m*i C޽GEDDz聦M6ػw/~jOPϣs?.4```/_?Pq9r:uB1`$''ԩS+**Bll,F#FtΝC^^^FNL2yyyXv'"..qqq022B~Я_rDzqFlذ+V@-sӦMpvvFΝJ-**իW{J'>>T*՞+?@ll۶m[Gvv6n޼'NСC m>*-[DÆ k!J|UY?6m*o믙9H$Ç!ߊ ٿR6mmBBBФIC96m`غu+VZJ_eהYPPPӧ#22O>Ŏ;X?kٲ%BBBЮ];4n"HIIeX1IThJ㧚w}S&_Tu?X9s&k 22RmշmTkee(**Ǐ]v޽{ g۶mvZ LvH]ѡCr5mM6Exx8~gYFX,FLL BCC{FШQ#[8>SSSm۶0o>ߠK$edd5D'ZuۼysL4 mڴ)%,-->fBjjƕouԇ;n8xxxx @WmQiԨ3266X\)L^bʔ).\И fffBWfE5Uշ#Gq3f ƍT-qU>3 _ ٳgo}{5kܹwaE%QG{ 5jMG|}SvzߦշmTs~0~rZ興ذaJSXX#"O -Z `nnBHLLDzz: I&2drssFEE +ݻ 44mڴ1i$VU߇~w}W-nbb.]P|eDֱٽ{7N8!ogee-M6ŪU`hh"=z'N!`kkp /ƨQ1[|քVݻwMMQ}TӷS2ʤ핓>3-߰ăhѢ:B;__Su}۲e ڶm ___L2FR[[4?300ҥKy&<7ovvvGNбcGob{FGARRJJ*@/Oߎ#M);(~v &yyy:ujȋj6~o߾8qPo7n܈˗/CGG...8p ڴi#Fk֬{lgV(YPP#ԅ/_c@DD*Qn;sssܻws)+55_ '''DFFb׮]rg'k;v`gqE|g@DDmVTq|_!;;[]$aذa* ˗?^qE*=ɪ޻w"!!X`߿_k׮ѣ2dƌccc 2SQL'| )Sӧ%ӫAT4* mlLLLCCC - m۶E@@`ѢE|@Wj{(h"lܸ0`~gj*_m91c֭[7o_pG˖-T;4WBN"Su{)㧚w}S&_T^e;w*%{]TTsYz5~m?Uv6ncǎ\t QQQe"i<|'N@tt41d>}gϞ.]TeH?/_. 2{nz e:t?/^ }}}t Ç ϕ+WVMBBt.]ï"ԩS8u𻎎nPd˕$&&*K}Ya+V@$UzuV :Zr,'OF۶mq,^7oެV&MBqq1p]M*Q}6DPv@ٴň-n߾~AɮWDUj@R2ko1|p < k*Ax~+WpDZFM?ռ뛲4|UUv Y-ZT}Kqq1/_OOO?;*"9/Rmc[?9r/;wRbvvvngaa֭[ ay7۶mC.]`ff///>}>>ukڴ)Zl @YTzvePl ^*";;DhLI&SN; IDAT777WӪ`֬Y000/RFU[_ꇥ%aggObĉޘx= oooX[[CWWYYYpju#ittt닎;7qݻZ粮.BCCggg@$NXX-}}}8p0zhܸq~fffx9.\-[իoE-*x"##UZXv-O?S68rol^1x5Ν jЮ];%5ՠADDDk׮PۡCsEQv,бcG5>233qU>|ǏWh_Fw߅4iBܻwIII䆂ֆEmǎx`nnݻzY)7J{@- H+??gzoH$˗/wijz{_666ؼysCttt)cСC u{aJUxm\z5pYDGGWtٻヒs*tI{;;;lܸƎ 777<7nĞ={}A`mm۷ocʕ8Zn4rTs"4 ^U9~xz]J"TM^~۷cĉhӦ 5kVaC럗0>u=$jjbA,S? cccm۶VNEOd,٭+DKe*:j,{$ӤIX[[Çޏ?{nnȵ-P?JOIN1c0`rydccѣX`AiEYW4h,--+;#!!-t_I˃QQ!HаaCĠk׮n/Bᇺ۸pZnplڴrݻ7HJJҘiJ{'Lq-iy$[%==;<<<0gwyݻwi{ꊩSѱgְF׮]qE̝;=cܸqhРA޽{#::}i3_Gn|o3g5kVMMM OOO{7oV>QGtA_:J[&ڭ[f?ڵÿ 'A6icƌjܡH$B6mcccp}`ܸq-Z… 1bĈJ'(@[/OC׷r?M㧊W+=yʿbĉqd0)̙3عs';DD{͚~V*oFl-77ʷR)n߾  uUq|$ѳgOGPEkWfff9s&b1rsszZiUfT*52Pz&)P?,,,l2!33QQQr,'pml߾/^D~~> @OOZ[ׇ#;v )))̄<<<п#447W_Xnv؁/`8yݼ|SSSt^^^ALL Μ9S'N,_hh0Sۤ sNnM47+"$$p ÉUJ4j֬LH]v oj wǎOrPEZh%K@___cXXXSN )=k+W7o"//C`` <<<0rH|r +++bǎ8}4rssѬY3… 1zh~^/o7:T[fͰrJacǐG:uBXXLMM`̘1緅=LMM@MhkO !+_!_S_~:t@1j($''֭[eWSL!>}e˖iuO<|D;v, 1zhHLLDPPahh}b͚5*=SPvi>jzzkUvL~ ={33JVu_>BTTT3G!<cƌIhӦ?ecƌk5ke˖x"fϞ T*9###jJU^9~?MiWg*^eoZZJWWW/M6p}C@It)E)C&xXZZ}ɝ) \ ooo(ySo߾زeKdoO.99sU7?L>gpvv3"""/`ժU5zù&}Ӱaʽ#|HHH|Iၴ4a_)ez~"wٳg,o $ŋ+m~oc.\X]7??^qO>֭[1vXH$j gϞp:EER)֯_%KNi<3gΔ?]m  :t^leQd˳?҇4uѿ MlD"Nf͚ɓ'5k܁doo/ :$dlϞ=»v4۷m6L4I1c7r;JuњN>iӦUx|Xhl]]]V:CUַ |e^z͛nZnrױl2̚5 4@ddd$Pb`߿ %}]̘1.]b{e4Eaܹprr|u }~UM{$[qJW\Rpvv𡕲hrT}?eϚ^M uV'N@pp0̄iݺ5 YIu뇳p;v bԦD&M+V!e_ŋၔܹϟGvv6Э[7 6 ?>FYs{4`Ϟ=ݻ7"##qI >FFFs[_Ek[i*/ʝl*hrT?eϚ^MWUFuSǏǕ+WвeK;666ػw/޽ W^ӧO*,MWWSNtRvH=cz .7o4ej$CZZ5kϟyCvahmdUA__/_TɈD~~~iiiHKKÑ#G7Ğ={}?-[vvvpwwGnХKtXdU7M=5=ܹ3}Xӫ 8x`5iӦpZf-ϊ:uuuѸqc#00P[ `ggKK20 ,Y[[WǪ,ŋֶq>ӧ닰 'xC$r#U[^u#F,Y΢(t=s9"E²߸qC)TxxxkG=zT̯_D"ڵkZ}z u2o7oV+..ƙ3gлwoֶuX__nnnꫯEUy6oތK.aٲeH$};w٣mo>4l@I8ZYHZydo%ko߾]T:jQTDoFn {uEmo.狪[Ekdo59Jo@"o_phҤ tuuQXX*887W{eQE@6_ e&_WƟؽ{7v]6M4T`ʔ)HOONJ+"":=z`ԩ8~8.\,5k&w68::(YUQ9>uVa2(,,n,Mzȑ#^^^fϚ^X www;|5~&w 55Ê+`cch̙3Gea常8i999;wny)..˱~zaذa6lXmrrr'Nԫah8T={࣏>?LLL0aaasUŠ.^6(=lnn^0깹BN6dff ?ԩI&/o:::055ʿ(W^A ?|yZ@JS)???sׯΜ9Ǐ׋@[/O뎲Oo~Pӫ)y.KYh{{{HR,X/4QrGxx8Ü9s5ܹsMcN^=== ǧjb={Ph?ylmm5'}PVGr.F6YYf;w3͛7ꁲBjrz5~`HMMb裏ӧO[öpuuUIheMвeKi@gH$Xt)D"-ZDwww ++ .\ÇZgTLoi{bGݱsN} u͙3G*LD7o.^rr]HRb4m7nܨܹgggBOOO?Pv{p%!}~~~Ֆ"HxӧeEt(ve 6lٳgxk!Hйsg;vLJި:oK{iSe+%I"}`ZeJ,,,0ydtR,^ڵg}ׯk㵱L(=ӺSMS&߯ji]lbkת\z/*8iwC ALL b1>/!^x!g6`  dcMOռиqcHGf~>}Zkbbnݺ)mNbveӆ'OƊ+0bJo[x^my(>6m~城=* 2~*9ƜـL])}Vusmnn> kkkݻÖ-[0sLDEE!..;vPxIL뿳F[VVF,U=*UݿRFǗ*+=5++KxC'??_@d=zVPAvvp R{x٭[7@uOudT*7@y}KLLPTϠAlwd}2⵱? R "P2[oS{iSe{;Sv...U7ӦMD"'OGx˗0113{eRv@큶_=e:o޷|5~U]WO|  >H$;w߃.+93f ƌعs' ]~=aii)tH4x`~ae*$''W+/** WF>}*}#F,7o*<6D:u*fϞhaՊmVի: t>Q77߯VTUNɓ'&X?d;$?>rrr`hh3fҞ_}BtrrQt qQ\e4h???[DYg``;;;!** ѣGqUdffB$ ?BڦWGGGHJJ&~/5|RVU*62o+aff *geeպ4%uѣGXlbcc1c`ʕnn:}/ۇddd@GGVVVƍWC>#F@"`ؼy3޽ SSSn:::XjٳΆ)F+++=zO>D"A֭ ܾ}7}޸q:uBXX pIdeeiӋ*zuc2dlmmrJlݺ7o궣#ڷoiӦ ΝÝ;w#GwAqq1٣?uWi ͛7(͛ DիWcǎ8w ޽{iii E{Sv{pu,]111044̙3ѿ9roFAA,--`}صkA5kÇ֭[000/Å>7|Ǐ<_SgHje^ߊ0{lXFFF5kǏT[zzz(..Ɨ_~Y&6Uq|Xx1f͚777͛q9Yf߿={Ikl5lw˗^vBzz:ЩS't٘3g<÷ݻwԩSe>OLL70x`={Z矦IU㧚5#M?޷|~m?UAӧp(Jaoo@l=zˍ'L9}4*+Whܸ,"oo2SǏV{GNZ[X"ɘ5k *o۶m*w޽{#::/_Āj5SY{C 򍘛7obܹ D"}Qo*eggcٲeHJJW|7o:w,c :Tժj_J %o׶IUEPɓ'#,, @ɒƢ{(..ԩS ƖnFTVܹs^ /e~j퍸J缼+STTWbroU8pK*ԖޠVl͛oooM6 'OT{'OϞ=:(SG{US0,&&F[֬YWWW߿ .wb1&L ?/..ݻqF!:qeu111ݥKhѢ*ۗYf!44;vp?_ʕ+::__SW ""~)^zA Q*oNN{wVU}A%EEp@PquE9kYfZ{R3EL񆠢&b(("_{ys}6kk`ɒ%.S\Ƙ!ӧ̙S={믿ۧ/b}/U))kXv-T*U_~K,ANN0]ĉRqڴiZjTL2nqF899ɓ'2eJgXm0s8|`ȑ/;w`„ ZiHYȩI?]v-<==˯/Sϯ`ĈU`Jlٲg0ٶmZhQ8 5L+HcǎaذaС3\~DTTTSI>)GFEE{._aa!lق{O>ٳ'\]]aii tD=z111j_̓#GO>:M6!233SNСC zcY t=YzzzDJJ V^uZQCNml?Gмys̝;o&+6l؀_~AAAҥ annl… tYa̙=z4===ŋ8r'><Ə1cƠW^Cnn.RSSUvw܁)T*U\===頻#R1G`` >իWq)$$$4H&&&ApppxB֬Y.]ʕ+1cF]C%,, AAAx!^u05%4 HIIy!;h%"`xzzʕ+|4߿huo"ܚ5kvaԩ< $~!((%%%Xzu`|#"""""""xHqPJ 8]%""xzz@p022<{4n߾> 0f̘j'&N: xرOf|#""""""""j@!///ڢXx[%""mz ׷ڪUٳ' _DQtt4?O$m+WGw}FDDDDDDDD0kpqqso>旈:t(qXB6q񍈈^d oooDDD ,, &&&gEEE˃!ϳ}vl۶tH߾}۷o`0`۷Ov^0ы<1JNNN!rrrׯ#66ǎCVV1xɜH8H8H8H8H8H8H8H8H8H8Hy[oq!%%'Nd~ʘ;w.~شiS폪6|pO˗/TfB&MclݺƍѶmj6l={VsCCCdiiJe`sAllFom۶rYXX`ݺuuJ#~昁G9r111JBÇT*EHQ[R/((HqܹW^E~~>T*Zj///IFlhabs??? 3޽{8<[b>Oх11111N}( ?x ޽>@ڵϛ5k&[[[̞=]v OشiFP* Ð!C}޴iS4mT8P ,@fff"}puu+:w <@CGiV ymK/!44:u𝍍 lllЭ[7kXx1\"[zn;sƛb_)ʇ+ \77777ݪ뇹s P?ХVaG۶mrJXYYGETTݻcccW^A-0vX`˖-5Yf ٳسgn߾ kkkbȐ!ԩ>cHI&aȑZ9Pb?0226 Ǐk--[Ć `ll"DGG#&&P(GPP|||`kkUVaʔ)wlom4Y[nU[F_ʇA>Z9Dȑ#8v \]gn-)׼ysፖTA[~5b|yy . {nP*)SСC̙3wA|||y.t>pqq_}ŷ <`ee۷ocٲeAr %K7GsJ:tmm6g~ 111o̙1bn޼ɓ'WFGGG >ް>?~DDD?8 A>}tH >mu}%Kݻ?~𹭭-F]ݻw Ԛ7o۷aٵkcȐ!@֭ann.\3U:th$v qAԩSqu@n0bO>EBBv؁Wj-Z 88^^^ppp)xk[n޽;ѬY3 ##ؿƱHO@^羾9r$ڴiSoƍh۶mU;w`„ ly6m]kkk >z=pm;v {xT孾+boŊ!.\{|~I_·XHR51/tP(н{whڴ) W8~r-R]b_}}}|T۷o z*=R<{ߞ7ydt k=At)U DII T*Sɓn,s: OԣSRRXRK]CՔP\NNs.]*2;;;ܽ{W}뭷[nomK &&@"On ˖-CV}naaOOOxzzb裏*ѿ O 7nݻW{77jϵM_~5ּys4oDtt4V\Yc'˖-UtaذaQ333C ̙3ahhXnJ"=lQ+V@\ȦT*4iaaaӧO{ҥKk섎5 ovkM4A&MкukƍذabccרGHHF  ؠwݻ7"""gDן.˃* UO4m/2|  ٳq+oRH՟ilME\bOxO//W>ڴiyٹwvvvC>}pE|Uc)r-bs1˯z>kkkhѢ>|XD)=}ooooooeKXn:ܸqڵ= 5@PūYjJJ$''ãJJiRĠAh>zP㧭%Z_MXZZbѢEP*֭[z~qABŢ\+mڴAHHҥMJ+^%55UDfff"..ׯ_ѪU+_^ ** ߇z聡C+W… +4h۶mJRu.Sv'*T*|ranrr2vލ/"//:t(,XmWn;wBZZѣG`:{>N*zu>}߇\\\Caʬ]CO?vI% IDAT?`޽ͅ}]t 7@nnS{饗0c 8;;ڵkݻwaaa zzz 竼ԥKJW"''pvv/S'W_ܿ.\tF 7obΝZt1֮] WW:_3g,F!C;ª|[!_Y?Ѽys`޽8wrrrЪU+1ppp'|SAʛT+uҥK$cǎܬSuWMEO&e}*;^>=$eelx7]?$&&"** IIIͅz~oVXʯC\{y0w\deeU.88@| 111113=s΅B}o Nl 5@Pp^춶Pn4BٳK.aBKMM;ӧMްAqqqki s^^郀k* w.\ ))I,(NNNE`` ̐e˖֭[ht=o޼*;vT!C~@Pܹs8{l˯G~dYǏ7˴(J,]fff(..]n3g`XjT*ϟ^{ 6 777aZEhݺ5LMM3ݽ4f|'O>DDD`͚5qy\|3f̀?¾h"Y›G۱l2ajl];?~<?~UVժ"%%+[~&C!""B.55|֮];"$$v Ef̘B]k"""h"CRW^U׿UmT`ӦM:th7k.xyya8vXc| 1aݻW`{]ѣG{~ZFsrrٳZIռys9s}=z$|bѭ[7899aIվ!DW=IQyIQޤO8R>1SCR_ˆ/Z|{˖- zrkXtt4mۆ7x֭kʯC\[XXKbժU͛?"""B/2v튋/b׮]Z} 111113a._fffHHH_~)oNCklll0j(cjj FOy{{#((&&&ÅەSi-`k*8b?###̟?=z􀕕`ff֭[cԨQo1}:K~cضmVXQFٳg/oڵkhv=GEE7U{imS~?O̯\ˇ :t ]v9`K;wVh_υ:q z7l'㪦޾ׯ_pF tvUVU9ŋ@7h a}U9urrr7~>}ӧѣUyփJPĮtXן8w>Jޅτs?dȐ+oRHߤ_\k>[{C/>elx&1m4aIuUxU֍7`jpHݾS|Ƚ&Er {=gƮ]bڴi1cT;sTS>777777ByUVx/^,ir.7< `bbO~v{eTG)CMW^j7=x ޽ 6`ҤIB+iӦi-hڴ)|||*z￱~$$$6 ڵkN.WN |r=z$|y& @ On{1ag~P(}^յܠ:#=IIIVbȖ-[j-** @ b|c˖-|T .eQ1T*3Q6>|ϟdRڟeoF3fhs}3H}]?]q޽jGt,obHٟiLME6S7*}KO/:^2ɧ|ױ~ٷT~tVP( ɓ'¨Q0vX@dddoNK}Znx~Z[ҧ͊Ϟ=É'4/66&L@^^^)FSRR#G/^yoZꡮ}ޏ?;v pwwG߾}???DEEaUz==ٳ'T;U+^ĉv*bbbСC S"]II Ο?`XYY޾:nW\#`dd^zIC7ST*!>>*[SNBʕ+ګh֬Y#_`u>O?UaCP;;*i޼4H:KJJj>33066rm۶{hݺ5BBB0tP?ϟGBBjԌбcG )M'>ן.kL/@Pѱtj5IQF}*Od%|ʇV{ttt״r*RW؞̟?~~~r ̙[nK.G~`ffٳgcȐ!XhQKrOt'O^Z~jC?ХrСC1qDoh2b٘:u*Zn۷o#33SN=.kx%} ڬU%a,YJ}_[Y\nr>r%Y۷/ %P(0f%%%ɶL;mսӧc9r$T*z) F\\?G[R[ zaÆsB'(ܻwOe6>h4[Vu~uIj5IQF}*#^}x)x&Q!Tm^Pk\ʯT.'cҥř3gh"|_Gƫ www_oFx 11111K-`(J޽)a7xcJR^6͛7B \~~~Zj~'uQxCiU¿[ljȑ#oݻ.\h4 -9_Ϻ_???3f!ˇzylmx)%^2ɣ|e/j^Uc4h|}}+VTZgb˖-|2>aĉذaCm~ hҤ W^+RoVZ%{ؽ{FK.t-i4c7oJ%?O>DfZ]zzzpvvP~>m jSpb&euҤcyLcoR/b_-oO/>e[d|kQ3ܾ׵Jn\l$$$ ##mcccq9t }t>777777R]o5ΫT.[VoGšУGj̉hMXĠAmǯ:/w#KF\vM$]&YUV:tYN}饗r_5ʩ|١]vcǎ!%%=z@utue0:^^^'OΝ;Ww@  ex -[vZ,,, nдiӦh|J5k@P>ƍqq矈_|chpXP&[&JL۷GNP[XE#DvBya2վLcoڮ_{շj}*Vy[dREorO_tIHO^t}/8ڮ:]~---jt*^rO۲e0nܸh"7}jrkcǎEXXJ%>K Tff&Ο?x嗫 bܸqJCR,uf͚Nk} ѪU+Q;wN+577G߾}5ڶls 31g|WVVsS[b]0zha8pv؁Ea֬YX|9݋gϞt˗/ÇkkklipaU7ɤ$v}+۾hݺWS~ Vj_z=IQHyշb}*fy[dԟh;t0xfܸqBצMa *.T}BT"%%jǃsN@Ϟ=Zl.(۷Ohؾprr4~L8Ǐz$...¹k4i&Mvqq^TztڵUpp0>S?ב>|xߪsU...5kҷJv`孾+[R77]O,orl2^6dd|W6m$ s̩eKKK,^ʺA{oȩ{< lٲgL-*MM rK===̙3B ߄UʪrJ+W`׮]=z4 [[[Oulll$JRɩteOZ&MZba= &gz(`bbYfa̘1իWB1B8O>Qo~F~p1!8%%%Xvm+]՞_XTҲyRyeS{qϑ\+UK-Ovi޽;;>ɓ'CR/q-XXXcǎӫtPWTT%K૯ /^ DEE!-- ѣ `Ŋ=z> F&$$ 88;wjpuuEn0|pt  #4h;իuml߾&LJ7|{"..pvvFpp0lQb|Muq)<~\.**BNNe;pPAAApttĉ1zh=zqqqHJJBff&`ee.]_B@AA~'?DFF,,,0e"::>JBǎ1d899!99͚5V.//;v,~zܹIIIB5i/Ƙ1cdƒ/iii VӾ%^&f"'FyӵT&x)eCK7y7׬Y0cѢE5j9d>>>8p LLL}Uo7R_B<=z Bnꫯ ?3._L(Jl3f J%ʕ+k<6ݻK}C}s:##뱨 ,ԩS/ήڷ5␛[=LMMccc;cǎ/))}WxU>rm6n={DHHBBB^pc$''cĉh߾}P:oŊ~Vҗ˗c011#0bĈ u_lٲ_}ڶmc(v}ۼy3ڴiooo8;;_WyyyiT׈]v={Dv*|_PPc ZӾ%^&f믾MSʛ\%ۧ /ןѣG 5<<vѩS'hfffGpU=zNhb=Ѕ111113It-)_GGG?;HLLd~HXj<==uV|6΍`aa___L>&&&Appp{7m۶Xpak?`hh~g|Wo?CDD:/߈f͚ 3LׯNP0Jc"h%""jHĕ+WwU<[ZZߏCLLL~&ɟ[K7""b|c|#" xHqPJ 8tr{旈H<==ٳJ$"ݧP(bt4^2&y[[[ o2DDD$'O|}}ѤIl 믍r \j/_^wÆ ógxb߈HVn {{{I ѣG/*""Æ 6n܈m۶ܹsHKKoXYYի Ν;͛78pgϞv|(D?CDDoDDDoDDbڻw/T*$Vx{{N""""0!//qؾ};mۆboߎM6TAghhñfd""""""""-P^;KDDDDSwHj]vBp۷W+++dffƍDdd$JJJ4ޗS?,--q5ݻc޽sNݏ"}roڴ)6m*hY`233ݗ鳰+\]]ѹsg@_8p@*PѣGZ=~`nn 6}饗N:U666֭^{5,^W\p=7T~yxxx)׹+E 333(Cwն=.=zTMhŨLMMtR...pqq#k4ci'Km۶ʕ+aeeB=zQQQwW^y-Zرc-[f͚%L9{,ك۷o2d:uꄏ?(,,t?RoҤI9rV9h>}X```t阘jǏߖ-[bÆ 066FQQT( #((>>>ŪU0eܻwO66,֭[-#rͯT僨.:{A]ы^_-\>>>(((ݻCTSLA0gܹsZOK>߈H~* pssn߾e˖UX6ʕ+8x ֭[L0Ó'O#]tAPP`޽/RSS/߇BBBk׮J%E[P_Ϟ=É'j^ŋ8{Ym{-DDDM6Xr%RSS}5DGGcر6m0vX]V66ܐ}w5ہ;w.0|,"v~*Duaaa@j^C޽;`͚5M#..f_֭[cҤI5kbO7""""""" <Ñr7_UV=z@DDDdĉwbn???aسg3gp$9**J%"<~@j(ꫯP(L?ܹƍJBjܧ6omlee9ss￱j*$%%5h~kP`uWAƌ1 X>T+ET/B}U۹sg|СJǶm۰pBxzzBOOi'*ɏ~e9ršJUngmm;~jrڵ ~~~Ν;ӧm-[Dh6}ǯM."v~n###*JXnFW=zڵk˗W_kddŋ?s_](}Œ%Kp]?^#GD׮]}}}dggݻxjÇvvvǏQOOݺuCf͚HLLku"$$}􁣣#W"##믿"''߷o_5/Ğ={j* wƿ7m۶~Ws&LP߿?Ѷm[T*dgg͛8y$~7dgg7XעE  055Evv6?qqqڧX׳=֭[5%>cDFFjM@Q ,@ 1DTBP@gΜ#F͛۷Ȩc)EZՖ~]XRR"(j9yd&$$ ++ fffܹs7TLKIIAbbbKq.Rլ _M ¯\ .]-Zwޕe~z-8::֭[o}~T>̄23g΄amT*ܡiӦ_p7o͛cʕ+WuGHHF  ؠwݻ7"""g|zxx`ٲe*0l0WD$$$cǎ /R0` _u9srr²eЪUr[XX?~<>z jԨQx+ jҤ 4i֭[#$$7n [ļ*om۶x>}cǎi=4%&111\O?q޽{bC[WT]9ԧmڴy\;;;;١O>x">Cܿ_[^^rY[[{ҥKk"ɭ ǼEU*OHџ3}DDDDDDDDU%)qqqPvKjjj,..Frr2<<<*(EITbРA4O㧭%Z_MXZZwaWU2 $ʠ$"^|-+̛c* MB(WEAyOL//skgZOuuu/^Q#vE:5BB+V [K9$6ٔíEN+,+';3 K.IY]]K.4 ^<3Yti:v옡Cf0`@6lQFSNI;yG2mڴ̝;77N=2bĈj*C |]w5_~y#ߟٳguo2p3foNϞ=ӱc\Gy3a„̘1#K,Ivҿ|ѣGFqRf\y֭[^z<y뭷ҢEr!{SSS:+SLYg}rַh6>cy~6{ne…خ6lx-r@]mrv0m۶Iƨ;6̳> .pl֬YѣG}޿Իwi&+VXZqj-]4x`}si޼y,Y~;ӦMw߽YkoJy?:lMZhv)}Ӵi̚5+_|qxO}E-|+@׾󉵝?z>뮻Ow\䥗^[bŊ?5Ww믿~SO0!ɪNMNìֺJerR[5h  Ju][m6p$7tz4iR:SUUU/X,EӦMsgnV>ͩoIrwdŊiذaڷo_j_nBrۘȞ$fY=ۣG$)Եk5}mcƌk,.goO+y%+fK׷|0SLY믽ZV6mVh*ݺuK&Mlٲ|iժUFN8!IlpfJ=(Uh5jT$Kw%K$ǃd3gfwטڶAJ*eiԨQ>Fo9䓳tfΜ3g桇ʿ]v9rwiM-]wuӴit![:3a„\~\zkWmݺu$qwk7t:rߐ=#ɪ/ko֠A~?7n={[niҡCifNuuԎ,~W6j{ҥs)dOL޿4o<˗//!3f๿rL2%ÇOVҾ}̚5x 7 _Bvc9&C͔)S2eʔ<ә1c y>oN}KVM_SOo߾:thnOl3lذTUUe]J>[n /:*7;\ѣN>=Oӽ{L>=Iҽ{$Y9UQ=zHÆ $?pV\OS6T֕+Wfiݺufzmms4i$w^ 9oW^0`@>4m4cǎ͐!Cr^˹uUСC3jԨ$Fcnߵ?_hQN;t5of,XPzv̦tٔ/Njj]֒Pロk&^xasA}q+W̒%Kdɒ̙3'ӧOύ7ޘC9$w3pl9s>usۿ(-Rn gn:IҰa{)TUU#^{UJ9swo]`mT5nܸoMq-OӦMs[nYڙ*{FJo@jx,Z(cƌI'>:͛7O~JEeԩyG3q,]rշZ~{N:e=\c ww(o9=(}zjjjҵk$9yEiOWY%RjC kmR׷m.(}ɓO>?TjC-W_}uFSN9%[=zq+{PW0`@ƎdT^{Fv~*=裏x\}kW:vX]MIҩSz)z衇Ҿ}=:_> +5wOjFrwo 什~lu=@o޼y.R/I&eڴiyWKСzCmw:}|&5w<2dH9z뭥uӧO{y;6vd-ɪYU~w]z>}dwOnݲۖ:ؾ媫C=Tos%<ywҮ]qkvm$;[ڃ9sf.\Mf]wͽ[a6ϧW^Ν;:vYO~wK[o[S}vX dܸqk O-Y$_}{\zi׮]Fk⿧˽uQAsIuuu}\zvƍgw믿mkjjҹs$7Xv̙3|KuYti/_ lp r.&NѣG'I{OMv碗:[lR[iSYϛ7/G}tY>K.{쑅 ?AXxq}.gȐ!ԩSzꕿIC=4yw2y->|]K.͢Efߨm۶s{y/_'|2O>d麼^{e9CҼy|ϢE\;Wpi֬Y>$fJSnpTPrrʼKիWve$IϞ=c֗gy&?n5֏ߏo-ўoEoER׷r׷"WIr!$I~̛7oN<؁EO{j6lȋ/NN˗'Ion{؎ݿvaI6mt_o> m[޺X{^}MYZ;cZ*RywyR=x?4׏ӧ'IZhQl{$I\ e]֘f^{lp;It<7n\o#8^J뮻|4jԨԑժU1wqGr+w{PQnڎ+V_5j(;F#F(m֬Y:蠍vt\|K$9&g}v~_SO-TyW?W_PgٲeI/e=76٘j4iҤXvyo$jݺuvyt5+Vآ~uڵ0aBaÆ9raRa&MpKs={/ɪet|?=XR0hРd޼yyG[ۃr׏r]iӦرc:w+W:,X3g&Y5;*]?*y-i?/I.\Xѫ׌r+]z葁n~+XQǯ*q}d}D{9w-{S:$Y'=]>駟O?=Irmeܸq|x~ӟnpnRG̿fvZ5*'tR?y̘1# 4Ȱar衇&Y={~^y,\0ɪYF:/N< 0`.;vlUk}3u]/]t)M_I^n$>tԩ4ǽ޻QᴢAG9䠃JuuufΜYZ~'Y@t޽ἮGG%%ɲeo&Iz-Zl׿uwg^-[\k]r+QVjsom@SN ۷o=]}WԩSgvѣӽ{\N_[̙{/mڴ)>>:駟ó^{m۶{V~R|0zj7o_7x#-ZHϞ=SSSkD{/+Ygm&~Fz(Z>裴i&O=4i$}J׷=~V߉'K߾}s){ӟ{. ,Huuu:uꔁOuuu}<W߫@T{)cƌqWz'r9?7uQfɹ 7~iӦNdɒ 2d >UUU>|xlpݼy<&MZv~ak:kG?J~J={vN<čZ2Rj'F!nnHD:(^xas?YנAvi9cSSSmN+2Z_ݻw.uK,\/]w5wkݾ:gyf9昵rq_`̘1kPo4sȑ# 3Oƍ({^v;t /4c̟???O6\}fԨQ?m7n3^|D}[݈#owo=D(/8p@?Os=;nX:rGk(R3W^ye>ϭ<^ _B: .g?Y]y5j/Irw+ب_K3pTt}+w{QF]-֊+>1Ƌ/.har*P<.kcEHI&#VZ?+/&LX,ܿJ?a„~[|\[s_~֭[ZldU?'Cr3ʁXZzKF`̘1#?xt[|uW&{gfΜ/|ϓ(K(Ry5\[n%Æ K^ұc4k,-ٳ3mڴ<m^kʔ)9餓rǧi׮],YYf=SZ#}cJ_bE<9ꨣҳgϴl2?9>lc~-_jUŋ|ҥKoo Sat, 11 Jun 2022 21:50:24 -0600 kel-agent (0.4.4-1) UNRELEASED; urgency=medium * Added the ability to send client commands to WSJT-X -- Chris Keller Thu, 18 Nov 2021 12:07:00 -0600 kel-agent (0.4.3-1) UNRELEASED; urgency=medium * WSJT-X listening address fixes * Added flag for selecting a custom config file -- Chris Keller Mon, 15 Nov 2021 17:11:00 -0600 kel-agent (0.4.2-1) UNRELEASED; urgency=medium * Add configuration by file -- Chris Keller Mon, 01 Nov 2021 20:26:00 -0600 kel-agent (0.3.5-1) UNRELEASED; urgency=medium * Fix appstream metadata -- Chris Keller Mon, 31 May 2021 21:43:00 -0600 kel-agent (0.3.4-1) UNRELEASED; urgency=medium * Fix flatpak app ID -- Chris Keller Mon, 31 May 2021 21:09:00 -0600 kel-agent (0.3.3-1) UNRELEASED; urgency=medium * Flatpak build is now hermetic -- Chris Keller Mon, 31 May 2021 19:25:00 -0600 kel-agent (0.3.2-1) UNRELEASED; urgency=medium * Add Freedesktop metadata to flatpak -- Chris Keller Sun, 23 May 2021 22:41:00 -0600 kel-agent (0.3.1-1) UNRELEASED; urgency=medium * Add flatpak build -- Chris Keller Sat, 22 May 2021 15:52:15 -0600 kel-agent (0.3.0-1) UNRELEASED; urgency=medium * Update wsjtx-go for parser stability -- Chris Keller Wed, 19 May 2021 22:34:32 -0600 kel-agent (0.2.4-1) UNRELEASED; urgency=medium * Update Forester domain in default allowed origins -- Chris Keller Wed, 03 Feb 2021 09:20:32 -0700 kel-agent (0.2.3-1) UNRELEASED; urgency=medium * Fixes bug: multiple JSON docs in one message (https://github.com/k0swe/kel-agent/issues/22) * Debian build improvements * Mac build improvements -- Chris Keller Sun, 03 Jan 2021 11:05:09 -0700 kel-agent (0.2.2-1) UNRELEASED; urgency=medium * Use sbuild chroot for debian builds -- Chris Keller Mon, 21 Dec 2020 19:31:10 -0700 kel-agent (0.2.1-1) UNRELEASED; urgency=medium * Add Windows release packaging -- Chris Keller Mon, 14 Dec 2020 16:58:53 -0700 kel-agent (0.2.0-1) UNRELEASED; urgency=medium * Add version info to websocket messages -- Chris Keller Mon, 14 Dec 2020 15:05:23 -0700 kel-agent (0.1.6-1) UNRELEASED; urgency=medium * New .deb file automation in GitHub -- Chris Keller Mon, 12 Dec 2020 22:10:34 -0700 kel-agent (0.1.5-1) UNRELEASED; urgency=medium * Test new Makefile release workflow -- Chris Keller Mon, 09 Dec 2020 12:35:52 -0700 kel-agent (0.1.4-1) UNRELEASED; urgency=medium * Improved websocket client handling * Remove default origins if flag is set -- Chris Keller Mon, 07 Dec 2020 16:01:33 -0700 kel-agent (0.1.2-1) unstable; urgency=medium [ Chris Keller ] * Initial release (Closes: 973707) * Added manual page * Updated dependencies [ tony mancill ] * Added myself to Uploaders * Bumped Standards-Version to 4.5.1 -- Chris Keller Sun, 29 Nov 2020 13:22:37 -0700 kel-agent-0.4.6/debian/control000066400000000000000000000027201425126170400162200ustar00rootroot00000000000000Source: kel-agent Maintainer: Debian Go Packaging Team Uploaders: Chris Keller , tony mancill Section: hamradio Testsuite: autopkgtest-pkg-go Priority: optional Build-Depends: autorevision, debhelper-compat (= 11), dh-golang, golang-any (>= 2:1.15~1), golang-github-adrg-xdg-dev, golang-github-imdario-mergo-dev, golang-github-k0swe-wsjtx-go-dev, golang-github-leemcloughlin-jdn-dev, golang-github-mazznoer-csscolorparser-dev, golang-github-rs-zerolog-dev, golang-gopkg-yaml.v2-dev, golang-websocket-dev Standards-Version: 4.3.0 Vcs-Browser: https://salsa.debian.org/go-team/packages/kel-agent Vcs-Git: https://salsa.debian.org/go-team/packages/kel-agent.git Homepage: https://github.com/k0swe/kel-agent Rules-Requires-Root: no XS-Go-Import-Path: github.com/k0swe/kel-agent Package: kel-agent Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends} Built-Using: ${misc:Built-Using} Description: Web interface for amateur radio installed programs (program) An agent program for translating between various amateur radio installed programs and WebSockets. This was built to support https://github.com/k0swe/forester but can be used by any web application that needs to communicate with amateur radio installed programs. kel-agent-0.4.6/debian/copyright000066400000000000000000000047701425126170400165570ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: kel-agent Upstream-Contact: Chris Keller Source: https://github.com/k0swe/kel-agent Files: * Copyright: 2020 K0SWE License: Apache-2.0 Files: hub.go client.go Copyright: 2012-2013 Gorilla web toolkit 2014-2015 The Gorilla WebSocket Authors 2012-2013 Gary Burd License: BSD-2-clause Files: debian/* Copyright: 2020 Chris Keller License: Apache-2.0 Comment: Debian packaging is licensed under the same terms as upstream License: Apache-2.0 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at . http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. . On Debian systems, the complete text of the Apache version 2.0 license can be found in "/usr/share/common-licenses/Apache-2.0". License: BSD-2-clause Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: . * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. . THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. kel-agent-0.4.6/debian/gbp.conf000066400000000000000000000000621425126170400162310ustar00rootroot00000000000000[DEFAULT] debian-branch = debian/sid dist = DEP14 kel-agent-0.4.6/debian/gitlab-ci.yml000066400000000000000000000024551425126170400172000ustar00rootroot00000000000000# auto-generated, DO NOT MODIFY. # The authoritative copy of this file lives at: # https://salsa.debian.org/go-team/ci/blob/master/config/gitlabciyml.go image: stapelberg/ci2 test_the_archive: artifacts: paths: - before-applying-commit.json - after-applying-commit.json script: # Create an overlay to discard writes to /srv/gopath/src after the build: - "rm -rf /cache/overlay/{upper,work}" - "mkdir -p /cache/overlay/{upper,work}" - "mount -t overlay overlay -o lowerdir=/srv/gopath/src,upperdir=/cache/overlay/upper,workdir=/cache/overlay/work /srv/gopath/src" - "export GOPATH=/srv/gopath" - "export GOCACHE=/cache/go" # Build the world as-is: - "ci-build -exemptions=/var/lib/ci-build/exemptions.json > before-applying-commit.json" # Copy this package into the overlay: - "GBP_CONF_FILES=:debian/gbp.conf gbp buildpackage --git-no-pristine-tar --git-ignore-branch --git-ignore-new --git-export-dir=/tmp/export --git-no-overlay --git-tarball-dir=/nonexistant --git-cleaner=/bin/true --git-builder='dpkg-buildpackage -S -d --no-sign'" - "pgt-gopath -dsc /tmp/export/*.dsc" # Rebuild the world: - "ci-build -exemptions=/var/lib/ci-build/exemptions.json > after-applying-commit.json" - "ci-diff before-applying-commit.json after-applying-commit.json" kel-agent-0.4.6/debian/kel-agent.manpages000066400000000000000000000000231425126170400201730ustar00rootroot00000000000000assets/kel-agent.1 kel-agent-0.4.6/debian/rules000077500000000000000000000011331425126170400156720ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/dpkg/pkg-info.mk BUILDTIME = $(shell date --utc +'%Y-%m-%dT%H:%M:%SZ') GITCOMMIT = $(shell autorevision -f -o autorevision.cache -s VCS_SHORT_HASH) VERSION = $(shell autorevision -f -o autorevision.cache -s VCS_TAG | sed 's/v//g') GO_LDFLAGS := "-X 'main.Version=v$(VERSION)' -X 'main.GitCommit=$(GITCOMMIT)' -X 'main.BuildTime=$(BUILDTIME)'" %: dh $@ --builddirectory=_build --buildsystem=golang --with=golang override_dh_auto_build: dh_auto_build -O--buildsystem=golang -- --ldflags $(GO_LDFLAGS) override_dh_auto_install: dh_auto_install -- --no-source kel-agent-0.4.6/debian/source/000077500000000000000000000000001425126170400161145ustar00rootroot00000000000000kel-agent-0.4.6/debian/source/format000066400000000000000000000000141425126170400173220ustar00rootroot000000000000003.0 (quilt) kel-agent-0.4.6/debian/upstream/000077500000000000000000000000001425126170400164545ustar00rootroot00000000000000kel-agent-0.4.6/debian/upstream/metadata000066400000000000000000000003371425126170400201620ustar00rootroot00000000000000--- Bug-Database: https://github.com/k0swe/kel-agent/issues Bug-Submit: https://github.com/k0swe/kel-agent/issues/new Repository: https://github.com/k0swe/kel-agent.git Repository-Browse: https://github.com/k0swe/kel-agent kel-agent-0.4.6/debian/watch000066400000000000000000000003661425126170400156520ustar00rootroot00000000000000version=4 opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%kel-agent-$1.tar.gz%,\ uversionmangle=s/(\d)[_\.\-\+]?(RC|rc|pre|dev|beta|alpha)[.]?(\d*)$/\$1~\$2\$3/" \ https://github.com/k0swe/kel-agent/tags .*/v?(\d\S*)\.tar\.gz debian kel-agent-0.4.6/flatpak/000077500000000000000000000000001425126170400150145ustar00rootroot00000000000000kel-agent-0.4.6/flatpak/.gitignore000066400000000000000000000000661425126170400170060ustar00rootroot00000000000000/build-out/ /.flatpak-builder/ kel_agent.flatpak repo kel-agent-0.4.6/flatpak/radio.k0swe.Kel_Agent.yml000066400000000000000000000060221425126170400215150ustar00rootroot00000000000000app-id: radio.k0swe.Kel_Agent default-branch: main runtime: org.freedesktop.Platform runtime-version: '20.08' sdk: org.freedesktop.Sdk sdk-extensions: - org.freedesktop.Sdk.Extension.golang build-options: append-path: /usr/lib/sdk/golang/bin env: - 'LDFLAGS=' command: kel-agent finish-args: - --share=network modules: - name: kel-agent sources: - type: git url: https://github.com/adrg/xdg.git tag: v0.4.0 dest: vendor/github.com/adrg/xdg/ - type: git url: https://github.com/davecgh/go-spew.git tag: v1.1.0 dest: vendor/github.com/davecgh/go-spew/ - type: git url: https://github.com/gorilla/websocket.git tag: v1.5.0 dest: vendor/github.com/gorilla/websocket/ - type: git url: https://github.com/imdario/mergo.git tag: v0.3.13 dest: vendor/github.com/imdario/mergo/ - type: git url: https://github.com/k0swe/wsjtx-go.git tag: v4.0.1 dest: vendor/github.com/k0swe/wsjtx-go/v4/ - type: git url: https://github.com/leemcloughlin/jdn.git commit: 6f88db6a6bf2176674100d1a1c692596fcf6e2ba dest: vendor/github.com/leemcloughlin/jdn/ - type: git url: https://github.com/mattn/go-colorable.git tag: v0.1.12 dest: vendor/github.com/mattn/go-colorable/ - type: git url: https://github.com/mattn/go-isatty.git tag: v0.0.14 dest: vendor/github.com/mattn/go-isatty/ - type: git url: https://github.com/mazznoer/csscolorparser.git tag: v0.1.2 dest: vendor/github.com/mazznoer/csscolorparser/ - type: git url: https://github.com/pmezard/go-difflib.git tag: v1.0.0 dest: vendor/github.com/pmezard/go-difflib/ - type: git url: https://github.com/rs/zerolog.git tag: v1.27.0 dest: vendor/github.com/rs/zerolog/ - type: git url: https://github.com/stretchr/testify.git tag: v1.7.2 dest: vendor/github.com/stretchr/testify/ - type: git url: https://github.com/golang/sys.git commit: 9f5ed59c137dcb0852024edd2e71af63c2d67707 dest: vendor/golang.org/x/sys/ - type: git url: https://github.com/go-yaml/yaml.git tag: v3.0.1 dest: vendor/gopkg.in/yaml.v3/ - type: dir path: .. # - type: git # url: ssh://git@github.com/k0swe/kel-agent.git # tag: v0.4.6 # commit: a2c6d56f7c99b662277886859287a00ec8000b7a buildsystem: simple build-commands: - cp assets/modules.txt vendor/ - make - install -D kel-agent /app/bin/kel-agent - install -Dm644 assets/radio.k0swe.Kel_Agent.svg /app/share/icons/hicolor/scalable/apps/radio.k0swe.Kel_Agent.svg - install -Dm644 assets/radio.k0swe.Kel_Agent.metainfo.xml /app/share/metainfo/radio.k0swe.Kel_Agent.metainfo.xml - install -Dm644 assets/radio.k0swe.Kel_Agent.desktop /app/share/applications/radio.k0swe.Kel_Agent.desktop cleanup: - /lib/debug kel-agent-0.4.6/go.mod000066400000000000000000000005351425126170400145030ustar00rootroot00000000000000module github.com/k0swe/kel-agent go 1.15 require ( github.com/adrg/xdg v0.4.0 github.com/gorilla/websocket v1.5.0 github.com/imdario/mergo v0.3.13 github.com/k0swe/wsjtx-go/v4 v4.0.1 github.com/rs/zerolog v1.27.0 github.com/stretchr/testify v1.7.2 golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d // indirect gopkg.in/yaml.v3 v3.0.1 ) kel-agent-0.4.6/go.sum000066400000000000000000000074111425126170400145300ustar00rootroot00000000000000github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/k0swe/wsjtx-go/v4 v4.0.1 h1:Pjf72bDrunhEnxUjR+DAZpvK/n02WlxUk/m0vjgJzrk= github.com/k0swe/wsjtx-go/v4 v4.0.1/go.mod h1:DTlv5rvH9eHJHiwD53j9SKAv0cHtGYNw8WTrOceBMAk= github.com/leemcloughlin/jdn v0.0.0-20201102080031-6f88db6a6bf2 h1:CdyD5OzAIzNFzpJ9WQRjJWj4pVRxZ9v15xdHnhvUPdw= github.com/leemcloughlin/jdn v0.0.0-20201102080031-6f88db6a6bf2/go.mod h1:LAowglanJPLb6WYSx3D1Ht/XE54OGIr0i4mz9kdbXrs= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mazznoer/csscolorparser v0.1.2 h1:/UBHuQg792ePmGFzTQAC9u+XbFr7/HzP/Gj70Phyz2A= github.com/mazznoer/csscolorparser v0.1.2/go.mod h1:Aj22+L/rYN/Y6bj3bYqO3N6g1dtdHtGfQ32xZ5PJQic= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs= github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d h1:Zu/JngovGLVi6t2J3nmAf3AoTDwuzw85YZ3b9o4yU7s= golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= kel-agent-0.4.6/integration/000077500000000000000000000000001425126170400157155ustar00rootroot00000000000000kel-agent-0.4.6/integration/receive/000077500000000000000000000000001425126170400173375ustar00rootroot00000000000000kel-agent-0.4.6/integration/receive/README.md000066400000000000000000000002151425126170400206140ustar00rootroot00000000000000When the fake WSJTX server emits the `.bin` file to kel-agent, kel-agent should send the corresponding `.json` data to the websocket client. kel-agent-0.4.6/integration/receive/clear.bin000066400000000000000000000000261425126170400211150ustar00rootroot00000000000000WSJT-Xkel-agent-0.4.6/integration/receive/clear.json000066400000000000000000000002301425126170400213130ustar00rootroot00000000000000{ "version": "kel-agent v0.0.0 (abcd)", "wsjtx": { "type": "ClearMessage", "payload": { "id": "WSJT-X", "window": 0 } } } kel-agent-0.4.6/integration/receive/close.bin000066400000000000000000000000261425126170400211340ustar00rootroot00000000000000WSJT-Xkel-agent-0.4.6/integration/receive/close.json000066400000000000000000000002051425126170400213340ustar00rootroot00000000000000{ "version": "kel-agent v0.0.0 (abcd)", "wsjtx": { "type": "CloseMessage", "payload": { "id": "WSJT-X" } } } kel-agent-0.4.6/integration/receive/decode.bin000066400000000000000000000001041425126170400212470ustar00rootroot00000000000000WSJT-XY?ə~JA2EJP N4BP 73kel-agent-0.4.6/integration/receive/decode.json000066400000000000000000000005631425126170400214610ustar00rootroot00000000000000{ "version": "kel-agent v0.0.0 (abcd)", "wsjtx": { "type": "DecodeMessage", "payload": { "id": "WSJT-X", "new": true, "time": 39435000, "snr": -5, "deltaTime": 0.20000000298023224, "deltaFrequency": 1302, "mode": "~", "message": "JA2EJP N4BP 73", "lowConfidence": false, "offAir": false } } } kel-agent-0.4.6/integration/receive/heartbeat.bin000066400000000000000000000000551425126170400217700ustar00rootroot00000000000000WSJT-X2.2.20d9b96kel-agent-0.4.6/integration/receive/heartbeat.json000066400000000000000000000003341425126170400221710ustar00rootroot00000000000000{ "version": "kel-agent v0.0.0 (abcd)", "wsjtx": { "type": "HeartbeatMessage", "payload": { "id": "WSJT-X", "maxSchemaVersion": 2, "version": "2.2.2", "revision": "0d9b96" } } } kel-agent-0.4.6/integration/receive/logged-adif.bin000066400000000000000000000005661425126170400222020ustar00rootroot00000000000000 WSJT-X\ 3.1.0 WSJT-X T3ST JK73 FT8 -8 -9 20201030 120816 20201030 120916 40m 7.075950 K0SWE DM79LV 5 Comment Jess T3STR kel-agent-0.4.6/integration/receive/logged-adif.json000066400000000000000000000013161425126170400223750ustar00rootroot00000000000000{ "version": "kel-agent v0.0.0 (abcd)", "wsjtx": { "type": "LoggedAdifMessage", "payload": { "id": "WSJT-X", "adif": "\n\u003cadif_ver:5\u003e3.1.0\n\u003cprogramid:6\u003eWSJT-X\n\u003cEOH\u003e\n\u003ccall:4\u003eT3ST \u003cgridsquare:4\u003eJK73 \u003cmode:3\u003eFT8 \u003crst_sent:2\u003e-8 \u003crst_rcvd:2\u003e-9 \u003cqso_date:8\u003e20201030 \u003ctime_on:6\u003e120816 \u003cqso_date_off:8\u003e20201030 \u003ctime_off:6\u003e120916 \u003cband:3\u003e40m \u003cfreq:8\u003e7.075950 \u003cstation_callsign:5\u003eK0SWE \u003cmy_gridsquare:6\u003eDM79LV \u003ctx_pwr:1\u003e5 \u003ccomment:7\u003eComment \u003cname:4\u003eJess \u003coperator:5\u003eT3STR \u003cEOR\u003e" } } } kel-agent-0.4.6/integration/receive/qso-logged.bin000066400000000000000000000002411425126170400220670ustar00rootroot00000000000000WSJT-X%wHT3STJK73knFT8-3-75CommentJoe%vT3STRK0SWEDM79LV1B1DIONkel-agent-0.4.6/integration/receive/qso-logged.json000066400000000000000000000011631425126170400222740ustar00rootroot00000000000000{ "version": "kel-agent v0.0.0 (abcd)", "wsjtx": { "type": "QsoLoggedMessage", "payload": { "id": "WSJT-X", "dateTimeOff": "2020-10-30T11:29:57Z", "dxCall": "T3ST", "dxGrid": "JK73", "txFrequency": 7075950, "mode": "FT8", "reportSent": "-3", "reportReceived": "-7", "txPower": "5", "comments": "Comment", "name": "Joe", "dateTimeOn": "2020-10-30T11:28:57Z", "operatorCall": "T3STR", "myCall": "K0SWE", "myGrid": "DM79LV", "exchangeSent": "1B", "exchangeReceived": "1D", "propagationMode": "ION" } } } kel-agent-0.4.6/integration/receive/status-222.bin000066400000000000000000000001631425126170400216570ustar00rootroot00000000000000WSJT-XkFT8-15FT8sK0SWEDM79LVDefaultkel-agent-0.4.6/integration/receive/status-222.json000066400000000000000000000012501425126170400220560ustar00rootroot00000000000000{ "version": "kel-agent v0.0.0 (abcd)", "wsjtx": { "type": "StatusMessage", "payload": { "id": "WSJT-X", "dialFrequency": 7074000, "mode": "FT8", "dxCall": "", "report": "-15", "txMode": "FT8", "txEnabled": false, "transmitting": false, "decoding": false, "rxDeltaFreq": 883, "txDeltaFreq": 1950, "deCall": "K0SWE", "deGrid": "DM79LV", "dxGrid": "", "txWatchdog": false, "submode": "", "fastMode": false, "specialMode": 0, "frequencyTolerance": 4294967295, "txRxPeriod": 4294967295, "configName": "Default", "txMessage": "" } } } kel-agent-0.4.6/integration/receive/status-231.bin000066400000000000000000000001671425126170400216630ustar00rootroot00000000000000WSJT-XkFT8-15FT8sK0SWEDM79LVDefaultkel-agent-0.4.6/integration/receive/status-231.json000066400000000000000000000012501425126170400220560ustar00rootroot00000000000000{ "version": "kel-agent v0.0.0 (abcd)", "wsjtx": { "type": "StatusMessage", "payload": { "id": "WSJT-X", "dialFrequency": 7074000, "mode": "FT8", "dxCall": "", "report": "-15", "txMode": "FT8", "txEnabled": false, "transmitting": false, "decoding": false, "rxDeltaFreq": 883, "txDeltaFreq": 1950, "deCall": "K0SWE", "deGrid": "DM79LV", "dxGrid": "", "txWatchdog": false, "submode": "", "fastMode": false, "specialMode": 0, "frequencyTolerance": 4294967295, "txRxPeriod": 4294967295, "configName": "Default", "txMessage": "" } } } kel-agent-0.4.6/integration/receive/wspr-decode.bin000066400000000000000000000001111425126170400222360ustar00rootroot00000000000000 WSJT-X@klsK6TGWCM95kel-agent-0.4.6/integration/receive/wspr-decode.json000066400000000000000000000005511425126170400224470ustar00rootroot00000000000000{ "version": "kel-agent v0.0.0 (abcd)", "wsjtx": { "type": "WSPRDecodeMessage", "payload": { "id": "WSJT-X", "new": true, "time": 45480000, "snr": -18, "deltaTime": -0.5, "frequency": 7040115, "drift": 0, "callsign": "K6TGW", "grid": "CM95", "power": 23, "offAir": false } } } kel-agent-0.4.6/integration/receive_test.go000066400000000000000000000015671425126170400207360ustar00rootroot00000000000000package integration import ( "encoding/json" "fmt" "io/ioutil" "testing" "github.com/k0swe/kel-agent/internal/ws" ) func (s *integrationTestSuite) TestReceive() { tests := []string{ "clear", "close", "decode", "heartbeat", "logged-adif", "qso-logged", "status-222", "status-231", "wspr-decode", } for _, tt := range tests { s.T().Run(tt, func(t *testing.T) { input, _ := ioutil.ReadFile(fmt.Sprintf("receive/%s.bin", tt)) want, _ := ioutil.ReadFile(fmt.Sprintf("receive/%s.json", tt)) _, _ = s.fake.SendMessage(input) _, got, err := s.wsClient.ReadMessage() s.Require().NoError(err) wantObj := &ws.WebsocketMessage{} err = json.Unmarshal(want, &wantObj) s.Require().NoError(err) gotObj := &ws.WebsocketMessage{} err = json.Unmarshal(got, &gotObj) s.Require().NoError(err) s.Require().Equal(wantObj, gotObj) }) } } kel-agent-0.4.6/integration/send/000077500000000000000000000000001425126170400166465ustar00rootroot00000000000000kel-agent-0.4.6/integration/send/README.md000066400000000000000000000002151425126170400201230ustar00rootroot00000000000000When the websocket client sends the `.json` file to kel-agent, kel-agent should send the corresponding `.bin` data to the fake WSJTX server. kel-agent-0.4.6/integration/send/clear.bin000066400000000000000000000000271425126170400204250ustar00rootroot00000000000000WSJT-Xkel-agent-0.4.6/integration/send/clear.json000066400000000000000000000001601425126170400206240ustar00rootroot00000000000000{ "wsjtx": { "type": "ClearMessage", "payload": { "id": "WSJT-X", "window": 2 } } } kel-agent-0.4.6/integration/send/close.bin000066400000000000000000000000261425126170400204430ustar00rootroot00000000000000WSJT-Xkel-agent-0.4.6/integration/send/close.json000066400000000000000000000001351425126170400206450ustar00rootroot00000000000000{ "wsjtx": { "type": "CloseMessage", "payload": { "id": "WSJT-X" } } } kel-agent-0.4.6/integration/send/configure.bin000066400000000000000000000001011425126170400213110ustar00rootroot00000000000000WSJT-XJT9KI6NAZDM03kel-agent-0.4.6/integration/send/configure.json000066400000000000000000000005161425126170400215240ustar00rootroot00000000000000{ "wsjtx": { "type": "ConfigureMessage", "payload": { "id": "WSJT-X", "mode": "JT9", "frequencyTolerance": 4294967295, "submode": "", "fastMode": false, "trPeriod": 0, "rxDF": 4294967295, "dxCall": "KI6NAZ", "dxGrid": "DM03", "generateMessages": false } } } kel-agent-0.4.6/integration/send/freetext.bin000066400000000000000000000000541425126170400211650ustar00rootroot00000000000000 WSJT-XJ72IMS K0SWE R-15kel-agent-0.4.6/integration/send/freetext.json000066400000000000000000000002271425126170400213700ustar00rootroot00000000000000{ "wsjtx": { "type": "FreeTextMessage", "payload": { "id": "WSJT-X", "text": "J72IMS K0SWE R-15", "send": true } } } kel-agent-0.4.6/integration/send/halttx.bin000066400000000000000000000000271425126170400206430ustar00rootroot00000000000000WSJT-Xkel-agent-0.4.6/integration/send/halttx.json000066400000000000000000000001711425126170400210440ustar00rootroot00000000000000{ "wsjtx": { "type": "HaltTxMessage", "payload": { "id": "WSJT-X", "autoTxOnly": false } } } kel-agent-0.4.6/integration/send/heartbeat.bin000066400000000000000000000000551425126170400212770ustar00rootroot00000000000000WSJT-X2.2.20d9b96kel-agent-0.4.6/integration/send/heartbeat.json000066400000000000000000000002641425126170400215020ustar00rootroot00000000000000{ "wsjtx": { "type": "HeartbeatMessage", "payload": { "id": "WSJT-X", "maxSchemaVersion": 2, "version": "2.2.2", "revision": "0d9b96" } } } kel-agent-0.4.6/integration/send/highlightcallsign.bin000066400000000000000000000000671425126170400230270ustar00rootroot00000000000000 WSJT-XKM4ACKkel-agent-0.4.6/integration/send/highlightcallsign.json000066400000000000000000000003761425126170400232330ustar00rootroot00000000000000{ "wsjtx": { "type": "HighlightCallsignMessage", "payload": { "id": "WSJT-X", "callsign": "KM4ACK", "backgroundColor": "#7FFFD4", "foregroundColor": "black", "highlightLast": true, "reset": false } } } kel-agent-0.4.6/integration/send/location.bin000066400000000000000000000000401425126170400211420ustar00rootroot00000000000000 WSJT-XDM79jxkel-agent-0.4.6/integration/send/location.json000066400000000000000000000001741425126170400213530ustar00rootroot00000000000000{ "wsjtx": { "type": "LocationMessage", "payload": { "id": "WSJT-X", "location": "DM79jx" } } } kel-agent-0.4.6/integration/send/replay.bin000066400000000000000000000000261425126170400206320ustar00rootroot00000000000000WSJT-Xkel-agent-0.4.6/integration/send/replay.json000066400000000000000000000001361425126170400210350ustar00rootroot00000000000000{ "wsjtx": { "type": "ReplayMessage", "payload": { "id": "WSJT-X" } } } kel-agent-0.4.6/integration/send/reply.bin000066400000000000000000000001041425126170400204660ustar00rootroot00000000000000WSJT-X? )FT8 CQ K0SWE DM79kel-agent-0.4.6/integration/send/reply.json000066400000000000000000000004441425126170400206760ustar00rootroot00000000000000{ "wsjtx": { "type": "ReplyMessage", "payload": { "id": "WSJT-X", "time": 1234, "snr": -15, "deltaTime": 0.5, "deltaFrequency": 2345, "mode": "FT8", "message": "CQ K0SWE DM79", "lowConfidence": false, "modifiers": 0 } } } kel-agent-0.4.6/integration/send/switchconfiguration.bin000066400000000000000000000000411425126170400234240ustar00rootroot00000000000000WSJT-XIC-7300kel-agent-0.4.6/integration/send/switchconfiguration.json000066400000000000000000000002211425126170400236250ustar00rootroot00000000000000{ "wsjtx": { "type": "SwitchConfigurationMessage", "payload": { "id": "WSJT-X", "configurationName": "IC-7300" } } } kel-agent-0.4.6/integration/send_test.go000066400000000000000000000023731425126170400202410ustar00rootroot00000000000000package integration import ( "encoding/hex" "fmt" "io/ioutil" "testing" "time" "github.com/gorilla/websocket" ) func (s *integrationTestSuite) IgnoreTestSend() { s.primeConnection() tests := []string{ "clear", "close", "configure", "freetext", "halttx", "heartbeat", "highlightcallsign", "location", "replay", "reply", "switchconfiguration", } for _, tt := range tests { s.T().Run(tt, func(t *testing.T) { input, _ := ioutil.ReadFile(fmt.Sprintf("send/%s.json", tt)) want, _ := ioutil.ReadFile(fmt.Sprintf("send/%s.bin", tt)) err := s.wsClient.WriteMessage(websocket.TextMessage, input) s.Require().NoError(err) select { case got := <-s.fake.ReceiveChan: s.Require().NoError(err) s.Require().Equal(want, got) case <-time.After(500 * time.Millisecond): t.Log("timeout") t.Fail() } }) } } func (s *integrationTestSuite) primeConnection() { // Because this is UDP, the server doesn't have an address for WSJTX until WSJTX has sent the // server a message. clearMsg, _ := hex.DecodeString(`adbccbda00000002000000030000000657534a542d58`) _, err := s.fake.SendMessage(clearMsg) s.Require().NoError(err) _, _ = s.fake.SendMessage(clearMsg) s.T().Log("connection is primed for a send test") } kel-agent-0.4.6/integration/suite_test.go000066400000000000000000000033641425126170400204420ustar00rootroot00000000000000package integration import ( "net" "os" "strconv" "testing" "github.com/gorilla/websocket" "github.com/k0swe/kel-agent/internal/config" "github.com/k0swe/kel-agent/internal/ws" "github.com/rs/zerolog" "github.com/stretchr/testify/suite" ) const origin = "https://test.example" type integrationTestSuite struct { suite.Suite conf config.Config wsClient *websocket.Conn server *ws.Server fake *WsjtxFake } func TestIntegrationSuite(t *testing.T) { if os.Getenv("SCHROOT_SESSION_ID") != "" { // TODO: fix these tests for chroot t.Skip("These integration tests freeze when building in sbuild chroot") } suite.Run(t, &integrationTestSuite{}) } func (s *integrationTestSuite) SetupSuite() { zerolog.SetGlobalLevel(zerolog.Disabled) s.conf = config.Config{ Websocket: config.WebsocketConfig{ Address: "127.0.0.1", Port: 8081, // TODO: use OS-assigned port AllowedOrigins: []string{origin}, }, Wsjtx: config.WsjtxConfig{ Enabled: true, Address: "127.0.0.1", Port: 2237, // TODO: use OS-assigned port }, VersionInfo: "kel-agent v0.0.0 (abcd)", } var err error s.server, err = ws.Start(s.conf) s.Require().NoError(err) <-s.server.Started wsAddr := net.JoinHostPort(s.conf.Websocket.Address, strconv.Itoa(int(s.conf.Websocket.Port))) wsAddr = "ws://" + wsAddr + "/websocket" header := map[string][]string{"Origin": {origin}} s.wsClient, _, err = websocket.DefaultDialer.Dial(wsAddr, header) s.Require().NoError(err) } func (s *integrationTestSuite) SetupTest() { var err error s.fake, err = NewFake(&net.UDPAddr{Port: int(s.conf.Wsjtx.Port)}, s.T()) s.Require().NoError(err) s.T().Log("suite reports fake is connected") } func (s *integrationTestSuite) TearDownTest() { s.fake.Stop() } kel-agent-0.4.6/integration/wsjtx-fake.go000066400000000000000000000032161425126170400203310ustar00rootroot00000000000000package integration import ( "errors" "fmt" "io" "net" "os" "testing" "time" ) // WsjtxFake is a test double that acts like WSJTX. It connects to the wsjtx-go server as a client, // but is actually a stub controlled by the integration test cases. type WsjtxFake struct { t *testing.T conn *net.UDPConn ReceiveChan chan []byte stop chan bool } // NewFake initializes a new fake WSJTX program on an OS-assigned port. func NewFake(addr *net.UDPAddr, t *testing.T) (*WsjtxFake, error) { conn, err := net.DialUDP("udp", &net.UDPAddr{Port: 0}, addr) if err != nil { return &WsjtxFake{}, err } t.Logf("fake is connected to %v", conn.RemoteAddr()) w := &WsjtxFake{t, conn, make(chan []byte, 5), make(chan bool, 1)} go w.handleReceive() return w, nil } // SendMessage immediately sends the given payload out from the WSJTX fake. func (w *WsjtxFake) SendMessage(payload []byte) (int, error) { w.t.Log("sending message") return w.conn.Write(payload) } func (w *WsjtxFake) handleReceive() { b := make([]byte, 2048) w.t.Log("listening for receives") for { select { case <-w.stop: w.t.Log("stopping") close(w.ReceiveChan) _ = w.conn.Close() return default: _ = w.conn.SetDeadline(time.Now().Add(1 * time.Millisecond)) n, err := w.conn.Read(b) if err != nil { if err != io.EOF && !errors.Is(err, os.ErrDeadlineExceeded) { w.t.Log(fmt.Errorf("got an error while reading UDP: %w", err)) } } if n > 0 { tmp := make([]byte, n) copy(tmp, b[:n]) w.t.Logf("received %d bytes, putting on channel", n) w.ReceiveChan <- tmp } } } } func (w *WsjtxFake) Stop() { w.stop <- true } kel-agent-0.4.6/internal/000077500000000000000000000000001425126170400152065ustar00rootroot00000000000000kel-agent-0.4.6/internal/config/000077500000000000000000000000001425126170400164535ustar00rootroot00000000000000kel-agent-0.4.6/internal/config/defaults.go000066400000000000000000000010041425126170400206040ustar00rootroot00000000000000package config import "runtime" var defaultConf Config func init() { var defaultWsjtxAddr string switch runtime.GOOS { case "windows": defaultWsjtxAddr = "127.0.0.1" default: defaultWsjtxAddr = "224.0.0.1" } defaultConf = Config{ Websocket: WebsocketConfig{ Address: "localhost", Port: 8081, Key: "", Cert: "", AllowedOrigins: []string{ "https://forester.radio", }, }, Wsjtx: WsjtxConfig{ Enabled: true, Address: defaultWsjtxAddr, Port: 2237, }, } } kel-agent-0.4.6/internal/config/parse.go000066400000000000000000000060201425126170400201120ustar00rootroot00000000000000package config import ( "flag" "io/ioutil" "os" "path/filepath" "strconv" "strings" "gopkg.in/yaml.v3" "github.com/adrg/xdg" "github.com/imdario/mergo" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) const ( appName = "kel-agent" ) var defaultConfigFile *string var configFile *string func init() { c, _ := xdg.ConfigFile(filepath.Join(appName, "config.yaml")) c = filepath.Clean(c) defaultConfigFile = &c } func ParseAllConfigs() (Config, error) { // Flags take precedence, and need to be parsed first for logging level conf, err := parseFlags() if err != nil { return Config{}, err } file, err := parseConfigFile() if err != nil { return Config{}, err } if err := mergo.Merge(&conf, file); err != nil { return Config{}, err } if err := mergo.Merge(&conf, defaultConf); err != nil { return Config{}, err } log.Debug().Msgf("effective configuration is %v", conf) return conf, nil } func parseFlags() (Config, error) { var conf = Config{} flag.StringVar(&conf.Websocket.Address, "host", conf.Websocket.Address, "websocket hosting address") flag.UintVar(&conf.Websocket.Port, "port", conf.Websocket.Port, "websocket hosting port") flag.StringVar(&conf.Websocket.Key, "key", conf.Websocket.Key, "TLS key") flag.StringVar(&conf.Websocket.Cert, "cert", conf.Websocket.Cert, "TLS certificate") var origins sliceFlag flag.Var(&origins, "origins", "comma-separated list of allowed origins") configFile = flag.String("config", *defaultConfigFile, "path to the configuration file") debug := flag.Bool("v", false, "verbose debugging output") trace := flag.Bool("vv", false, "trace debugging output") flag.Parse() conf.Websocket.AllowedOrigins = origins c, _ := filepath.Abs(*configFile) configFile = &c var ll zerolog.Level switch { case *trace: ll = zerolog.TraceLevel log.Trace().Msg("TRACE output enabled") case *debug: ll = zerolog.DebugLevel log.Debug().Msg("DEBUG output enabled") default: ll = zerolog.InfoLevel } zerolog.SetGlobalLevel(ll) // hosting address backward compat if i := strings.Index(conf.Websocket.Address, ":"); i >= 0 { port, err := strconv.Atoi(conf.Websocket.Address[i+1:]) if err != nil { return Config{}, err } conf.Websocket.Port = uint(port) conf.Websocket.Address = conf.Websocket.Address[:i] } log.Trace().Msgf("flag config: %v", conf) return conf, nil } func parseConfigFile() (Config, error) { var conf Config dat, err := ioutil.ReadFile(*configFile) if os.IsNotExist(err) { log.Debug().Msgf("no config file found at '%s'", *configFile) if dat, err = yaml.Marshal(defaultConf); err != nil { return Config{}, err } if err := ioutil.WriteFile(*configFile, dat, 0o755); err != nil { return Config{}, err } log.Debug().Msgf("wrote default config to '%s'", *configFile) return defaultConf, nil } if err != nil { return Config{}, err } if err := yaml.Unmarshal(dat, &conf); err != nil { return Config{}, err } log.Debug().Msgf("using config file at '%s'", *configFile) log.Trace().Msgf("file config: %v", conf) return conf, nil } kel-agent-0.4.6/internal/config/sliceflag.go000066400000000000000000000004641425126170400207370ustar00rootroot00000000000000package config import "strings" type sliceFlag []string func (i *sliceFlag) String() string { return "my string representation" } func (i *sliceFlag) Set(value string) error { tokens := strings.Split(value, ",") *i = make([]string, 0) for _, t := range tokens { *i = append(*i, t) } return nil } kel-agent-0.4.6/internal/config/types.go000066400000000000000000000025051425126170400201500ustar00rootroot00000000000000package config type Config struct { Websocket WebsocketConfig `json:"websocket,omitempty" yaml:"websocket,omitempty"` Wsjtx WsjtxConfig `json:"wsjtx,omitempty" yaml:"wsjtx,omitempty"` VersionInfo string `json:"-"` } type WebsocketConfig struct { // Address is the IP or hostname from which to serve the websocket HTTP Address string `json:"address,omitempty" yaml:"address,omitempty"` // Port is the TCP port from which to serve the websocket HTTP Port uint `json:"port,omitempty" yaml:"port,omitempty"` // key is the path to the TLS private key file (needed only if serving securely) Key string `json:"key,omitempty" yaml:"key,omitempty"` // cert is the path to the TLS public certificate file (needed only if serving securely) Cert string `json:"cert,omitempty" yaml:"cert,omitempty"` // allowedOrigins are the web origins which are allowed by CORS AllowedOrigins []string `json:"allowedOrigins,omitempty" yaml:"allowedOrigins,omitempty"` } type WsjtxConfig struct { // Enabled is whether to listen to WSJT-X Enabled bool `json:"enabled" yaml:"enabled,omitempty"` // Address is the IP or hostname on which to listen to WSJT-X Address string `json:"address,omitempty" yaml:"address,omitempty"` // Port is the UDP port on which to listen to WSJT-X Port uint `json:"port,omitempty" yaml:"port,omitempty"` } kel-agent-0.4.6/internal/ws/000077500000000000000000000000001425126170400156375ustar00rootroot00000000000000kel-agent-0.4.6/internal/ws/client.go000066400000000000000000000072161425126170400174520ustar00rootroot00000000000000// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package ws import ( "bytes" "net/http" "time" "github.com/gorilla/websocket" "github.com/rs/zerolog/log" ) const ( // Time allowed to write a message to the peer. writeWait = 10 * time.Second // Time allowed to read the next pong message from the peer. pongWait = 60 * time.Second // Send pings to peer with this period. Must be less than pongWait. pingPeriod = (pongWait * 9) / 10 // Maximum message size allowed from peer. maxMessageSize = 512 ) var ( newline = []byte{'\n'} space = []byte{' '} ) var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } // Client is a middleman between the websocket connection and the hub. type Client struct { hub *Hub // The websocket connection. conn *websocket.Conn // Buffered channel of outbound messages. send chan []byte } // readPump pumps messages from the websocket connection to the hub. // // The application runs readPump in a per-connection goroutine. The application // ensures that there is at most one reader on a connection by executing all // reads from this goroutine. func (c *Client) readPump() { defer func() { c.hub.unregister <- c _ = c.conn.Close() }() c.conn.SetReadLimit(maxMessageSize) _ = c.conn.SetReadDeadline(time.Now().Add(pongWait)) c.conn.SetPongHandler(func(string) error { _ = c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) for { _, message, err := c.conn.ReadMessage() if err != nil { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { log.Warn().Err(err).Msg("websocket unexpectedly closed") } break } message = bytes.TrimSpace(bytes.ReplaceAll(message, newline, space)) c.hub.command <- message } } // writePump pumps messages from the hub to the websocket connection. // // A goroutine running writePump is started for each connection. The // application ensures that there is at most one writer to a connection by // executing all writes from this goroutine. func (c *Client) writePump() { ticker := time.NewTicker(pingPeriod) defer func() { ticker.Stop() _ = c.conn.Close() }() for { select { case message, ok := <-c.send: _ = c.conn.SetWriteDeadline(time.Now().Add(writeWait)) if !ok { // The hub closed the channel. _ = c.conn.WriteMessage(websocket.CloseMessage, []byte{}) return } w, err := c.conn.NextWriter(websocket.TextMessage) if err != nil { return } _, _ = w.Write(message) if err := w.Close(); err != nil { return } case <-ticker.C: _ = c.conn.SetWriteDeadline(time.Now().Add(writeWait)) if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { return } } } } // serveWs handles websocket requests from the peer. func (s Server) serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { upgrader.CheckOrigin = s.kelagentCheckOrigin conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Warn().Err(err).Msg("error upgrading websocket") return } client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)} client.hub.register <- client // Allow collection of memory referenced by the caller by doing all work in // new goroutines. go client.writePump() go client.readPump() } func (s Server) kelagentCheckOrigin(r *http.Request) bool { origin := r.Header.Get("Origin") for _, allowed := range s.conf.Websocket.AllowedOrigins { if origin == allowed { return true } } log.Warn().Msgf("rejecting websocket request from origin '%s'", origin) return false } kel-agent-0.4.6/internal/ws/hub.go000066400000000000000000000060211425126170400167430ustar00rootroot00000000000000// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package ws import ( "encoding/json" "github.com/k0swe/kel-agent/internal/config" wwrap "github.com/k0swe/kel-agent/internal/wsjtx_wrapper" "github.com/rs/zerolog/log" ) type WebsocketMessage struct { // Version is kel-agent version info Version string `json:"version,omitempty"` Wsjtx wwrap.Message `json:"wsjtx,omitempty"` } // Hub maintains the set of active clients and broadcasts messages to the // clients. type Hub struct { conf *config.Config // Registered websocket clients. clients map[*Client]bool // Inbound messages from the websocket clients. command chan []byte // Register requests from the websocket clients. register chan *Client // Unregister requests from websocket clients. unregister chan *Client // Wrapper for the WSJT-X connection wsjtxHandler *wwrap.Handler // WSJT-X message channel wsjtx chan wwrap.Message } func newHub(c *config.Config) *Hub { var wh *wwrap.Handler wsjtChan := make(chan wwrap.Message, 5) if c.Wsjtx.Enabled { var err error wh, err = wwrap.NewHandler(*c) if err != nil { log.Warn().Err(err).Msgf("couldn't connect to WSJTX") } else { go wh.ListenToWsjtx(wsjtChan) } } return &Hub{ conf: c, command: make(chan []byte), register: make(chan *Client), unregister: make(chan *Client), clients: make(map[*Client]bool), wsjtxHandler: wh, wsjtx: wsjtChan, } } func (h *Hub) run() { for { select { case client := <-h.register: h.clients[client] = true log.Debug().Msgf("Established websocket session with %v", client.conn.RemoteAddr()) case client := <-h.unregister: if _, ok := h.clients[client]; ok { log.Debug().Msgf("Disconnected from %v", client.conn.RemoteAddr()) delete(h.clients, client) close(client.send) } case command := <-h.command: log.Debug().Msgf("Command from client: %v", string(command)) h.handleClientCommand(command) case wsjtxMessage := <-h.wsjtx: h.broadcast(WebsocketMessage{ Version: h.conf.VersionInfo, Wsjtx: wsjtxMessage, }) } } } func (h *Hub) broadcast(message WebsocketMessage) { log.Trace().Msgf("broadcasting: %v", message) jsn, _ := json.Marshal(message) for client := range h.clients { select { case client.send <- jsn: default: close(client.send) delete(h.clients, client) } } } func (h *Hub) handleClientCommand(command []byte) { var msg = &WebsocketMessage{} if err := json.Unmarshal(command, msg); err != nil { log.Warn().Err(err).Msg("failed to parse client command; dropping") return } if msg.Wsjtx.MsgType != "" { // Don't know all the payload types here, so re-marshal just that and handle in wrapper payload, _ := json.Marshal(msg.Wsjtx.Payload) if err := h.wsjtxHandler.HandleClientCommand(msg.Wsjtx.MsgType, payload); err != nil { log.Warn().Err(err).Msg("failed to handle wsjtx client command; dropping") return } } } kel-agent-0.4.6/internal/ws/server.go000066400000000000000000000030721425126170400174760ustar00rootroot00000000000000package ws import ( "fmt" "net/http" "github.com/k0swe/kel-agent/internal/config" "github.com/rs/zerolog/log" ) type Server struct { conf config.Config hub *Hub Started chan bool Stop chan bool } const wsPath = "/websocket" func Start(c config.Config) (*Server, error) { if c.Websocket.Key != "" && c.Websocket.Cert == "" || c.Websocket.Key == "" && c.Websocket.Cert != "" { return &Server{}, fmt.Errorf("-key and -cert must be used together") } hub := newHub(&c) go hub.run() server := Server{c, hub, make(chan bool, 1), make(chan bool, 1)} secure := false protocol := "ws://" if c.Websocket.Key != "" && c.Websocket.Cert != "" { secure = true protocol = "wss://" } mux := http.NewServeMux() mux.HandleFunc(wsPath, func(w http.ResponseWriter, r *http.Request) { server.serveWs(hub, w, r) }) mux.HandleFunc("/", server.indexHandler) addrAndPort := fmt.Sprintf("%s:%d", c.Websocket.Address, c.Websocket.Port) log.Info().Str("address", fmt.Sprintf("%s%s%s", protocol, addrAndPort, wsPath)).Msg("Serving websocket") go func() { var err error server.Started <- true if secure { err = http.ListenAndServeTLS(addrAndPort, c.Websocket.Cert, c.Websocket.Key, mux) } else { err = http.ListenAndServe(addrAndPort, mux) } log.Fatal().Err(err).Msg("websocket dying") server.Stop <- true }() return &server, nil } func (s Server) indexHandler(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("Congratulations, you've reached kel-agent! " + "If you can see this, you should be able to connect to the websocket.")) } kel-agent-0.4.6/internal/wsjtx_wrapper/000077500000000000000000000000001425126170400201255ustar00rootroot00000000000000kel-agent-0.4.6/internal/wsjtx_wrapper/wsjtx.go000066400000000000000000000102771425126170400216420ustar00rootroot00000000000000package wsjtx_wrapper import ( "encoding/json" "fmt" "net" "reflect" "github.com/k0swe/kel-agent/internal/config" "github.com/k0swe/wsjtx-go/v4" "github.com/rs/zerolog/log" ) type Message struct { MsgType string `json:"type"` Payload interface{} `json:"payload"` } type Handler struct { wsjtxServ wsjtx.Server conf config.Config } func NewHandler(c config.Config) (*Handler, error) { ipAddr := net.ParseIP(c.Wsjtx.Address) if ipAddr == nil { log.Error().Str("address", c.Wsjtx.Address).Msg("couldn't parse WSJT-X IP address") return nil, fmt.Errorf("couldn't parse WSJT-X IP address %s", c.Wsjtx.Address) } log.Info(). Str("address", fmt.Sprintf("%v:%d", ipAddr, c.Wsjtx.Port)). Msg("Listening to WSJT-X on UDP") var err error serv, err := wsjtx.MakeServerGiven(ipAddr, c.Wsjtx.Port) if err != nil { log.Error().Err(err).Msg("couldn't listen to WSJT-X") return nil, fmt.Errorf("couldn't listen to WSJT-X: %s", err) } return &Handler{ wsjtxServ: serv, conf: c, }, nil } // ListenToWsjtx is a goroutine that listens for WSJT-X messages and puts them on the given channel. func (h *Handler) ListenToWsjtx(msgChan chan Message) { defer func() { h.wsjtxServ = wsjtx.Server{} }() wsjtChan := make(chan interface{}, 5) errChan := make(chan error, 5) go h.wsjtxServ.ListenToWsjtx(wsjtChan, errChan) for { select { case wsjtMsg := <-wsjtChan: log.Trace().Msgf("Received message from wsjtx: %v", wsjtMsg) msgChan <- Message{ MsgType: reflect.TypeOf(wsjtMsg).Name(), Payload: wsjtMsg, } case err := <-errChan: log.Debug().Err(err).Msgf("wsjtx error") } } } func (h *Handler) HandleClientCommand(msgType string, payload []byte) error { switch msgType { case reflect.TypeOf(wsjtx.HeartbeatMessage{}).Name(): var heartbeatMsg = &wsjtx.HeartbeatMessage{} err := json.Unmarshal(payload, heartbeatMsg) if err != nil { return err } return h.wsjtxServ.Heartbeat(*heartbeatMsg) case reflect.TypeOf(wsjtx.ClearMessage{}).Name(): var clearMsg = &wsjtx.ClearMessage{} err := json.Unmarshal(payload, clearMsg) if err != nil { return err } return h.wsjtxServ.Clear(*clearMsg) case reflect.TypeOf(wsjtx.ReplyMessage{}).Name(): var replyMsg = &wsjtx.ReplyMessage{} err := json.Unmarshal(payload, replyMsg) if err != nil { return err } return h.wsjtxServ.Reply(*replyMsg) case reflect.TypeOf(wsjtx.CloseMessage{}).Name(): var closeMsg = &wsjtx.CloseMessage{} err := json.Unmarshal(payload, closeMsg) if err != nil { return err } return h.wsjtxServ.Close(*closeMsg) case reflect.TypeOf(wsjtx.ReplayMessage{}).Name(): var replayMsg = &wsjtx.ReplayMessage{} err := json.Unmarshal(payload, replayMsg) if err != nil { return err } return h.wsjtxServ.Replay(*replayMsg) case reflect.TypeOf(wsjtx.HaltTxMessage{}).Name(): var haltMsg = &wsjtx.HaltTxMessage{} err := json.Unmarshal(payload, haltMsg) if err != nil { return err } return h.wsjtxServ.HaltTx(*haltMsg) case reflect.TypeOf(wsjtx.FreeTextMessage{}).Name(): var freeTextMsg = &wsjtx.FreeTextMessage{} err := json.Unmarshal(payload, freeTextMsg) if err != nil { return err } return h.wsjtxServ.FreeText(*freeTextMsg) case reflect.TypeOf(wsjtx.LocationMessage{}).Name(): var locationMsg = &wsjtx.LocationMessage{} err := json.Unmarshal(payload, locationMsg) if err != nil { return err } return h.wsjtxServ.Location(*locationMsg) case reflect.TypeOf(wsjtx.HighlightCallsignMessage{}).Name(): var highlightMsg = &wsjtx.HighlightCallsignMessage{} err := json.Unmarshal(payload, highlightMsg) if err != nil { return err } return h.wsjtxServ.HighlightCallsign(*highlightMsg) case reflect.TypeOf(wsjtx.SwitchConfigurationMessage{}).Name(): var switchConfigMsg = &wsjtx.SwitchConfigurationMessage{} err := json.Unmarshal(payload, switchConfigMsg) if err != nil { return err } return h.wsjtxServ.SwitchConfiguration(*switchConfigMsg) case reflect.TypeOf(wsjtx.ConfigureMessage{}).Name(): var configMsg = &wsjtx.ConfigureMessage{} err := json.Unmarshal(payload, configMsg) if err != nil { return err } return h.wsjtxServ.Configure(*configMsg) default: return fmt.Errorf("implemented wsjtx message type %s", msgType) } } kel-agent-0.4.6/macos/000077500000000000000000000000001425126170400144745ustar00rootroot00000000000000kel-agent-0.4.6/macos/K0SWE.png000066400000000000000000002324661425126170400160500ustar00rootroot00000000000000PNG  IHDR'dzTXtRaw profile type exifxڭi\cr09A/(ZHU|/;@w_-riZ=#Nkv^nq]%6k^~=$ }Iw֭Ώy= ~\7|\Ƙ3ɍ&ךʊ+ʪƚy]w}=O<vg.Hq-vwfɲ֬۰[־Oŗ)5[kߗo3CQS4E,(7eb[~ܿ7WWsJߑ9G9o#/c.TL}>Ej_hP3{ mp׷ $ GM6[y|nBJ@Ueb5'eۨYW˯x%XE^s]B99Q(Y/6"'VD-۵vʶ::>tǝ9μO]AF.&? zK1V2GI+eexMOݷPq7 z+WJ,QB0|*nZѨVwm-Ryb<1|<<ɝ; >YdABf]rOʓ3 HXneJ}ţuG?pc7 yڸ?\-Ѹ|ZΝ|p3VgҠӞhz*]pu]!IiR F.`F6NsqҾ Uk-UJ;ԳYF7 TQH5δ"S r Q}BfJ,Й; S~HdQCfϞ[W%m:E~͓-֦!j[C4RΨDN,eNTS*gtͽ ꠶ugu#S4~=EDuE_v ~:o%~n2GHOsXnȄrY EAֵ ӥ(r!#~zk,|jG9NED"o7xO-z%NzQ1 ;4-+ %!$y ]2t"T@iţy(E(0BvuPVWOI1z }6uBً~("{*z?|u?PD<KfB'+{ ' @w#|x 93(ceC [5 TLDoGvhTd):Pߍ-UY~Q8!1!Bo#GsC O0=&%t I+FJt.-?'׻Y_-;P9$(LB .zD Lq%߰˫~# )s|kU. [P vb C&0UXE:];)aFS`2杗(oxp\#p\vg`RPr]$vhRSgjJ7KMs)dmǭj||=ҴyHX.\P024ZP//BC `1f#` C&o oxcE8 ("PCy э` k¶FU"UPg!U Qhg͘Zi&oB-䗊&!)B )P.8cg$l';Hz<7hƅAކiN7"A` mIg9>bicqy>QX!,(<ׄd IThA_SQބL<)pN&;,Ai)^񅾸 Gl /[rdyGl:g>>&(l`8U dz/,—XT=w!MK=g詰XE aDVJh*>i #oXuuaNUхz|@)F+zI{j#WA'q[IŭޡO)` `?KL;5eHI[Pf{E! Md ͡|Be/CpȜJ\4)"3ב"" lGHBҌ y~4`NK A#~3pȯI1Ðijsԡst jr zwWwh N'y:T/,™ VI; ٬TމZQ7s4G#X>4 QE a]2␾i הa;3NM>G'|AdiG ?Ԩ[6ZGHAFnjH?xJ#LŠPQhe߂; E;sK}0Iz%1ʨ^(( *`j5_"N H8aB"M,Q{^m^ X )`NWe%@TY!4}g5[ΐ!;pP>j ^E%QƸ<%J0fcgwq?<#'t(N@|s)OigEkt80e¥!5[ɦϹHc3l4 C8KVnM\M&~@W0;$B1֦6.X >Gcr '2oA}`<{ Ve'}"1 * 4 6Ajggk%G"xӢ?7ؐ !W1a5u4*o b#Q nj|YP wN^V`9}Ҟr Ӗ<92dpx_)I/MIr5̵ \*L6 3.)$ ߐ]S<{: Jx\lvk* ďO :BFKxM 21 oQX0VʚEBv$9ᆕzhFͥ2M筗 )4r3b@A%=%p >eވckr |qKM֜ZWWN5KYIj!ptU[-f".3k[WiӳI<zKI]C.1qKU#$ifh BPmyE)iE*4iuVAoƻ9ӋEM8WauZD^ !3'CgJjѮA!\j+u2j:+]֪Aqs¦fW. BE3t YG`Qj1u{u2$֒ 19myc5J&vh!R 1Q;PɡT=Z3@GI] m׈uN'"WUT>F$N8kޝPb O13f,쇶 ь4;ڀ<:ЩBg\.j|f8 EDZѨ. f\#w&2\I.}D&~e}3hf\iJA-Lp3u: D y{;ܗsHx~Hy0 ]}Hj Ehr4/4X3GxR<,rDVM2J"yZE\(~{d##+oNO4}åi`WiL ~f/1Pn˸dU K'`5 + 3txqфb@ jr-vĖ4;qDU[XI KWQ-tt4mt!c[춈f9jéK&o'@>VhOCY[J6V3vjBnG!LgTX~8rtL`AhR8"Q Xݨm,oDtx3Kˣ渘SjpڇŖmUR:zPmŋXͰS=$} "61iPZ[0z+USБ F`OoHxjA8)5_tHq8oylTyPY W}Nl)p֑;2(AI;& d/8.FYܝ7!EB~:3^0^!i~\$Fjn'[ҭ&ɤ@h:5ȉ> }_ܣ;N9$MSpѝpnF`~ØW rnmjE,e%:hHT+`yUp0_坏Nݐ/ud[]ʸIqƳ,MRh[x!5ڍ1=1"-Ga EbEeWq/D־[jyzbBM;Y lPG{%(iA>P!@46ETШ];o[]1+\L0vA˜M@"p0j:Jz&1~&ƳhVpN iClBTGj!mf:hed͐ѸԭĔ~_:~xEtd/B#I+u(',2^ ]FrETR_L4၆yU]nksOĖE<CFțt㽴/dzҊ0B JDϮTj9I/^uh.Qv#\{؛INzHDl^P"Q)o´钦Aҩs˂(zaS3"1 4!'l@{Ϧh'֩}pq/r!.nX+'r_"$C_8IMt A\\wѾb1O쑌& xtAk ϞjQǪ9u4kPt4⺲:J'6Y \鰷Jhp-,vqa qaNdhQlSew<bͶ˖~o'OG?7](v\vYxiCCPICC profilex}=H@_JE*vPqP,8jP! :\MGbYWWAqrtRtZxp܏wwQa46ɄͭWDF1 2$)u_<ܟW[ ij0m MOe%Y%>'3ď\W<~\tYQ3'V:Lx8j: YU[J_+\9$ "PF6XH~?%r)*cUh]?*LNxI8#@xhq'@j$bG@6pq֔=rx2dSv MPr@-гR7!0ZuwwwV?<r\bKGD pHYsXtIME 'N IDATxwtT$I!zH""ʵX@D b{""#I{}nd2IޯX]k}ff>s[8!TX-6V:l&{XklcuhۍCuXm1TTl56Cn;jUUm3HRYUpklQy>|}d2eA$d29,^$o0{x:&OGl0^fd0^mrk./4Uj=kl6STY]eLUJCYePTVbCEu;޾ ??y:iqZk-^ޞZo//Ycx<4;@@ЄXW,2TxV<*l6 Sieܘ_Zl,,)1dʫ*,4+/@_Z#mX>&l=7~<@CIJLեrsqEGqYԐWZl,()VNQM E̓C!A _@_?˧]mY'Mr0Rt.o{VV̥ŦCznמ&!ᎈG=WmxVsOzLն2RsaIG^i)9ZE8"Bkj}m>ޖj . eյ2sAIgFa)-7˘mp8Kp /OZG:ZՆكlA>U>~*i" hDA|RCijܫ3##/ǔfU3@pK&Z;ZEԶ zT{/Z  p0n,*ثĒSo>e:vVdh,}ё--C"aAP*_S`% :zo{T[̩RżI4y(*"լ=2(Pc.1o%%%E޹ŅԼl3獹ŅKZm,0:8 gr+ m2Tdo.@smAU!ۦ227VUQ!ԜLϸdSFA.@4ySv-A!!/1:Д ˹ ]w͂B]ZEFE4 )lu@cȇoXҋ}2%J++]\GVVͪCЁϗxɰM>c,whZ;ngo&NaTp3+ ﳙigOmv>YHG6jZEVp;@=xw=}fX$64Qf/P.eu V.XVI9t3137TPGvm[TFZ'NeK7.(Xa(*<s ϒ^ /4eŧ&Y>a+mR]ZEW6 .}iX@@>9Y~)9p _~[XV*;\^f ޥetE@@Z *M9x椩ߍFdR_@+?@ y' o,f/ I}a_:PaYouԪUVfQͦ_Cl6X%*(-V~i JKTS!]{tjѺUTdqF:&/|'-/w%E>40f/ PdpZyH"B[PBàNYe J!+X*WZ^ 󔖗r t ޹e2KT^uAԯf?u|>'7͂C2$Ƞ 6\sUK`%357[مyJ%7-BufnަI3*<CnIއOX~=<6#[]dKE7gf-&"RA RV^U %elV2Ӕ˫ȃgێ;tٶd;k@ۖ'{R.w!%Ix^:par)-;?6!r\")Flmګ[hujծyKYx^ZIYKIɔ$8)I:vN56E:tik٪I327Rv8YVY8Y0ukNZGOx38s5vddY<ϳ:rVլF_v ح"*y)t)%/3`O٬4 ZWNN][SҪ|, NuMv=.5YGΞVbf*d!o٦䕻gpU:G.Jx&+6!N'S'zV qU:윳1g|b0ޯC U:vS69&# O1 qIISVa>sûuhٶxi'w=NNPQy)'F ޷]WUk t42s?~dJ㱞Y|4s [ún}if`?Qd;]qGAe30Bf-ClٲE XaIϑl/iXѽ.W;8PGRDv8c Ph@cd~QE/ {@@G Sɠ.A0OûԵu4$)+];NҎ!IOaP Owcݶck.gD M=+qG _E>4 W[fP7Q'iуpheg0(€jtUVRbZp-2ǝKz,ƳɏɤA{j\!w,[뚽VE*,-QaY KKTT^C,VXV,I**+Uá2UPZ/S]cӿs/OcG&o $`0(_'_yL W}~A݉OM=ZG;NRE5[έ:.ާsx&%tԇ}$Γ=~natYo.=PAVnI U"(0O9E+)RaY JTϒu[A~ 5)?PA  Q)<0H͂Bo<VQ]'SigPUEnΈ L_@B߁'ea/OwUQ)?fQF~Rsd)=?Gy)WfA^ ۮeQD`"Ca"4B-B&WU6Z"[/1 7?G#)^ ]?d_tB~1/- |FGt2N3zQԽM;EE4o^^U{ޭN4~eᶞQ m2uU+`c u ytE#tKtyz$)&1^1 qMSj^6#/@:tU]ԿCWUmqhkhVܨU{4gv\5`[Oz۳hjUfgc1 ɤ1/ҭ#/-V̙8L8_LNu_.6W٪6fܶ^kTEuUeTulӶ:Z*@@G|w?yl`0hp瞺eeyXE7sĹD;iOQLdEjPڥt~b67+./mۛœ:8O΅FO`_FӜ_uhS'e0֦n1Nf-9jߩwTj*(᩾;kH^ܥth~.*;7F?m#Z8\SUlZ)n_6KJyѸ.FQkҥ4}3 DΓGt&2 m#5G?O#zU9LIҧ[* Q_Nc{*^p ptsAk6W٪y F SwRB>^{.9EwvѦu)5/Qwhhޭ,akkA}~~طQ>k/ν ^gF5U xMG˫*M] c]c7-r xcL Yp_#{>&ߺVlQ'%6ʹͺjnX'Lawko*҃~>rRPZ(ӬkԤKј>e44kv=uLbh:x[7ץU[3H5->};66UCU5 w[Nj*#6voZnv5ukԠjC^|(R4kh^g.;H;5K`ݷ:~OV֔9T+zGoxo9v0 ~evK\YW.aRZێjQ~fA!P]5`,ooáВ+nKz޷z< |gLhLi%WiU7S6 ϹŅZ[ІC{ h<ѣh06|7|F3o]7xTUQ󮽗t=EYA?f)(os޹e\m~ u63MOvjX5$ߴl MhH^nf^n}Ͼm#Z88ty%T%:=``0~CwhTnWágNhþm:BC!t1{_dgljCz/f!wS`4n{?Tڹ9ݣkލwvKL˭ h[Fm#/Wn׿ZGݽE/}=հ:ㆡJ߸o|ǡ3'C6 MQOLW vWr bF}mQuqu8ܺm]n'ypѽ/+Sْ@@/VӁ%E n'rh ~Eu~طM_l[ { @{S74ko5fn~mGaͧZqWqET=ۖMGI:VgIo^VdJoIm^y%|8Xfxw.5Э^w%']>Wnf&ƺl1>|yU4CҼMٖ5wШujFIcQfnӯ}녯?Ov6q r0dTْiSezqր {} | M1Uq8y>Vڬ* @i鞱d2X;N_c^k}X>y[&lXdy!OhPwnnkцX~:w{"CݢO[_ l q0xT;ޘL~=CqarOwZʫ* Гޯ./s(1^F_m_r~hM& -.f/[c n~˲ޠc y_0Qӯ^ڗ~ط]_GSP'tn\]`?z>wޠƱMx3CF~TY!)-;d́] f8_zͭ} ׾fu ) ;F]Wߢ-Zk_*p -5=MpeUFȟ2`i_x`PbzJƌx@̓/Μk뻽[UcSL׬koӈ/zqzw7U71бk较^t6}xy IDATΆe6G.W;K}w>:jӁzmr[`v\9pXwd{>M{wwgs1Mk>ӇSyU%nӬI^7LTTDzCQyC-\B6{ۏYQW-:[tW{wΜ ]hgCj޿CW-~ps3ʷ˴tӏ j! h~zΩr2] {BF|oY /I2=z$umն^\vۏW莵ou2HuźŪUlj22zϬz=.5Y/}T_m_=3ah=v=/г_}~ pJNWBNo{<vIS6zG4E.?vBF}}F:(?h ?b=> ?_cn=NFQꪖ&L@09 (߭b6kMw&ӵ~VvhB0L/LQ\zZG>_H}zd~]X'Nk ueOhzvߦ=-~p+)+?ջ?}jvh0Wz t鱳 57n=F[u\;xtދt z󅦭Gb3s?||ue`pԗT}2  /O&=z$^L@m#./ykʜRf|ךBz,%Sf.;^kы_/UNQ?}4Ɖq-ezwUr-P=L&6ʥ3ΧR o]WRQƌwqطM~NPQp=y}t50)ٍdwP]s_:VNB޹Ϋ_OV.;x-m;C-ơqJ=vWѝj]*[8_aGxkC9u83-/GO}X-lpz9ߡˎX&/|Ng3rLGr\;xt+`8 e_w>"C]r rj*+( F3=w rڜW嘄9={gS!@@o]v:[lSL֜Vء↑sn SޯiWM{t* Ӭ;F_YCQ@@ormgxukNg?^]r״.f7p^֭K]TyV?~ 4tLW^̣2dX'/p2 yz~4*+KwFq﶑륻ypXphO_k諸~Cm ˵N @@oܦ'?vXO\rv_ͻ.|I;_{RϞrޭ}t!9IX][ܾ^~u=sΤhhátmVoMKzcU٪زEzǯp^1`d{T?Ŷa2oOakvk=|1MhGXqv3*(-q1:1~%9/{q#a+l1[Z5ӗs_.=XϜEh `4#Xg3tVcqL^{ ;ҪmJOD2 XаVk΍I[g KRIy>6cPXVbHLO~`C?J:40\A ~V &QO:Yo6ph-~IIY m#/c}s,z^ensƌ/zTkN ͂B4׀:=N^I -߲B4:WwVuzXrۜ{㞱v,B:4Mk6;짃·w .R϶8kcv*L;oQ:PRgW*13U'ϟusK,Ǯ٨ 8;*?z}1y1J*5㽗5w[*Ku@VeOvjljCѣǚ=ӟWh4ih2r^0|jv'SlQm""3n9ۮvP~0Dif ^&ùV=n~io5ݗT\QFu'T٪jfiL2{x:ё-5~ZKEe{56f~!@^XkSz?!7;S_}\N@=: ԡy:i? O=9Y,(q0qj3^A~q )K62N_[WX+!# |Zꠎ-8}?o6rL9S{Ye!)+ӫw}ݹjάĈO KyG̓Ü޶+~yN| 3UڪՎ ^vnv _ZRQn.:wVoV,Н'bEakbv{w?n:F>||TwB9ueW<+˩m ]5` 2hz=ςCaqԿN/ߺr5.~,|!ߠf>%O˟n*MABFV٢Q=+"(ĩm _xh_~]oޓ^x  拏~w!Gn]'ۨo~vr^\(XlQšɢ:PtZ}`jSr>3[3@@--?cMqzCO._「*~6T^e4:w-^#~ؿM5[-!#t}7`<{n Oͪpn0}5;vyU|)}~ {v5Fһj]{[T]S_ʟL9qﴩص?W2@@C+V~$tmf2dzvFA#VHrVVߡ+ WSn۬.G+woא~\Yu[;*|6F>3tٓDr bF_-BvHO+woj:~v}=3A+vnZ1%M>m>C㟙7VZY/W϶ԩeSnL#{7+mՆ<f?T5lk|i^a2鋹<*wjjM ݝvfsV\o J 55>?yV٫>m0乺g5Nm7X=5MkcvQ4"{i+%7˩G+,/Os^o xw=eh}ovmuϢp/L|HSɩmfjho1*Fty?Y )Nm>as bwZW,20< 9^1;?|vF|ԭ뱛'9 ǦLyFTl), ivnV"[.7Z,?h4ޯg=]v^Nm3>5Y>>UiyT M@IEVެV۳mh]z9iLΚa1UYTMPeuٵYwNk^g\~N6{!02w\`tҥom ,uy8Ҫm}摤Ӻi-.hªlZswf-Ҧ`UJHu9F/˘ah=`@-Bµj6c5i+)l55zFmY[q·:Q)#%g}p)vݖJf;nڃ__sp TۈNk3&!N=9MTMeun|9imY|uXz9;7z[ $_c՞~ka2limN?k²* 56#phl~K>.? Ov?:4nZ|p4]5p;˟z ;OvZڴszq)IgBYh='ϝ> ͹NK9Tʫ*usPbڼPtz9o7O_?mk϶]z܁/䜡+)'T*mZg0LNisHJ֡\~>ُ>\uOl[\Aw? U-Cõe1;g2]iRYO-.ԸRBF|{\ Qkw[C2R]ܹlEHSګU(&!luu ҞY_?@ʛdA $OY\lCOh@nNip螷mc(p3SuUVYZkżdpG.}sufz}}%_l]\QUҫӮGnl_-&=?G%a[G*4 ȩU 1e*q@7uj~*5٥W/>E_[JNw&2 4ހݔg\z3SfZjg]o'S74O56*ǖ-G87OЮ]'ZtժGVVwxDyEunzP-mkkuO* 2t3q99ˏ=5#8^7xӶTkMR*>G𢛵ЂI3\s9y A$NA<ܲ6eWk;Wެǖ-J:!ɡQ=\p[wSt63uJ7=ȼB?:w<$Х;QE^s2^XTv ?Kou/0K>Utˀ^xxKs~ nf-/?*ZG~i\p[m#\ y- 3 ^[tx_pى{g9'?[S.*]o~,9{ Et'xbu1\yE;]p;+vnݛ>{J/6{)s]n2LFbPQyˮ SNv 5Wh':p38t}:!//4mkve_wS|hR ѨuϛϨV}m+ ")15-e/]_m_U{P щKMւ^p;Q5]tCzf0o[ \wd4I3/̂<|U4Z T#7ޥ.1><_8g}zpMQѪU/]6o0!;!=ŐUt?úyFQad{#! *U ( ". XPAj4Az5${^);-3 3gΙ{h IDAT33gP\`swW,|zv^/G@Asy|`'v7Z/xP]t H@ViOQE`WG<`Vm zS}  wVrVmd2|>'y  vL|KɄy<(/h ^% іa:xv^[*f  l6UKY'<{?,X/e;A$۫VI="N}8v9BNAr: ~=u; '0G\pg= HAfQdã0VmM&2q  ?ZU-"DWg#1;Շf XzXEJ<2v > Y4AA8'dexAzIp,ʽ[i  {}U][whV(hpx^\_^"Hrß3omY @LAQF 7`#Zf {&LAb~ܔ.<1Vmc4AsyA|65U73X &'BZ":5aeX$xJGc Wȗn ;ѓߓ]%[A  /CF~CܡtrR 7 Nw]UZVU)-kH!/BaC#z"> zUW 5U(,GbNq5'i4 AlƢ_Wa뻟3nC.ǞŒyd˔'L/{A8@/(/ժqo1l< +ʰj6]Bpr9b"[ttk~xe#>3 8 iVh2< ״%z>m;sD5xJ'g4Ѧ.Z2Φ$Lr<ǝCvI!MA٣HȾ6MC0w7TA!( @)AՂߜ!/nAݦ#}6Y϶lWja4VCD}T4v;tEѠ1M}Nqe߃ȠFڵu>m;]M/Xu_<-s 2_dBտ/z5DB0a0Vmx~'Ft_p!MA%-2[a\_Z]SCFЮ?:/z(DqU9vǞCx{vR(q={Id6KaFem-/KFTk=?co0ZTUV$+ѧm' FMCEJql?6ڍSIq0kg|czD-$;V]DZrVs,s;[|ˈ k×V7<&>.]K\ F.'c߅S9mhj4* ?T߲^d6P[ ٌՕ(y-pR(\q#3\nSdsW ܔ.w0a0i۩ΏTRc{ ^6=[i9i}T4ZGj#C&^Nc+uϥ NcŷT^ecX篷 `(DHx |S|}#L~zٷ]gr1\+s@ UJv 2 g F#V˜4|n1x{I]m^+*qP`Lρ8 \^+ĄGsr3.7/k?ĪWov!$:4K&[quZl!5?[x>; )O ) q.J4 Ýɯaڐ18 ~^$9"ڷ Օڜ6d }?ziE "A#^DWlLzU;NFVq;-w쉙̢|/QUKC!yVӶ]]!hL\]?< 0 |;}6Vmqx Ϫ,юI`=[ú\ZtfJf-E;;ܭe =%w5~ܷU#xuEk((/]=_NHb/{G2 I]\vbzY2m>ٴKJ9+0cx _O{ bq@~9`7!|=фH~3 ͉%YDF`ok Hu]MI3F&e&<4BcJkҝܻ&3s6g%yX@'qzRn *a\F]NNr'T'=Yl> ^P@f6i>?A<\ʷ;5<(}ty_^)[cD&ђV tOoxvғH ̵<=  ؔN .I#k+.%TbtڰTC;䤎9-"$W4<2[mp+Ed2& FHq@v l|CIe`93y=/qDDq>J+ jr2n^ ,߽lMVʪ|ȊN Қ !Dn}xjl<箿YQxs3kxhOҙ7J2/=yصkIbXGɻN >0^oSn)rؗkLʕ $_+y\N]?Vm<+=8:A/ }cr!]w*9kaƄ_: [Nqiٯ6d(%L~.RnB3qpl}s-gE=$6kO.h)1Hl'8|`2rVVm?g L3I] WΧ&n.=Yݙ1+59RZpZ_md}9^nvl}c\ĺ7ϋ,xK/l9\n* VW:9etC["8؂~e%=dMCѭE[)2%Av++ռczde99/ ޶Is C25MA9cx] B|'s>}=ׂD41,p_7{cQ3f3bSD;VMDVܔ.h>%Z1& g,xq]8 KiVm5P敗xЌa=@"#{phw)cAtu$ߊj|jA?T\Tx`,zEL#~^d+u^TY.5l\ڃqX;i! YϹǚ$pz粆}`//u%zvqA te^h2ϳGپ0qpV.B(–ɱEڕjc;c&>Uwg qBXBȂ"T%֊*ˑ}@'}ν@7hTU0>Ffh*M?{i!ˇ.sp/ /?d]RwG_L/C-{@~\AD\t^bvsoPd24 zD`{D ,ߘ7cnh2a#3+V4u+4A؍@{ky~lݎ>\#}E[/Z3ǫ#[]\y>eHjӑN@km"Z:$i! Z+d2+-5$%@m{TVѬa7RU-Cm@Q@;@$ixhզ#>&>8v}= ,&iIV MpFez~,Ղ^gw7@?p *y}A<++\hV ^P^{ Iowtnޚԛ-Hʺ8/gF~폨y0sxkmmWG< Oo:3@OVQ 6,t|='h:OjENkV tN3>lx2)  xW7}v,wGRo?uGС~tW72 ^|#~^xetb`tkaZn-T);~qb_HKF!w1y]z߶yլt׻vхBWɓsxgczdP#)zs,S#](c{C&)g zDlD\^V&GrqE Vo0͌[ϿZ]LE @pf,3ոXv8+/!~f$e~dP#|+t7w@o_L~1: Hv-\gDlAgOmGEc]I ,ЭV!.#񱺷j'H(Zqe t /ЋOQь?FSqzxaJFyM5>uX2ux,gY@L{l&s`4P^Sb^s.Im,f7OByMvètit8kJ[\VKg cxSQe)š!}[Zì[˶?wκ8ؙ9|<=$׏~Օ?sџdбhx=Uh2aØ}i\F="r0x탘`7Kb}{ٌ3WD(E -= %u tSU۴ :A6U-e<kv{G<+ [2 M!ysVۊؔTU0#>lpqVJflnx'oHWit퟿#6:HOw1(6N'-O'_=D_[-ُ{ ZE5b"o /+iM}v[#dwl&BJ\fLU\ƢFIUdY'!16s| ^[ug㑅/cװa{|=/o?BO`#A#-HGӐ0v9v3awb6x|$u0ơ7 I,4Kn(R a3(Fni1J*P^SjuZQ/.#ǯ^iV) ٳ}h(.8t $M *Ddm{yc:rTOK'^–zyOql?&*Ʀ r0Y>t,>5IX|T!6#ٖuY]Ft'H$6[.Yx& fD\&GdPRޞZb-v^.9ըt(/tgU-\pwMwUZ t=5)q3yz!:4PqAziuo5FmGsDoٌY?}Ū 5BcV[EduNڄky23(ğfб6 NcD=W/u/ށiCFzE_xk͸4 ZS1]%֪TLg} P8󞺁X1 ۲y%( q\ZU8RZ/,YY'H{");{Şs'0%1ZMDS?MUS8 $M znY16& %[/؜!Pxww c{=$y[sONxxjK}lZ(腆+]Qe9ϝG[U^VゥFCF рjM&Tj3QP^B\+'o%?Tti@? *.N OJ!lEyvYP^|&DOtzf,jeZEZ (r=f$t-úRq%x}^~{xcw]Z找J^iۑ 0pR(߽=!`!P[5ͨFE>MAvlz.B ɫg_Vb//3ew,W|8qARnF5ylcӓlޞ6&aVmm7fܔ.UQ3n+Yih³ZU% &kj^1QDD=0k/thB<v~^cc<ҥ$ê<\$qOiH"`U{,vk"n]BNv~%#wsTUS8 '+Um2S\FC h IDAT}f3XenпC7I3GymM邯^%sѧm'jzI<܀~abC^ Q۵ar4vlMDtԚs)ѪkR&%Uo&AHPTj܍uEMު}8i9iEswy^۟=n5F(W}vw;IZ9|=瑵/19>x]J)O)`3~g%'Iph].j xnBx"TA!5^\YËC8%/}.J5r,OWwtj#sSZoո fzFRÝ׻V}Ĩ;QoG,xjN 5d"X zc+r4zQeM5K="0R5 'KyxYT@϶}`O׽U;U\m=K 9Vf-@qxmٴ.[׻MV Po8m`hew #-<=$ |@?xjkLBtG )7@j{+es~AH}r…4o:s z*.u9E|:5$ ɳ;&}T4d2=_1;wyr,X\+̓$_Eէ=ǟߠc@%LJ2tu-6ϱ@o_AJ`VЀ B 1aN:^tAŽCڵZ˪JI_\f*/zx_5ȠF,ʯsfO ~xB?FYOX@29kj8,?4F )7>Z'AHEkjռ?zbB-e9c'^Nz̎ZIb3xi3/ிVWb7EXi$]g#(ߡʗE⹁⹁r3q(P9c DFQn7hY]% ΠǹD>a (&{WDP#ZLAHG ZA:Xja6 YaVWrn61eH0,z~_][5nV`ڐ0͈JÁKgI?N'jԈJ}M[OP0Lū"ҒlUbB_VAT t #k:z}f3#O AYM^)'B^C5 Bj|X]~\v>_쵪YC>EۜOGOf\خI45ƫ#J+ =\QTQ tgL^.!qDbso?t{C Gf 3*ko}KIF'^n7t}=ğ-8طJ& n6i&ĵwb/пCW[wE!pvq֌Ix,E6hgMyx{>^^KNtJ@gЗTQHU@:x$zhxŷ\nUO! C3Kb~Lٌ :up;B eAQ"F[ 'K~Y x|E@t?@g*[Χ%b v(d*Fᣉ3-6g7wY:-LfOnӓp(t,<|cXD5@EX.$q'.ɂ.BƂ@4AHGuZW:?/o}fqMs6tR(J0r֗SߴhU8mfj]|#i_n78/.^u$ ztXuF{pb8VEa%wqW;AR5+}T:q֓q,b?"0N 0-#ƈ0瀻0?/7wkc O\E7Gd2L8^'_lꊱvt/7wu@F+g˟LZV1G FK  =dvsqao%Yٌ~Y٠}ʪ+9_O{o9u@&w[>gm/zb[bYՕH8=\}⬔L. /& 0 \J]A) ju- d,\bMܲ(@K{g j O~65݈38d :4mYgUn-+zFri}8Ac1) Avo+d 'BBį@dpuV23I.5U?/>2 /|!t';@W:9cKs-x~{3N%z߅Sx`4WkX{u[uP +B+U5%80D7p*S :\,"gF UN &#+v .J0s^Z,bA/bc&mw=}g?.ƿًNG7%d2|kxuēڹ|-ETy7@5لsWo?97S㷍lvbAMI@FMŝooL"!e=t'/]5=}$qQ3n^YTvs- =~0Y2r>D|kk gD 􆰠'fgܖ:17Cs~@@?w$ϹK5l1ANKT}-?; I%Z+ɱbpaq]]Gl7G/7& 6%nJdQ.1MC-sH|N[G>/ n̚QyBLf!!η+xCō;xf5ȱP> ^V^|d z﮿WVuTVn# }gO7R\!xak ILo>~wVH5O)]I:'fpPDfA=Lb /|$ /еzD`>0os|Lx/o%өy ^}O'Gx[xϠC]=C>,4\lrP}l>Y&ؔ5 /~rJ9&! Dai뻟7?(_D"K>.y9²&<ġc&  tW"9Z!X FĹ[ɾ1k0 Z^}hj) +Fe^aLR<@e5U++c=4!-?[~jAo)ss,"] YНD.rz'$BJ篞lcН=)?^8kRUKX1)KaO6AbN׻LWWџJtzh6e&HTťKCZqtgYqaAۮ3B|1{?/U9",1'Ґb l<p3A':8)XlRIkϟ A>~w=1'l^.nT0Jg+ZknoL@S;PF|=n& :J=]bBJt-ٮJGJ{-{ OwBFY@w-CN OoSX[2s<]7xibM!Kxx<\g}faL"U9AnJ%q9l> piFIRX{K!!>(KսU;>o~e5Uͤ$Ф扮OG/h_>~xcw wAH m= 笨5Fqe<NRnyOOFd>TVo[7ĵehL H7gAHI;.ٸ3}K B./BS< _W:9cy-/{XiH试kV9'-"зmgmwAx@-[&3WzaMJt]/_g8< Z t $Օ:/EJvɽg-$>t `}oE|N?ؖ\ņn!ގ_,@jhL]܅BX$\iĜLΜSu刜J<>aaAEoQ 2O.E0߆GfA)d>`Aج;# r89vMD~fKVw/Gr(L};K41|`@7M8$Gh vw'!μX;N hYUnGGv%F~Tӗ9#U trObv4,ٺc=!WAy)*M/EzA.~>P:]ѿCWqo`>Pg!OuDqjH'%9qjF rw+ ]\m_etq K3G< dя ĕEl20789;5j,ss9#<9'&}QGۗa刓{tyGCBx=6%aɷ t31B y@'BBM»@/`aA0Vt  IY/bIdm}M[bθIYwN%rٽU;ї G$)7^} ͛Cq$9zgyM)epKv)bAb4Gt%NtA XXH K܀aLρ DŽGYᄟ^ڞ_^kR8~"Cqмxf<" r:<UכJ ء΋#fp._2 AV tW䗳L2TKт^v@?mIn=*8Acoٸ 挟+pg`nA=p} tLW_sDq9 yzTp(9 RI &L52/jV֚;@-ظ&ȠFxgs [a_2 bOb_oVdAI<<%\ɐDM =y1{}%n0$ v$WpoAwqu3L!~Q3/'2,!&ÒIJǯGIn,o|sflJ:nVk+?x#'ғj.{_}u8VZS(;RDJIRZRUVkd2G=t?'<(NΒ)oKg3pϳGa2D}+oZʋklfVP5Y{ q\͑$W>77 \JƑYގXye69ޡgSH[ N pZq)`Ag#ЯAxE!-k86t6nG.7cбS˰HؓtZN+(/ʼnѻMGIWq^\DƨE?uwY?h$8="++-GQk`:v՚k7,Dƺ-L~3k4*{M\J@OMpb8x<uOMDv~A,<t)/4Z-|p02 IDATL&N Ĵg|+)2yI$ Iq3h_I4._ckņ8tWkPQIbج I[ W1I>\OM邗?6IF|:3d<ؾ +o+&Ff36?XH&Yobz'dK OW7 Fx@0sxƒBH!]{KגYՆ8tU\Yo]ܢg#ˇ>߲N2vV8aS/ڏ;3މ:7f }|1Mތkzf|疹.l~CpzV/B`s\%:@ՂU\(ğV{c̻=aK 9\?A\?%#Brtq5ϵPJ+[P^LH mq&JBOo ^Mā^& !-Ћm0e2^6|Kqq$tOA t/o/B.f`pު29E:zYM TɍuQXw+k <~erIgjup T3Hyt })yY))dLyx#<(-B#$Po4#8k/6%úŴQQJ^ X,2qomrr^l~I/}ݼ6=q7&;ț# :8+km-q4ci֨yIfU $-x3>%Nݭ\5jZ %U?Y-[Z܀,e%'+jq: ̖ )76ϥw%Ʃ8Pᥦ%B}3[6=P9V?,ZFjlZI[䌥SभBLr]f#f}f5M: +rFڿh玩lFU3>@1aol+dA'B7g72 zcqR[&v}Ş++EH^_Qyx1^s^, j&RׂFۗ{l ş۳@ѽ;.@yLD}'~z?%)*Jc(4 @:DMk${!.: Xm~{;͝[ P .'>ër3 K-fLŠ2dΝJtB$8#uz.)AJi B0x+pF}z$VG$ًqI[zM/^G&XRO7ٔx:GVu+J%\ *<ڑҒXvnwxIp^yM5o (/ Z1@gORv~ },=oؖ$q%?#+Y*_ls'xCfϼb$®zq&+Cv3\ ,ذB>пCWګRb{Kgngԫd~쌢|LX?A8  59Ǿ` *TUŹ %_/mIge#)y A'|=kr9z k1WcqQz-jZKF.k=+8;}8^Z1f2%.#}ޞ4/a-kyH+Tf?Nu􃏰jXa0ro'O4h{}NV9ڶIslVj2$6Mʈ'8mSaGtD᝟durO[?}E"sa x`sHȾ`.)k1^ϒ]Ҹ8[^nxey?!f*A؟@aǡ9..+a{$_nyOAmM&+rz􇇫v3{;46.\& ir{]) 0w;{z/mzd4_',e%1A";1t+( QTjon8Q`Gn59HC %/-[П?U[7 jAد@5{}y%{ sNinN_q: :,O0DvFQ>"|OlƆCfX,u%:{t{u_+lA̋5|=̢|N.\0Gt="0wf{xB.GP@f ?Lf,j_:ͪCǒ@KX!^Uvt V[L&8}=ޚ_,@Nid[Z翄>@YMj4*,7yi,f,_,Z/c &-pfK^+ \ lZ !+lwNcx=ZFFbB z|`'FdK#f޿_϶JލyeŘ4(^_oqe9s3'q4N˙o(JGywĠ;)2x67-H[6 a=ӷU;atr}ɢ_W"1S )uߡ2\Rn&SGbjN]s,Cio$  EٔtZI=niں.yF ӓAH]>[D0nd6#{a}NZqv D '?NoYwolv%|Ta|cXJɋ*سߙ/M?#.42#Ohjy]2'>_,s'$9?|X'dFCHvB[8%ypgS^jANm2F Ոtx'X="' /|u@S6AD0Eln,0@1y  ; ;0~<_lnlC?ؽ;[9㝾lDbw6mϪ=x<珘O:hlWM٪6, a_Ň/? ..FfpFuj#3h(VSGOps~zGuɯvVUGAq PP\B#&+/B$+ n2?^mO`Tj#4_*! G/RzruWHKD -}ÙiY"Qr2g }cC[MʪPST Ԕ+잙Lfe =;lIWqR7HW]ֆ3k#SHKZ0ԮR_)״|II§D|JIo x^#B z Ţ!B(pUuݼMiBU䲕{ uv>m"6$S)*)ۄ/xvF FJ&w VdY2PWVɄ<2rlt%a)a]X`n`DvB(U~E^>&'2BS0R97gr(©o!G%2rB"; )oFAQxю"8vC*cxs6AG'rmgRK,><7bqT+BO-йX21B!Bs .sGRƶu9 tBI6J#,jPe))c\~t !B*1e{8Y U;U,u4ɣF:Y؈%-sG%\ޏq]CUQ2!B,0Rr)jtu,){;!thhU;_-G)Ƣka,E'BhX/XU'KԷSQP߾4U!t0TVMq)=@2B! 'Y ]Β;R_ s#JG~\bI&!))c@?:҄B!? &zBqM,>K},O!,daQ/*mԂ6!B+`ZaBrpX&!,6G- U >hB!̾BK} 5uxzJԽB:?Nֹ;WX TөqB! M1KD!OPr8BccTK,7$v֟9$t9!B!j UƳ8|Xꫢ376ʤ#G6h]Bgtgn!Q'B_^܅.g5bgFŋpB(@FXޢegbB5%e:B반L(t9W='RgY jYf#P^AylX^s bFӑ'B_gdgn-T<3w[[mPhZBzUXg)*-zFn6V#t9;F#ۺt !0c.+x܎2B(@ECGsZw-WâsJpf01v&dY2tB!䯰j?PV Mbs#:ܥ' yX*_Tc[ݽ Bssqk#t9.FeGժN=B(;ӱޢz޽GДB!odbbwZV\\ !Vrb4+ 7]QN3B!`oX ] Hdޭ6 Hwxq[;!5iYZu3B!Vץ<{g ksM Ѕ`i%0}Z ?r߉SפB! yY902,Px"!jkӠX3pFF'A[UB!H= BGMC貞Ž;Z΍ N(#Igխegm\vSgkbcLzB!Ro1hP_r9%]3ŜHKWĂMGBVuAsyȃxz!MZ ]VmS ΫgtB!D*utn~SDR!6~A6oň:Ro`ňI]K٨W")kx8ЙB!ck" Οz,lFi%t$ !DL:8۱Uxlȵ )|o) {'/>-Br27%Z*jBU)`!D' :"}ϸή- ٰL6|‚ & <1B GMFc")kxXZGGB)Do?gv5B!H\@پHʺ.VkUxl)1!% E;QxX_C_wf IDAT}M=+iGZ#!=E0B!|ת3v.){$v:GNAXЯEpit4 !,87ojx`9((ԚGMM:s!"vF80m dX¿cYobm-׼>9!H:@WzjbkL޶J$eĜ20B!bcB.V'9YujDB!@__ҭqK'xͼ86{4U "B$@ł*tD0&`1ERރ1wk#¥hsB6jS+ ")g,,EBcHu)<܅M=v[lxF1Gachk`iA] !,ImxOq̇7JPTRcgȲd.\Fw$B!1 /CzS2 j頯[[(+({\ۧ}|ERE3ps`bGL!R:~0?+t$~v4Bl"B. ŘN} /+'pf9% O1m`0DR^l%vttmVsL: !bLInܲ^]w^>uY}}Ӧ@SB!b "Ѫs_fJ[e{8\F!1L0̀Bc8D;Oɉ2p-nx=uM\\6F"+s-8wTmQàM!PZ*dؘMŴxUM՚[[/ָ}s71Yg`GFL*Bjh\".: {Yy:8??fRjaZΫgt%Bm{a{ )*)x >mʭ6l8**pYbaڅbO 6u,6لVG_EuùhQ|g!*uGMFf]ƇAW!ߦ+w}%LBaq::7K('/p}.;/+s sJ 857 u̗>m89bo*ot '0u*uk}ڪ긹lHg#UJƤarAǧ‡ܾ u%_~5'#pJ!+ OOܧ~WXaxԴYYA'D˺"+[F:ܦ"[D4} 3&?(@a4w_=(m[-0inniZ7vƵ*]UG^7?Ufo77WbЕD!Dh!Cbj!V>W<^mUUTɹaps/2 z?}!6y(>>ge ɄZuHbYfS}O-5 U)8_\' ƍO`kd78'LSBp1LnaөکAkEs\ Z1Gb ytB@zS y{rIiacd20k[@L~xW1֋Qiyw]t|:8m oB`woȲd#!DS{G ñYG`? 0m7))Y峐YbI4<ſ6$8~7Z"DCYEz"-w8qDڤ#}J!<: !D8 i-?[/uk=X$XS "bVT;/> [NH|➡!2r{ slbN f\w\UipiˏFM;޽{n07|B FMhܐgAEZ&;''jS{G:e>x`0~4 />:ݐ5 skCS ^ }#q(6,\Ь#wPF\k;!TU-WR_|h;)25UTq~~HWΫgp84znA>F@{`kX4_~-7.mӵ6h!|#Mqhsy\ ^9S~GGM&K7޽ý=qMOwA-=*5n2g@SGgVi~ocStBH ף6)fW7m؍O0أaZ&)Ý}g.\d~oV BGMC垼BH]r%cq4VBn;ybEëw`kPi_!bGS˘jorqr 7t *l넹UN~qXK!h(PU,?&Î'1rBmjMm5=WO#;STħ}C&~e݆8ӥg5{TEZ"eW0 ]7BJ g||+':~pp4?BE PbmYcb0|B̚XehJbݪ8\.|Wcו"/ۿc/ ia+,ΑW}S$vbޔCCnՑƨBeɠEe>S_|׏8 {)yV^.z, _n[[+lʶ Z\` dXl33T?4ӽ$xsSHB(@/NT7ImbĚ~DVJ |~z_?|Q0~.<[- ;Z0I ̭J% #uˍGp5dռ=Ԕw(HV^.+mSfKrgiNbIANbj!"/; &zLA~亵jz4ow B]~^-ӌu$6q˶40ƍe[Um40O::i|d1k:*y Oɥ9N[-AO´^Cq7t-l%jG ̿ga^P+#;\1yL \> EEXy<'j"vƵps64B{Ukd %,]YACZwGa!ʅ S<[MQYbA:CpVUǙyk0gܦe|D^,0O/4ķI[V]q_ƀ Gf.ҴḽqrNXL&]"b,l/7Ɔ13? "p6]+#K{wJXX}8rVK=!DT ha Ԫ 0R}$d2yӲ3%&+%aЊِԔ lա`ØYtBl9$a5ZI !0֭p\L~Kg #7%^+;Vpѐinv~n\ - G40U??$atBx:5½;9y!*1h6/q7_;VP/\g rr8K]kNǰUA(p$ΞM=vN NB(@m[ۢmvb/|WW?W[ MҖ#/u`ݸ̜fmۍwEnėohlW\7yF"h{D2Bȟhhe_~BS>}$}^fٶg313ĺvuKc41EtH erܬNR*])"~?A^m7 $ڶgɇޤD^EQ <:[F:Um. cfԢܺ߿ߺExn?%a00VoHC]ۖ뱏1e*շW3O0 d 'XcT 6nSr"/q|M]?gZ0e~C ~|VUKofV E?}Pl^ۉܪQJФItB0F6'Er}IץC}D^ ej)ӹܲR^Zފވp\4G^bCZV0y[4TT1߈rˋ9%p4,r0=:nx8qFB{cJx<@ܷ *,'%ϟ .6mbw= ˽&}aQҥ=͔4q?h&yfϡOB$'6*::-|:)p啾%K$}t^+/wXFgCg'KzN5 aQ{)uk\8xLep8f>m>[a9Z85/ ]J?35@mѳR2e'ɬU~k#O \OвnC \GYAM-da]>K0fVfR98m Ff5ads\_=I1ټeB(@O1boi)7/+5E^ɉxp98nx g+{ihj8/zꄎ|(}NÑ[oJn )tzXp4?Gz7oGsyzB_)0}]V) 5\8^gFm~E֕xTFuqa:8[׮Ӓv UgAcKJ!'ؤY OKS2"'/Ć134(\} 'z1h;;L7d󓑛\7ujd ,זlRa;) !SG7%G'FݍƳe>Kba 0~ ,_YAWXL6L—o\oh?7$?``bׁ^ fղ^m/^| ihO_2t|1]I"yb}pSo/ܷڶw|&^IKyYs')x<Ž*bWbqs0拧h5c]?.Ũ=„PnhO: pqzړ'l鏫,&n-k͆刮ʫx3MmuTZb:ې-m}æնw1t<Kꚼm (Bohݨu^ LF3wcّ]|fM(WG1f2-:Szpy\\}hf'_ȋ|?Jds)8"L6Gvp,T`klue]7yY9,2]XE_WHETxzg,2:&B.wN\P%5I1+/WA85hZk(շCm;x곇8ZLQwG?Hc2qlV(t}f|濝u }R<"-7:{|>,w) ЫYkj(RUژu`)+({Sw]`$~azA)+("9#T} b$S[%KMqz^7<%.'RfMeUO[QpN!R5x>ۤ>ޢ?Cq%AmWC6!dX«Ór(8K>סZٗN QP[6p>IC믟ziYe |Uba\{B%Cw BT :`j: IDATViϟ{sK ]ݨoiWmy=MJ ϧM׌従(8' ХGIE[O33 =-;cUj^Cp7t-lھރä2w5'r``ɰq'pSn|MKДLv֭ \bGPScf`^K޳}G:ѱ;s=C\\+OYO^C#-A*ڭ֦K)8')[~߄¤4%vnĻsy<\|ro>s3TZ} -w_+:utnamw|fӰ2LO )ȃ,|H u̬ʭqX:pcȢU**e=g:f)It!OK,Z*jGn]Bn%cSda {SڛX`"mPתmp\ٽ6JM.UE%g̕#ɣ+Bon{6L64'}}H|JNmbѐD + /*,SAs$a}u;jĂdvRzvTdwObash`eKjێ>|z!#7߿{x9ߺKaΞe@c1 po_iY-l!bWUS9 YQ/*ĵxdeޱ0Ffݪ&|ƻ/tW!%b ¦31ޫ?׃6K|('vۢݹcek-# ] 5Pn\,c_imh GsâlqAp| ob~XĽhj^F !`.Xg\׍Njz0LGZ.*^<`:}75 [P.TNFn6R`fwroJkgV;ߌ6CAN[ǍO>)..Bb,&m7F6jmU_/'Ml0.Yܷ،4|=M0 `axT{S}3W6=-+*C}-=MsBHMzv]QyKR x1nxv B`xnapu,8\ڙUr߉Q}핖pWfB $gwsOq^0]wB /)v;%Y[cr:a.:B}K;(+}gergȯc/hLYAˎE^~6f8~7Z,(+2[-R ;PUn+bRu62m6ub !+ired>$I͛ɉ>v.2,ZuFf[|Pb4$M'KS%'bÙC3'܁q̿Ė;ۯ gn,&_RM !D4EBwwݠU:7. %+<L]X~3+hj[׹&Zv*%`^F\I12šK1SjVeGtOI>cf.e" !։K6~Z>)^j܂|DFǃ[`2%yvnA~JNDĹ`d}؂kbc(rPҮL~b?+\gxnB^b(>O>, ba)$|VOyY9ԭe=1@4Cܷ*gn&H۶[%pM\CMIx<."lG'"wf{?umJˋKJp)>=I[V 7u1 M}{ES5-ECCN [,OT!1\NkrV7G /S0q }E"mUQPB@>m BnEekcBץ_o^;!+VGa{daw1p^~v-5ԩeϟ{cτ_jcNAN޿/;&m"H Zs{hb[&1?8nXv+Qz%c-px:5*y~Qo=zblc4kb_ RjVF]K~hj_ƥYJP4B(@s ^9Gs ]uMl3ݚN=X3·ڪ{{]L!8[+{s -Cdm-攠KDW.2t,|xɨoރOXTޛ;:7+i]yJ`߾$}%Y7 lBZv&,_T6/Hvr}9ô&mYs}CoC8uTOFat|{1TG1yk n+xv 9%bB:l|u'\~T (p0f|jQ܍sG1/r#s$Ӿ!5+:j%wq(׫?cr"֞:@CY;xtݞ1mb= - vagXi8N޿1QPT78>pf[2Ԕ+̾܄_Nƿ 1p,i [TY FwG!UR2[I>1mў3`BP0iJt?")+(b[,sƀϋ܈-Kt0%q^Y֥Ode`Wnn~TYv"2,ݹZQQ akdƷLm7£wKdfK ,&&:050>i{>nm*,/& ]7* -gn' ݖ̼nYݩ7M=Şj]Ēaˁ0q dr8YzN WLW:!PN~ :G\qnym?b8~7ZmWC_ݩ7L-ޏF |' [K6Ym*-']CdY\xr4jfI~x I~pt_p-ztJ(Bj:9Y8YubkC2F2uc7.+cxX9"oEEh:e7B;܏B$ ?ul]Yw9) [!w"Zs6SaTN]ڎwz)!'4jB:wuusJ xbw_i;֋R/Q05*-@S əlqƓ-Ѯ;6ubBD(eJHT.H{c|/]FaЪ6Z;M$PĽ-8ZYr\hoUn ><]֎0Ŏ'1b͂2`oY\=U4GAe3J{tmGg.ct#-]!;K'1uje`0гGg4?? J!FP{zJ&[jgz2nMźݓc֮ux%Num[ﲼ vNl1ӅM˱̡2ퟺX`98pU|z>?̫\Gv~S'/N9 ݚDVUy1Įo6θ{wFx!4߯y kbdXtlK 7Y!+݅-טY4UT}{4q.D}WWVr6ʢ;!(IO?Ê/<#u㾻_LG!wgzΫ;:aS޷J P܂|_Rpݛcr"rQԢ}Qa/c.޵Oez5v 5.eݲLdx5;/!YJg?9ˎ,]!;]w*/bPPܷ|-4u`C(p0zbKCV]]\\Ix6;w%W|c4UŶ].ua=)ϻU!' /Z3xڦMɆ:cH~-(*]8x"6^aqQb1mZǚ.Jx=X{cDoJ8:Jeh(VD\> B]Ikcwtpnu%rW=,YmS _R n ? ;9  ]tɥyg?곇q${I7R%hU `o\<1=/p}\}@t_U9 Nz;Y z4@Ai[?}zfP+OǮvo`eU}5Nݿ7 ky '00S^ĨE) 6M _BwBPj00t[-_ǞX8xƚ~%dm&jw *jٗ]# 6D̓nknk2uuwUՑ<߷pyYfL=$k0f~E$GNF>m߱7[Wۄ/{=ܾ\n]@{t.2,Nĸ. 5/;ָ^Ľ8q:~x]n w:~p\~lV(LʭSN!דE%]0pu%B[ 9cȽ65ȋƛBDdn\|e dž:>Z3r~9NVsnabNIwΨve=z|]}Yl;nIOv?u`40?s#V+6=vA4LX&zEa޸ҿCϟ ^4N޿Oɉ~gE֖mO`TB=p:gڹwd1sط-GJr!o9t uq&sn_S|m3TxƎ'? V&b۶*]@daExn>Z.NPWu7˾em7{e;ZTAO]Ƶ*P@ݿ/{CGMYy9HLGjV&R2 @{a)={ĘN}P\RMv+zS;2~ pU~p9k3tF-b2uOd1h^ &e`G'L=T"״LݾZ`2MibۼmA!7z^Fm b|Md{|[V '}|1 ʉ}Xsr?֜M ^÷.ЭK:{<8 }#шQMutn\c΀52݄/=ׯ߄ _.I4|NID|j2~×$ w^=+MEc20ՇԕT '\gg"j:879f/?wm o~g4MދPe*{)(C @E_*8WQ\8"lP N޻IR 纸h]Q[ޤ\M,! pw2yA/<:9'H0sxvٟnHgز4ꤾe(,ǩ3Anwf<7O;8Va Pш[ ζP D|ؿ`tܿ'=[Ԉؐmr>>orOe٢q'`0LoLgMc\j9r/;n0)q?f g>bE"Du1[tlUN)cv۾ܲ/L~a^wMc7B'ދWe8nQ IDAT?/lmfh3ǟoxg5qkWgwښ|ͷ =fopVo#W`c]S-xoj=ηK~E)֮15v$& ⥃"|`V`f)(1_?hd~fmK>ت}-zdhjja5UG H$W}=g~.`u@H )NP`U?eqV,K\1c p7{M$흰?cڒ+kvS"gmx::4-v^؀̢|S-,i3+b΍&o># SFǴyEt/rt_τ= {s݇FM嫁joozҷK^f>!DDĀ~?tۮߡhCށC3?'|k3x`65ᚋ>]<[߿?GVh؟~>"`ʠ'KmN<\;y6rb{s[\Vj\]vm35t MK*2̽{\S.{>Z:Abć0iPLZ4E'`)m]WG+C3ao+kf_?l@=b0C'S*T- ?X [ѬJ$6y~96@آ8z{l[]j EBL8B%J\7i~ """ިYMGط*U`ΧrӯX2c.F{@ Ȟ0g?$eeu˾hk lIǖxE"~jjuv4Fx1?ٌc9Z>M>| =, oZXkJ۟nE!P/?l`XdZ0;bS߳#'^ߵ7O :Dj颓C2ߎ}wW`(ޙ>1fH.8sGf%5;2a0~&.Q&]_=osM#:6i5q0L؃ QTUaNI$xp̽~o\݊v3؈^;GUM}Cډ`sFOXd[Nv ;o>٢P7;9lޱ+idIMž$jhW5+cJ"#z ?6K=^zaοʷҰuSMmvzE]@=q3zqUROgA)sqߠa Y$R([6qUٷӖ<;q ^}K2Y(P `r̰MZU/a$E)-8ed[\<Òs/_ae>ڋGnHGFs55n2/j67bk8-vtӧs^;DDĀ޾|K/T?%qoZ=)0{$3 R`_;˅ڽixx_O]rHdi*Hdq>ЗgCje}Tf2G{yY栿8y܈3% W{|Y@>&zMT77Ѡn2- q~4z?;٢|όzI@o>{o&/ZߚOǫnxm'W\ ZEԂ=|Zɾ`@ aUwk^,#?kq{mcv !pv'NƸ>ȥjo ?}U] #z\Gu.+iuÜ뫍_`?u2o3wpxyIJǞh4ݛ<.ysJߪ`8'""f錧4m2Wt**ǼCto㑈05vv9|T^z =rg޾' LLܷ%όjr#PXY :^gKK9l/ Wރ. ^w^3֨u&рy^k7˦'0Ǖ&]qo~3T-⃦Iϯ(ŜOco)_CMkg{{dM?9-z}x>` e ,CuCe=kvmDn&9|_{cjٹ7-?^h1:;Fv~4T7Ե.HqWA&ԃnN*t /`7`0LW)(s-w -y2#zOfU66 5/Z/[!ۯRـ9|8Ͻn?}f{^q|غ~wfDDtX<1SP*aGo#{?&}/7େD37e8ǐp29pv)FN&mf!"*M =c,$śGf%AfMηoסB^3k*[s]p \-2̍pplOyҋܬӍekp?`[ ?1rt1`gr֤^y9 !㟰1s6uz=VL.ùP 3G-I;3_X9s䄊n~Ax x7m|um~qyq }Ƶܰy6+ƖD!yj쨛w&u0w4?C$b֘&e(7=UiFXAo=g@H1hl~+tӑ8YxdP5ϿD4j4평 F7\ ٟ~ݞW{#eN?ė[h3S`g7LiMk *j٦߰⯟NOi/VL0T\9Կtt6A73;$m\"CC3㧶*<lO Υ"AF<59[^ڱp%4-z X7_sהvw1E#v&]*n5DB\0{o61Cw;=#V#scP\U}o//!wnyct󂃍-Z 5jV YE8Q7s\1qPL?}C"LVo NƇw;F{~uஆAݢߝg!15yjbv 5=,)y=~'߅<3٫\ZS.>1 쉳29ά՚ PɕU>;X/!0'+Hv- 1a=LekEKxlԄz, 'S0'ФB<}1L0h+v?Xw)@vƻ6xK5 | qV5шGa} ³wOôQZ"|{9%Xw@N]rON-n?}7`6wATPWLǸ>Lstxx`N6vY LҏipcF|VjiO\kӮxܷEۤ_&I"B@kO{b;EIH1Q1AoOBɓ/$"[;|(ݗz.>㸨f٣'* O%ՕXp6$Ǝc|PHeuv-)NTͷ1F6mϫO+b\V>1dy[CG4[mB!u[1q&11kD ^f<$쭯bjz)2DlA=qWA z\Y_{a~yaeYy.zXCxfl%-z[ALIu%X5'NF.mc&QШQcDZ^}xo*]F}|c.Bn~v;|]ܡ![!t, {Я'|WChMr)d,nшO75;7bZh-wf5(DڙH?͉ZTccpWAٳّmMF~.>'|_ 7N*;cLn;wqV 3Nuh#XP3 Sl_9'}3aJPL).vpVFǐq 67?{eز?>;ǫ_W7Hf:vO{^Ĉ}WjPY_"VcߴXOm[oFꏻ Bf 5jo{R:s" 1gHʷg)1m`rSSDm( Yc&T`lK>Ia ju<fD D @pw`4 u(jQ:6'EȞ0g_x88ǟ/6w;BE]M{~ ]{,}Yg'""tjxbOQUla; Y'a|Xڲ\lI<;%mQG"/Fꏑ=WPh] ͜[~^񮾃>x~BDD !Geݖ|PQ\]!'c4fn~A4Z<Ǐ`H8jv2|B]08wƠf.:zo98Xݡ-!SXЩ+2$K5{N Ō0-vWnH?cgJ"Le/R @O t:#zZծC~?Cg{PsU &?7I""b@ůWXI~4A7t܂e2wahdv7R &8ǰ/(H|g Bc`xĆj:Zb0ݵ  9C߷Kds%1SC'Rl%:8ajHLn_Jx)M?T&2Va]hwtDv"%GsNb΍qϖN>dҽWݲ7&""tPތcc9x|05vŎG:eH8)H8Ĭ NI$"'$1G\z_kڄٝyk٣oòǟцЩ{7l&[gwE oH{C;F^8x.:\:WGtpzg`ewUue6ݵ {ӒіѴD4ED̊pDDĀN+W 4 iɊNsވB 1qw;hcaIAJ^&O 9$3  rBw`DwAπDIea{6cӑjuw{'㝽W2;1Suo\JUKKVWuG,ᎈ(L0!{/둑HıSH;9t[Y D_0" ү "|;̼qKj!a8ۏ&t h615./=""b@'ǂrI񲢪NwBĄC1x::w㯬Ej^^IDATR򲐒H;:8Po?y#a>W5grNc]N {ӒБW0Vamѫ6ݯzC8ЉL٥vۏWUtI  :( b|Xv󡬦 8QS~Bo'Og{# !^~ D/|];mXw`2:m;8Ez{V=):ѕXbAAUDyg~7Oq}b10"Q??fd (Y).vIa;E"x;" A^tFE_$NFF3ӰN߉y=\cb!~/91] +yE'sK:` *$&IAeWV"䗗Ly ΔtY Սl6L!n<yʄzlONxl:M<zQyxMys̉Fx|JASC)ʬ38͉9)IY'zw 7txW@d!C DDD t]i՞d+I6M.b@H a30S-+E^$NIĎcQM0{wpXݛ<!""b@6oVXeLMh 1a= =# "!;ѭ둘ݩIؓ}iG/^ic=9ë4EЩ [\_Uڗ8Ys (er 醘 -Fэ rOa[r'3_g7'=qDDD ]uRv@qS aBo8ڳaRO`Ojv$ЩTs5z7kıAЩ[ғUS5OtrNpf:Já4dɁWF0Ɔu[\5[:Uō 'SGsN2]^+5"*0~,>GFvqEbf:de )+MZ :yvS{9,Y^ ""b@bKgl%9P)0 Aw.ÞvjrJ qT*dЩ4$ff^ȆV 祐 _8:ubRJ8"*9|%RtF_0CPq)FJ^ROg!5/gBn7hɭv̧ul"""t}2 W?Aeuo@Wz!'!cp[igxJn&Jk87J~!0FЦ:}#""b@'WD[o}Jq&o!g7x";>vF]9hDAeUy8398]V|}k|j_8DDD DfWe' lgWVMd ݽ`o{#.vrX$b#uBZ]3rK.d#8%\6rTvu[3NDDĀNtX' YEgN\"_w~sq+<!i/D~y +ː]\sa(gKY{- 䠬򔑭BDDĀNtS#QaYќScF#?u.psp+<\'7;8 ζpg[U((Cae(*G~y)QR]Ɇjz{h|N-BDDĀNt[,Ci^IuRf$&Ye'[{9Bpw6*8ZLΆPnDe]-kQYWT״U//AIu%:,+8T^t\.ODDĀNԶ}Y^YubVՙR:*[@.NiTTB r*}-Jr6w#V׌u)50 ިiVwNYF}u:ԫ.ܦn֢IAF N&4h΅ZTՠ\f<]B"t> ? 1.C^XYL>iUR]]3; KC\ ng[{. 86 :Q#ՙ6ܒBőtqaE_'DԦ9QA]n qS:QWʊYNDmŭA7s&C9:Q`ej^$L_?DtKu7j}V9:OeU3Ù"/&M(_!Oڰt3NDDĀND}:P!*kvV5,>GV!""b@'k ;PR)/(ȳJ-EDt)@0CokʳEވЉ&xy2aMZ[R$;sJ\XɪDo]jg0=1JqqqhVsK %2NY 0titpn mdž!"""t6WkdeɱSBM CΉBxݼtnGUӞҳexy2amFYXQ&.)g6Y`Of{gڮqCȉ[RXP+/.+J4h%j<ptR۫l~`91uO"iimL~>XsD7Ln t6y5vMoM-DDDĀND3Ҋ*yQe$H]|Fe݈O1KuU+k,FDDD DtUDXnH*9ŅB։Pɕ`OoA[7->DDDĀND7+|"j4*k%V DJNF0y}\NvZGF_q"""b@'[ʪ%3e%Bnpw;*kf 1QaL-ijTZW %|6K"CflnV*UE0u"""b@'o?5mTWK|[Zevv3;U:;uR)*r4qqql """b@'5MZiɪ֪FTR])<]V,hP7I$rt1;\Tz;kJa,H5,FDDD DDo?446JMV5uzQymJ!J4z:hcQZlfDҬԲ`1"q?},h[tZfQmc^TQ[#V 5j6T;e6z88ޠ+ \RXfHT6sH::@T,5fqSFԨшM:uNP (DcP Z{k\iT)zDbPJzN*zes̙F:u0vA bYkjVج 5AV-lh FA5 i}JhP@J4FTfK dVRJlt DDDD DD76A&6kDz^5 N zY+f^i:Y4i4N N'hP7IA=.LPI Db gE³oQ$J$H$ PACKAGES MUST-CLOSE-APPLICATION-ITEMS MUST-CLOSE-APPLICATIONS PACKAGE_FILES DEFAULT_INSTALL_LOCATION / HIERARCHY CHILDREN CHILDREN GID 80 PATH Applications PATH_TYPE 0 PERMISSIONS 509 TYPE 1 UID 0 CHILDREN CHILDREN GID 80 PATH Application Support PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Automator PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Documentation PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Extensions PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Filesystems PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Frameworks PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Input Methods PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Internet Plug-Ins PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH LaunchAgents PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH LaunchDaemons PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH PreferencePanes PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Preferences PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 80 PATH Printers PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH PrivilegedHelperTools PATH_TYPE 0 PERMISSIONS 1005 TYPE 1 UID 0 CHILDREN GID 0 PATH QuickLook PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH QuickTime PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Screen Savers PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Scripts PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Services PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Widgets PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 GID 0 PATH Library PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN CHILDREN GID 0 PATH Shared PATH_TYPE 0 PERMISSIONS 1023 TYPE 1 UID 0 GID 80 PATH Users PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN CHILDREN CHILDREN CHILDREN GID 0 PATH ../kel-agent PATH_TYPE 1 PERMISSIONS 493 TYPE 3 UID 0 GID 0 PATH bin PATH_TYPE 2 PERMISSIONS 509 TYPE 2 UID 0 CHILDREN CHILDREN CHILDREN CHILDREN GID 0 PATH ../assets/kel-agent.1 PATH_TYPE 1 PERMISSIONS 420 TYPE 3 UID 0 GID 0 PATH man1 PATH_TYPE 2 PERMISSIONS 509 TYPE 2 UID 0 GID 0 PATH man PATH_TYPE 2 PERMISSIONS 509 TYPE 2 UID 0 GID 0 PATH share PATH_TYPE 2 PERMISSIONS 509 TYPE 2 UID 0 GID 0 PATH local PATH_TYPE 2 PERMISSIONS 509 TYPE 2 UID 0 GID 0 PATH usr PATH_TYPE 2 PERMISSIONS 509 TYPE 2 UID 0 GID 0 PATH / PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 PAYLOAD_TYPE 0 PRESERVE_EXTENDED_ATTRIBUTES SHOW_INVISIBLE SPLIT_FORKS TREAT_MISSING_FILES_AS_WARNING VERSION 5 PACKAGE_SCRIPTS POSTINSTALL_PATH PATH_TYPE 0 PREINSTALL_PATH PATH_TYPE 0 RESOURCES PACKAGE_SETTINGS AUTHENTICATION 1 CONCLUSION_ACTION 0 FOLLOW_SYMBOLIC_LINKS IDENTIFIER radio.k0swe.kel-agent LOCATION 0 NAME kel-agent OVERWRITE_PERMISSIONS PAYLOAD_SIZE -1 REFERENCE_PATH RELOCATABLE USE_HFS+_COMPRESSION VERSION 0.4.6 TYPE 0 UUID 77A58E78-3D6F-4424-8F33-116E67AC3556 PROJECT PROJECT_COMMENTS NOTES PROJECT_PRESENTATION BACKGROUND ALIGNMENT 4 APPAREANCES DARK_AQUA LIGHT_AQUA ALIGNMENT 4 BACKGROUND_PATH PATH K0SWE.png PATH_TYPE 1 CUSTOM LAYOUT_DIRECTION 0 SCALING 0 BACKGROUND_PATH PATH K0SWE.png PATH_TYPE 1 CUSTOM LAYOUT_DIRECTION 0 SCALING 0 SHARED_SETTINGS_FOR_ALL_APPAREANCES INSTALLATION_STEPS ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewIntroductionController INSTALLER_PLUGIN Introduction LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewReadMeController INSTALLER_PLUGIN ReadMe LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewLicenseController INSTALLER_PLUGIN License LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewDestinationSelectController INSTALLER_PLUGIN TargetSelect LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewInstallationTypeController INSTALLER_PLUGIN PackageSelection LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewInstallationController INSTALLER_PLUGIN Install LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewSummaryController INSTALLER_PLUGIN Summary LIST_TITLE_KEY InstallerSectionTitle INTRODUCTION LOCALIZATIONS LANGUAGE English VALUE PATH_TYPE 1 LICENSE LOCALIZATIONS MODE 0 README LOCALIZATIONS TITLE LOCALIZATIONS LANGUAGE English VALUE kel-agent PROJECT_REQUIREMENTS LIST RESOURCES ROOT_VOLUME_ONLY PROJECT_SETTINGS BUILD_FORMAT 0 BUILD_PATH PATH .. PATH_TYPE 1 EXCLUDED_FILES PATTERNS_ARRAY REGULAR_EXPRESSION STRING .DS_Store TYPE 0 PROTECTED PROXY_NAME Remove .DS_Store files PROXY_TOOLTIP Remove ".DS_Store" files created by the Finder. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING .pbdevelopment TYPE 0 PROTECTED PROXY_NAME Remove .pbdevelopment files PROXY_TOOLTIP Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING CVS TYPE 1 REGULAR_EXPRESSION STRING .cvsignore TYPE 0 REGULAR_EXPRESSION STRING .cvspass TYPE 0 REGULAR_EXPRESSION STRING .svn TYPE 1 REGULAR_EXPRESSION STRING .git TYPE 1 REGULAR_EXPRESSION STRING .gitignore TYPE 0 PROTECTED PROXY_NAME Remove SCM metadata PROXY_TOOLTIP Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING classes.nib TYPE 0 REGULAR_EXPRESSION STRING designable.db TYPE 0 REGULAR_EXPRESSION STRING info.nib TYPE 0 PROTECTED PROXY_NAME Optimize nib files PROXY_TOOLTIP Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING Resources Disabled TYPE 1 PROTECTED PROXY_NAME Remove Resources Disabled folders PROXY_TOOLTIP Remove "Resources Disabled" folders. STATE SEPARATOR NAME kel-agent PAYLOAD_ONLY TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING TYPE 0 VERSION 2 kel-agent-0.4.6/main.go000066400000000000000000000013111425126170400146410ustar00rootroot00000000000000package main import ( "fmt" "os" "runtime" "github.com/k0swe/kel-agent/internal/config" "github.com/k0swe/kel-agent/internal/ws" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) var versionInfo string func main() { versionInfo = fmt.Sprintf("kel-agent %v (%v)", Version, GitCommit) fmt.Printf("%v %v %v %v %v\n", versionInfo, runtime.Version(), runtime.GOOS, runtime.GOARCH, BuildTime) log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) c, err := config.ParseAllConfigs() if err != nil { log.Fatal().Err(err).Msg("couldn't get configuration") } c.VersionInfo = versionInfo wsServer, err := ws.Start(c) if err != nil { log.Fatal().Err(err).Send() } <-wsServer.Stop } kel-agent-0.4.6/scripts/000077500000000000000000000000001425126170400150615ustar00rootroot00000000000000kel-agent-0.4.6/scripts/build.sh000077500000000000000000000007131425126170400165200ustar00rootroot00000000000000#!/usr/bin/env bash set -eu VERSION=${VERSION:-v$(< debian/changelog head -1 | egrep -o "[0-9]+\.[0-9]+\.[0-9]+")} BUILDTIME=${BUILDTIME:-$(date -u +"%Y-%m-%dT%H:%M:%SZ")} export LDFLAGS="\ -w \ -X \"main.Version=${VERSION}\" \ -X \"main.GitCommit=${GITCOMMIT}\" \ -X \"main.BuildTime=${BUILDTIME}\" \ ${LDFLAGS:-} \ " mod="" if [ -d vendor ]; then mod="-mod vendor" fi # shellcheck disable=SC2086 go build $mod --ldflags "$LDFLAGS" kel-agent-0.4.6/win/000077500000000000000000000000001425126170400141675ustar00rootroot00000000000000kel-agent-0.4.6/win/kel-agent.wxs000066400000000000000000000026631425126170400166100ustar00rootroot00000000000000