Compare commits

...

4 Commits

Author SHA1 Message Date
8b5a97da54 chore: add logo
All checks were successful
arcad/emissary/pipeline/head This commit looks good
2023-06-22 09:01:41 -06:00
d73e027ee3 doc: uci configuration deployment tutorial
All checks were successful
arcad/emissary/pipeline/head This commit looks good
2023-05-26 15:53:00 +02:00
054744e3e9 fix: systemd units
All checks were successful
arcad/emissary/pipeline/head This commit looks good
2023-05-25 21:23:15 +02:00
0e664bce44 doc: add first steps tutorial (fr) 2023-05-25 21:22:56 +02:00
8 changed files with 637 additions and 4 deletions

View File

@ -1,3 +1,7 @@
<div style="text-align:center">
<img style="width:250px" src="./misc/resources/logo.svg" />
</div>
# Emissary
Control plane for "edge" (and OpenWRT-based) devices.

View File

@ -5,6 +5,8 @@
## Tutorials
- (FR) - [Premiers pas](./tutorials/fr/first-steps.md)
- (FR) - [Déployer un serveur mandataire inverse sur un agent](./tutorials/fr/deploy-reverse-proxy.md)
- (FR) - [Déployer une configuration UCI personnalisée sur un agent](./tutorials/fr/deploy-uci-configuration.md)
## References

View File

@ -0,0 +1,3 @@
# Déployer un serveur mandataire inverse sur un agent
> TODO

View File

@ -0,0 +1,130 @@
# Déployer une configuration UCI personnalisée sur un agent
Via la spécification [`uci.emissary.cadoles.com`](../../../internal/spec/uci/schema.json) il est possible de configurer un agent avec un système OpenWRT. Dans ce tutoriel nous verrons:
- Comment exporter une configuration UCI existante au format attendu par Emissary;
- Comment modifier la spécification d'un agent Emissary pour mettre à jour sa configuration via le serveur de pilotage.
## Étapes
### Identifier l'empreinte de votre agent
1. Sur la machine agent, utiliser la commande intégrée pour récupérer l'empreinte ("thumbprint") identifiant l'agent:
```
emissary agent show-thumbprint
```
**Noter la valeur retournée. Elle sera utilisée dans les étapes suivantes.**
### Exporter la configuration UCI de votre agent au format Emissary
1. Se connecter en SSH sur votre agent Emissary:
```
ssh root@<agent_ip>
```
2. Sur la machine agent, utiliser la commande intégrée pour exporter la configuration UCI de votre agent au format Emissary:
```
uci export | emissary agent openwrt uci transform > my-agent-config.json
```
> **Astuce**
>
> Par défaut, l'outil [LuCi](https://openwrt.org/fr/doc/howto/luci.essentials) est disponible sur votre agent. Vous pouvez y accéder via l'URL `http://<agent_ip>/`.
>
> Vous pouvez utiliser LuCi pour modifier la configuration de l'agent (par exemple, configurer le WiFi, créer des règles réseaux, etc) avant d'exporter la configuration.
>
> De cette manière, il est possible de répliquer celle ci sur plusieurs agents via Emissary !
3. Transférer le fichier `my-agent-config.json` sur la machine hébergeant votre serveur de pilotage Emissary.
### Transformer la configuration en spécification
#### Prérequis
- [`jq`](https://stedolan.github.io/jq/)
- [`sponge`](https://linux.die.net/man/1/sponge) (paquet `moreutils` sur Ubuntu)
#### Étapes
1. Sur la machine hébergeant le serveur de pilotage Emissary, utiliser l'outil `jq` pour créer un objet JSON correspondant au schéma attendu par la spécification [`uci.emissary.cadoles.com`](../../../internal/spec/uci/schema.json):
```bash
# Créer la structure de base de la spécification UCI
cat >> my-uci-spec.json <<EOF
{
"config": null,
"postImportCommands": [
{ "command": "uci", "args": ["commit"] },
{ "command": "reload_config", "args": [] }
]
}
EOF
# Injecter la configuration récupérée de notre agent dans la spécification
cat my-uci-spec.json | jq --slurpfile config my-agent-config.json '.config = $config[0]' | sponge my-uci-spec.json
```
Notre spécification est prête à être assignée à notre agent !
### Assigner la spécification à l'agent
1. Sur la machine hébergeant le serveur de pilotage Emissary, retrouver l'identifiant associé à l'agent:
```bash
# Déclarer une variable contenant l'empreinte de l'agent précédemment trouvée
AGENT_THUMBPRINT="<empreinte agent>"
# Récupérer l'identifiant de l'agent
AGENT_ID=$(emissary api agent query -f json | jq -r --arg thumbprint "$AGENT_THUMBPRINT" '.[] | select(.thumbprint == $thumbprint) | .id')
```
2. Assigner la spécification à l'agent UCI:
```bash
cat my-uci-spec.json | emissary api agent spec update -a ${AGENT_ID} --no-patch --spec-data - --spec-name uci.emissary.cadoles.com
```
**Bravo, vous avez déployé des spécifications UCI sur votre agent !**
### Exemple: modifier le `hostname` de votre agent
En intervenant directement sur notre spécification, il est possible de modifier la configuration et appliquer ces changements à notre agent.
1. Sur la machine hébergeant le serveur de pilotage, faire:
```bash
# On créait une variable avec le nouveau hostname de notre agent
MY_NEW_AGENT_HOSTNAME="MyEmissaryAgent"
# On utilise jq afin de modifier la valeur de configuration dans notre spécification UCI
cat my-uci-spec.json | jq --arg hostname "$MY_NEW_AGENT_HOSTNAME" '( .config.packages[] | select(.name == "system") | .configs[].options[] | select(.name == "hostname").value ) |= $hostname' | sponge my-uci-spec.json
```
> **Astuce**
>
> En utilisant la commande `grep -C 10 hostname my-uci-spec.json`, on peut voir que la valeur de configuration `hostname` a bien été mise à jour dans notre spécification.
2. Mettre à jour la configuration de l'agent:
```bash
cat my-uci-spec.json | emissary api agent spec update -a ${AGENT_ID} --no-patch --spec-data - --spec-name uci.emissary.cadoles.com
```
3. Sur l'agent, après quelques secondes (par défaut, la fréquence de mise à jour est de 1 fois par minute) l'agent devrait avoir son `hostname` mis à jour:
```
uci show system.@system[].hostname
```
Un message de ce type devrait s'afficher:
```
system.cfg01e48a.hostname='MyEmissaryAgent'
```
La modification devrait être également visible dans le prompt du shell de l'agent.

View File

@ -1,3 +1,158 @@
# Premiers pas
> TODO
## Prérequis
- Pour le serveur, une machine [Ubuntu 22.04](https://ubuntu.com/download/server)
- Pour l'agent, un [RaspberryPi version 3](https://openwrt.org/toh/raspberry_pi_foundation/raspberry_pi)
## Étapes
### Préparer votre RaspberryPi
1. Sur la page des ["versions"](https://forge.cadoles.com/arcad/emissary-firmware/releases) des firmwares du projet Emissary, télécharger la dernière version disponibles correspondant à votre système cible, dans le cas présent `openwrt-<openwrt_version>-emissary-<emissary_firmware_version>-bcm27xx-bcm2710-rpi-3-ext4-factory.img.gz`
2. Brancher votre carte SD dans le lecteur, flasher celle ci avec le firmware:
```bash
# Chemin vers le fichier de firmware précédemment téléchargé
FIRMWARE_FILE="openwrt-<openwrt_version>-emissary-<emissary_firmware_version>-bcm27xx-bcm2710-rpi-3-ext4-factory.img.gz"
SDCARD_DEVICE=/dev/sdX # Chemin vers le "device" correspondant à votre carte SD
# Décompresser le firmware
gzip -d "${FIRMWARE_FILE}"
# Flash la carte SD
sudo dd if="${FIRMWARE_FILE%.gz}" of="${SDCARD_DEVICE}" bs=2M conv=fsync
# Attendre la fin des écritures
sudo sync
```
3. Placer votre carte SD dans votre RaspberryPi, le connecter à votre réseau en Ethernet puis l'allumer.
4. Scanner votre réseau pour trouver l'adresse IP de votre Raspberry Pi. Par exemple, avec l'outil `nmap`:
```bash
sudo nmap -sP 192.168.0.* # À modifier par le préfixe correspondant à votre réseau local
```
Une entrée équivalente à la suivante devrait être affichée:
```bash
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-25 19:29 CEST
Nmap scan report for 192.168.0.24
Host is up (0.0034s latency).
MAC Address: B8:27:EB:E5:7B:55 (Raspberry Pi Foundation)
[...]
```
5. Se connecter en SSH sur votre RaspberryPi et définir un mot de passe pour le compte administrateur:
```bash
ssh root@<ip>
passwd
```
### Installer le serveur Emissary
1. Sur la machine Ubuntu 22.04, télécharger les paquets Emissary sur la page ["Versions"](https://forge.cadoles.com/arcad/emissary/releases) du projet. Dans le cas présent, choisir le paquet Debian `emissary-server_<emissary_version>_linux_<arch>.deb` où `<arch>` correspond à l'architecture CPU de votre machine.
2. Installer le paquet télécharger via `dpkg`:
```
sudo dpkg -i emissary-server_<emissary_version>_linux_<arch>.deb
```
3. Appliquer les migrations sur la base de données:
```shell
sudo emissary --workdir /usr/share/emissary --config /etc/emissary/server.yml server database migrate
```
4. Redémarrer le service:
```shell
sudo systemctl restart emissary-server
```
5. Créer un jeton d'administration:
```shell
sudo emissary --workdir /usr/share/emissary --config /etc/emissary/server.yml server auth create-token --role writer --subject $(whoami) > .emissary-token
```
6. Vérifier l'authentification sur l'API:
```shell
emissary api agent query
```
Une réponse équivalente à la suivante devrait s'afficher:
```shell
+----+-------+------------+--------+-------------+-----------+
| ID | LABEL | THUMBPRINT | STATUS | CONTACTEDAT | UPDATEDAT |
+----+-------+------------+--------+-------------+-----------+
+----+-------+------------+--------+-------------+-----------+
```
### Appairer l'agent avec votre serveur
1. Sur le RaspberryPi, exécuter la commande suivante:
```shell
uci set emissary.agent.server_url='http://<server_ip>:3000'
uci commit emissary
reload_config
```
2. Via la commande `logread`, vérifier que l'agent arrive à se connecter avec le serveur:
```shell
logread -f
```
Un message de ce type devrait s'afficher:
```
Thu May 25 18:48:51 2023 daemon.info emissary[2202]: 2023-05-25 18:48:51.611 [INFO] <./internal/agent/controller/persistence/controller.go:58> (*Controller).Reconcile no changes detected, doing nothing {"controller": "persistence-controller"}
Thu May 25 18:48:51 2023 daemon.info emissary[2202]: 2023-05-25 18:48:51.675 [ERROR] <./internal/agent/controller/spec/controller.go:43>(*Controller).reconcileAgent unexpected agent status {"controller": "spec-controller", "agentID": 1, "status": 0}
Thu May 25 18:48:51 2023 daemon.info emissary[2202]: 2023-05-25 18:48:51.676 [INFO] <./internal/agent/controller/openwrt/uci_controller.go:32> (*UCIController).Reconcile could not find uci spec, doing nothing {"controller": "uci-controller"}
Thu May 25 18:48:51 2023 daemon.info emissary[2202]: 2023-05-25 18:48:51.677 [INFO] <./internal/agent/controller/app/controller.go:43> (*Controller).Reconcile could not find app spec {"controller": "app-controller"}
Thu May 25 18:48:51 2023 daemon.info emissary[2202]: 2023-05-25 18:48:51.678 [INFO] <./internal/agent/controller/proxy/controller.go:35>(*Controller).Reconcile could not find proxy spec {"controller": "proxy-controller"}
Thu May 25 18:48:51 2023 daemon.info emissary[2202]: 2023-05-25 18:48:51.680 [INFO] <./internal/agent/controller/mdns/controller.go:38>(*Controller).Reconcile could not find mdns spec {"controller": "mdns-controller"}
Thu May 25 18:48:51 2023 daemon.info emissary[2202]: 2023-05-25 18:48:51.680 [INFO] <./internal/agent/controller/openwrt/sysupgrade_controller.go:36> (*SysUpgradeController).Reconcile could not find sysupgrade spec, doing nothing {"controller": "sysupgrade-controller"}
```
3. Sur le serveur, vérifier que l'agent a pu s'enregistrer:
```shell
emissary api agent query
```
Un message de ce type devrait s'afficher:
```
+----+-------+-----------------------------------+--------+-----------------------------------+-----------------------------------+
| ID | LABEL | THUMBPRINT | STATUS | CONTACTEDAT | UPDATEDAT |
+----+-------+-----------------------------------+--------+-----------------------------------+-----------------------------------+
| 1 | | 21CnUATcboKCaheb2uczWCuoxTZtnp... | 0 | 2023-05-25 18:49:51.652680196 ... | "2023-05-25T18:49:51.589225817... |
+----+-------+-----------------------------------+--------+-----------------------------------+-----------------------------------+
```
Noter l'identifiant associé à l'agent.
4. Mettre à jour le statut de l'agent afin qu'il soit en capacité à récupérer ses spécifications:
```
emissary api agent update --agent-id <agent_id> --status 1
```
**Bravo, vous avez appairé votre premier agent et son serveur Emissary !**
## Aller plus loin
- [Déployer une configuration UCI personnalisée sur un agent](./deploy-uci-configuration.md)
- [Déployer un serveur mandataire inverse sur votre agent](./deploy-reverse-proxy.md)

View File

@ -6,7 +6,7 @@ After=network.target
Type=simple
Restart=always
WorkingDirectory=/usr/share/emissary
ExecStart=/usr/bin/emissary --config /etc/emissary/config.yml agent run
ExecStart=/usr/bin/emissary --config /etc/emissary/agent.yml agent run
[Install]
WantedBy=multi-user.target

View File

@ -4,9 +4,9 @@ After=network.target
[Service]
Type=simple
Restart=always
Restart=on-failure
WorkingDirectory=/usr/share/emissary
ExecStart=/usr/bin/emissary --config /etc/emissary/config.yml server run
ExecStart=/usr/bin/emissary --config /etc/emissary/server.yml server run
[Install]
WantedBy=multi-user.target

339
misc/resources/logo.svg Normal file
View File

@ -0,0 +1,339 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 620 620"
version="1.1"
id="svg110"
sodipodi:docname="logo.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview112"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="0.38064516"
inkscape:cx="310"
inkscape:cy="306.05932"
inkscape:current-layer="svg110" />
<defs
id="defs50">
<style
id="style2">.cls-1{fill:#fff;}.cls-2{fill:url(#Dégradé_sans_nom_22);}.cls-3{fill:url(#Dégradé_sans_nom_22-2);}.cls-4{fill:url(#Dégradé_sans_nom_22-3);}.cls-5{fill:#acdce9;}.cls-6{fill:none;}.cls-7{fill:url(#Dégradé_sans_nom_14);}.cls-8{fill:url(#Dégradé_sans_nom_14-2);}.cls-9{fill:url(#Dégradé_sans_nom_14-3);}.cls-10{fill:url(#Dégradé_sans_nom_14-4);}.cls-11{fill:url(#Dégradé_sans_nom_14-5);}.cls-12{fill:url(#Dégradé_sans_nom_14-6);}.cls-13{fill:url(#Dégradé_sans_nom_14-7);}</style>
<linearGradient
id="Dégradé_sans_nom_22"
x1="389.04"
y1="577.91"
x2="843.52"
y2="444.49"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
stop-color="#f9b233"
id="stop4" />
<stop
offset="0.05"
stop-color="#ef9630"
id="stop6" />
<stop
offset="0.12"
stop-color="#e2742c"
id="stop8" />
<stop
offset="0.2"
stop-color="#d65729"
id="stop10" />
<stop
offset="0.29"
stop-color="#cd3f26"
id="stop12" />
<stop
offset="0.39"
stop-color="#c72d24"
id="stop14" />
<stop
offset="0.51"
stop-color="#c22023"
id="stop16" />
<stop
offset="0.66"
stop-color="#bf1822"
id="stop18" />
<stop
offset="0.99"
stop-color="#be1622"
id="stop20" />
</linearGradient>
<linearGradient
id="Dégradé_sans_nom_22-2"
x1="347.14"
y1="435.21"
x2="801.63"
y2="301.79"
xlink:href="#Dégradé_sans_nom_22" />
<linearGradient
id="Dégradé_sans_nom_22-3"
x1="363.28"
y1="490.17"
x2="817.76"
y2="356.75"
xlink:href="#Dégradé_sans_nom_22" />
<linearGradient
id="Dégradé_sans_nom_14"
x1="295.82"
y1="531.3"
x2="915.72"
y2="148.45"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
stop-color="#36a9e1"
id="stop25" />
<stop
offset="0.01"
stop-color="#35a3db"
id="stop27" />
<stop
offset="0.09"
stop-color="#3281b9"
id="stop29" />
<stop
offset="0.17"
stop-color="#2f649c"
id="stop31" />
<stop
offset="0.26"
stop-color="#2d4c85"
id="stop33" />
<stop
offset="0.37"
stop-color="#2b3a72"
id="stop35" />
<stop
offset="0.49"
stop-color="#2a2d66"
id="stop37" />
<stop
offset="0.65"
stop-color="#29255e"
id="stop39" />
<stop
offset="0.99"
stop-color="#29235c"
id="stop41" />
</linearGradient>
<linearGradient
id="Dégradé_sans_nom_14-2"
x1="302.24"
y1="541.68"
x2="922.14"
y2="158.84"
xlink:href="#Dégradé_sans_nom_14" />
<linearGradient
id="Dégradé_sans_nom_14-3"
x1="308.78"
y1="537.64"
x2="795.27"
y2="237.19"
xlink:href="#Dégradé_sans_nom_14" />
<linearGradient
id="Dégradé_sans_nom_14-4"
x1="416.02"
y1="726.54"
x2="1047.24"
y2="336.7"
xlink:href="#Dégradé_sans_nom_14" />
<linearGradient
id="Dégradé_sans_nom_14-5"
x1="422.84"
y1="722.33"
x2="909.33"
y2="421.88"
xlink:href="#Dégradé_sans_nom_14" />
<linearGradient
id="Dégradé_sans_nom_14-6"
x1="265.42"
y1="482.7"
x2="896.65"
y2="92.86"
xlink:href="#Dégradé_sans_nom_14" />
<linearGradient
id="Dégradé_sans_nom_14-7"
x1="272.25"
y1="478.49"
x2="758.74"
y2="178.04"
xlink:href="#Dégradé_sans_nom_14" />
</defs>
<g
id="Calque_4"
data-name="Calque 4">
<circle
class="cls-1"
cx="310"
cy="310"
r="310"
id="circle52" />
</g>
<g
id="Calque_3"
data-name="Calque 3">
<path
class="cls-2"
d="M382.43,551.91c-2.64,12.13-5,37.74,16.31,54a11.79,11.79,0,0,0,10.58,1.87L536.08,569a11.7,11.7,0,0,0,7.55-7.05c3.86-10.27,8.25-33.2-17.06-54.7A11.78,11.78,0,0,0,515.5,505l-7.95,2.44L446.89,526l-56.42,17.28A11.72,11.72,0,0,0,382.43,551.91Z"
transform="translate(-270.7 -127.63)"
id="path55" />
<path
class="cls-3"
d="M479,384.26c4-2.64,8.43-5.3,13.2-7.93l.27-.14,0,0a4.79,4.79,0,0,0-4.38-.81l-88.29,27a4.79,4.79,0,0,0-3.24,3.35c-1.38,5.29-3.62,17.74,2.13,28.71a4.83,4.83,0,0,0,7.07,1.77,382.78,382.78,0,0,1,40.87-24.81,4.88,4.88,0,0,0,1.67-1.49C453.94,401.88,464.6,393.06,479,384.26Z"
transform="translate(-270.7 -127.63)"
id="path57" />
<path
class="cls-4"
d="M681.16,333.19a.1.1,0,0,0,0,.2c11.53,2.22,19,6.52,21,12.92,6.28,20.52-46.38,54.83-117.63,76.63-57.32,17.55-109.18,21.65-130.54,11.9-5.18-2.37-8.59-5.55-9.82-9.55a12.49,12.49,0,0,1-.15-6.41.13.13,0,0,0-.19-.14A342.31,342.31,0,0,0,405.47,443c-23.59,17.58-35.56,33.72-31.8,46,4.78,15.62,34.14,22.17,78.27,20.26a.12.12,0,0,1,.13.16l-3,9.55a.13.13,0,0,0,.16.16L508.59,501l13.77-4.21a.13.13,0,0,1,.12,0c1.58,1.16,3,2.32,4.45,3.49a.1.1,0,0,0,.1,0A977.1,977.1,0,0,0,638.75,472.9c136.34-41.74,238.73-102.19,228.68-135C859.38,311.6,781.74,311,681.16,333.19Z"
transform="translate(-270.7 -127.63)"
id="path59" />
<path
class="cls-5"
d="M694.39,348.33c-2-6.73-12.15-10.44-27.62-11.29a154.33,154.33,0,0,0-16.61.08c16,24.86-14.65,54-30.44,66.73C667.12,384.85,698.66,362.27,694.39,348.33Z"
transform="translate(-270.7 -127.63)"
id="path61" />
<path
class="cls-6"
d="M873.93,336.73c-2.86-12.89-15.64-20-33.51-23.45A283.91,283.91,0,0,1,861,371.4C871.09,360.09,876.52,348.34,873.93,336.73Z"
transform="translate(-270.7 -127.63)"
id="path63" />
<path
class="cls-7"
d="M299.08,479.05a2.32,2.32,0,0,0-2.2,2.73A309.22,309.22,0,0,0,323.68,562a2.27,2.27,0,0,0,2.43,1.21A283.59,283.59,0,0,1,299.91,479Z"
transform="translate(-270.7 -127.63)"
id="path65" />
<path
class="cls-8"
d="M363,548.81q6.94-2.13,13.73-3.79a2.31,2.31,0,0,0,1.63-1.53c.36-1.14.75-2.3,1.18-3.47a2.3,2.3,0,0,1,1.5-1.43l59.3-18.15a2.31,2.31,0,0,0-.74-4.52c-14.07.37-63.57-.07-72-24.18,0,0-5.47-10.13,4.16-25.33-1.49.5-3,1-4.51,1.45-18.64,5.7-46.44,10.25-67.28,11.15a283.59,283.59,0,0,0,26.2,84.17,2.25,2.25,0,0,0,.62-.15A298.13,298.13,0,0,1,363,548.81Z"
transform="translate(-270.7 -127.63)"
id="path67" />
<path
class="cls-9"
d="M363,548.81q6.94-2.13,13.73-3.79a2.31,2.31,0,0,0,1.63-1.53c.36-1.14.75-2.3,1.18-3.47a2.3,2.3,0,0,1,1.5-1.43l59.3-18.15a2.31,2.31,0,0,0-.74-4.52c-14.07.37-63.57-.07-72-24.18,0,0-5.47-10.13,4.16-25.33-1.49.5-3,1-4.51,1.45-18.64,5.7-46.44,10.25-67.28,11.15a283.59,283.59,0,0,0,26.2,84.17,2.25,2.25,0,0,0,.62-.15A298.13,298.13,0,0,1,363,548.81Z"
transform="translate(-270.7 -127.63)"
id="path69" />
<path
class="cls-10"
d="M582.7,722.94c157.87,0,285.85-128,285.85-285.85A286.63,286.63,0,0,0,861,371.4c-37.61,42-140.24,77.8-140.27,77.8s-98,40.65-188,55.81c0,0,0,0,0,0,.64.42,36.32,24.49,13,68.42h0L403.46,615.88h0a41.35,41.35,0,0,1-5.21-3.19h0q-6.9,2.55-14.24,4.81c-5.69,1.74-12.1,3.41-18.87,5A285.19,285.19,0,0,0,582.7,722.94Z"
transform="translate(-270.7 -127.63)"
id="path71" />
<path
class="cls-11"
d="M582.7,722.94c157.87,0,285.85-128,285.85-285.85A286.63,286.63,0,0,0,861,371.4c-37.61,42-140.24,77.8-140.27,77.8s-98,40.65-188,55.81c0,0,0,0,0,0,.64.42,36.32,24.49,13,68.42h0L403.46,615.88h0a41.35,41.35,0,0,1-5.21-3.19h0q-6.9,2.55-14.24,4.81c-5.69,1.74-12.1,3.41-18.87,5A285.19,285.19,0,0,0,582.7,722.94Z"
transform="translate(-270.7 -127.63)"
id="path73" />
<path
class="cls-12"
d="M390,402.12h0a42,42,0,0,1,1.88-5.3h0l102.24-31.3h0l5.49,5.94h0c.52-.28,89.13-46.1,171-40.81h0c.13,0,111.77-28.45,169.78-17.37-46.14-95.88-144.2-162-257.72-162-156.72,0-284,126.12-285.81,282.4,16.25-9.51,36.55-18.94,52.53-23.83A177.82,177.82,0,0,1,390,402.12Z"
transform="translate(-270.7 -127.63)"
id="path75" />
<path
class="cls-13"
d="M390,402.12h0a42,42,0,0,1,1.88-5.3h0l102.24-31.3h0l5.49,5.94h0c.52-.28,89.13-46.1,171-40.81h0c.13,0,111.77-28.45,169.78-17.37-46.14-95.88-144.2-162-257.72-162-156.72,0-284,126.12-285.81,282.4,16.25-9.51,36.55-18.94,52.53-23.83A177.82,177.82,0,0,1,390,402.12Z"
transform="translate(-270.7 -127.63)"
id="path77" />
<path
class="cls-5"
d="M613.5,341.7a453.93,453.93,0,0,0-50.22,12.41c-26.81,8.2-50.89,18-69.82,28-20.21,10.63-34.52,21.39-40,30.47-2.22,3.7-3,7.13-2.08,10.14,1,3.39,4.13,6,8.91,7.89,19,7.48,64.86,3.18,116.58-12C635.88,378.72,622.92,352.06,613.5,341.7Z"
transform="translate(-270.7 -127.63)"
id="path79" />
<circle
class="cls-1"
cx="432.9"
cy="112.17"
r="8.82"
id="circle81" />
<circle
class="cls-1"
cx="360.51"
cy="174.46"
r="4.41"
id="circle83" />
<circle
class="cls-1"
cx="253.85"
cy="124.11"
r="6.72"
id="circle85" />
<circle
class="cls-1"
cx="140.55"
cy="150.46"
r="4.41"
id="circle87" />
<circle
class="cls-1"
cx="221.77"
cy="190.68"
r="5.03"
id="circle89" />
<circle
class="cls-1"
cx="316"
cy="56.58"
r="4.41"
id="circle91" />
<circle
class="cls-1"
cx="272.6"
cy="497.14"
r="8.82"
id="circle93" />
<circle
class="cls-1"
cx="370.54"
cy="480.67"
r="4.41"
id="circle95" />
<circle
class="cls-1"
cx="434.95"
cy="420.7"
r="6.72"
id="circle97" />
<circle
class="cls-1"
cx="546.81"
cy="388.76"
r="4.41"
id="circle99" />
<circle
class="cls-1"
cx="495.3"
cy="463.34"
r="5.03"
id="circle101" />
<circle
class="cls-1"
cx="347.84"
cy="391.81"
r="4.41"
id="circle103" />
<circle
class="cls-1"
cx="346.13"
cy="559.72"
r="6.72"
id="circle105" />
<circle
class="cls-1"
cx="499.71"
cy="334.37"
r="4.41"
id="circle107" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB