Project homepage Mailing List  Warmcat.com  API Docs  Github Mirror 
    npro  
 Modern all-safe Rust Network Protocol library supporting h1, h2, h3, ws, wt sans-IO and with socket IO + tls
git clone https://npro.rs/repo/npro
 
root / src / power / p-ws-server.c
Author[]Andy Green <andy@warmcat.com> 2020-04-21 21:46 UTC
Committer[]Andy Green <andy@warmcat.com> 2020-04-21 21:46 UTC
Tree927373e681fdf97fe5f2d080114529d2e5fb6930   Raw Patch
 
sai development
sai development

Also add .sai.json
diff --git a/.sai.json b/.sai.json new file mode 100644 index 0000000..e284804 --- /dev/null +++ b/.sai.json @@ -0,0 +1,37 @@ +{ + "schema": "sai-1", + + "platforms": { + "linux-ubuntu-1804/x86_64-amd/gcc": { + "build": "mkdir build destdir;cd build;export CCACHE_DISABLE=1;cmake .. ${cmake};make -j4;cpack -G DEB" + }, + "linux-ubuntu-2004/x86_64-amd/gcc": { + "build": "mkdir build destdir;cd build;export CCACHE_DISABLE=1;cmake .. ${cmake};make -j;cpack -G DEB" + }, + "linux-fedora-32/x86_64-amd/gcc": { + "build": "mkdir build destdir;cd build;export CCACHE_DISABLE=1;cmake .. ${cmake};make -j;cpack -G RPM" + }, + "netbsd-OSX-catalina/x86_64-intel-i3/llvm": { + "build": "mkdir build destdir; cd build; export SAI_CPACK=\"-G ZIP\";export MACOSX_DEPLOYMENT_TARGET=10.14 ; cmake .. -DCMAKE_MAKE_PROGRAM=/usr/bin/make -DLWS_OPENSSL_INCLUDE_DIRS=/usr/local/opt/openssl@1.1/include -DLWS_OPENSSL_LIBRARIES=\"/usr/local/opt/openssl/lib/libssl.dylib;/usr/local/opt/openssl/lib/libcrypto.dylib\" ${cmake} && make -j && make -j DESTDIR=../destdir install && ctest -j4 --output-on-failure ${cpack}", + "default": false + }, + "windows-10/x86_64-amd/msvc": { + "build": "mkdir build && cd build && set SAI_CPACK=\"-G ZIP\" && cmake .. -DSAI_LWS_INC_PATH=\"C:\\Program Files (x86)\\libwebsockets\\include\" -DSAI_LWS_LIB_PATH=\"C:\\Program Files (x86)\\libwebsockets\\lib\\websockets_static.lib\" -DSAI_EXT_PTHREAD_INCLUDE_DIR=\"C:\\Program Files (x86)\\pthreads\\include\" -DSAI_EXT_PTHREAD_LIBRARIES=\"C:\\Program Files (x86)\\pthreads\\lib\\x64\\libpthreadGC2.a\" -DLWS_OPENSSL_INCLUDE_DIRS=\"C:\\Program Files\\OpenSSL\\include\" -DSAI_GIT2_INC_PATH=\"C:\\Program Files (x86)\\libgit2\\include\" -DSAI_GIT2_LIB_PATH=\"C:\\Program Files (x86)\\libgit2\\lib\\git2.lib\" -DLWS_OPENSSL_LIBRARIES=\"C:\\Program Files\\OpenSSL\\lib\\libcrypto.lib;C:\\Program Files\\OpenSSL\\lib\\libssl.lib\" ${cmake} && cmake --build . --config DEBUG && set CTEST_OUTPUT_ON_FAILURE=1 && ctest . -C DEBUG -j4 --output-on-failure ${cpack}", + "default": false + } + }, + + "configurations": { + "master+builder": { + "cmake": "", + "cpack": "&& cpack -C DEBUG $SAI_CPACK", + "artifacts": "build/sai-*.deb, build/sai-*.rpm" + }, + "builder-only": { + "cmake": "-DSAI_MASTER=0 ", + "platforms": "windows-10/x86_64-amd/msvc, netbsd-OSX-catalina/x86_64-intel-i3/llvm", + "cpack": "&& cpack -C DEBUG $SAI_CPACK", + "artifacts": "build/sai-*.deb, build/sai-*.rpm, build/sai-*.7z" + } + } +} diff --git a/CMakeLists.txt b/CMakeLists.txt index b46a3bc..3b08ae7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,26 +1,117 @@ +project(sai C) cmake_minimum_required(VERSION 2.8) include(CheckCSourceCompiles) -set(SRCS_M - src/master/sai-master.c - src/master/conf.c - src/master/ws.c -) -set(SRCS_B - src/builder/sai-builder.c - src/builder/conf.c - src/builder/protocol.c -) +set(CMAKE_SKIP_RPATH 1) + +find_package(Git) +if(GIT_EXECUTABLE) + execute_process( + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMAND "${GIT_EXECUTABLE}" describe --tags --always + OUTPUT_VARIABLE GIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) +endif() + + +set(PACKAGE "sai") +set(CPACK_PACKAGE_NAME "${PACKAGE}") +set(CPACK_PACKAGE_VERSION_MAJOR "0") +set(CPACK_PACKAGE_VERSION_MINOR "1") +set(CPACK_PACKAGE_VERSION_PATCH "99") +set(CPACK_PACKAGE_RELEASE 1) +if (WIN32 OR MSVC) + set(CPACK_GENERATOR "7Z") +else() + set(CPACK_GENERATOR "RPM") +endif() +set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") +set(LWS_BUILD_HASH ${CPACK_PACKAGE_VERSION}-${GIT_HASH}) +message("Git commit hash: ${LWS_BUILD_HASH}") + +set(CPACK_PACKAGE_VENDOR "andy@warmcat.com") +set(CPACK_PACKAGE_CONTACT "andy@warmcat.com") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PACKAGE} ${PACKAGE_VERSION}") +set(CPACK_RPM_COMPONENT_INSTALL ON) +set(CPACK_RPM_PACKAGE_RELEASE_DIST ON) +set(CPACK_RPM_FILE_NAME "RPM-DEFAULT") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/cmake/Description") + +#set(CPACK_DEBIAN_PACKAGE_RELEASE_DIST 1) +set(CPACK_DEB_COMPONENT_INSTALL 1) +set(CPACK_DEBIAN_FILE_NAME "DEB-DEFAULT") +set(CPACK_DEBIAN_PACKAGE_COMPONENT 1) +set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) +set(CPACK_DEBIAN_MASTER_PACKAGE_NAME "sai-master") +set(CPACK_DEBIAN_BUILDER_PACKAGE_NAME "sai-builder") + +option(SAI_MASTER "Build the master / server part" ON) +option(SAI_BUILDER "Build the builder / client part" ON) + +set(LWS_OPENSSL_LIBRARIES CACHE PATH "Path to the OpenSSL library") +set(LWS_OPENSSL_INCLUDE_DIRS CACHE PATH "Path to the OpenSSL include directory") + +if (NOT SAI_MASTER AND NOT SAI_BUILDER) + message(FATAL_ERROR "Have to build both or one or the other") +endif() set(LIB_DIR lib CACHE PATH "Install dir for libraries") set(BIN_DIR bin CACHE PATH "Install dir for executables") set(INCLUDE_DIR include CACHE PATH "Install dir for header files") set(DATA_DIR share CACHE PATH "Install dir for data files") +set(SAI_LWS_INC_PATH CACHE PATH "Path to lws headers dir") +set(SAI_LWS_LIB_PATH CACHE PATH "Path to lws library dir") +set(SAI_EXT_PTHREAD_INCLUDE_DIR CACHE PATH "Path to an external pthreads include directory") +set(SAI_EXT_PTHREAD_LIBRARIES CACHE PATH "Path to an external pthreads library") + +if (NOT SAI_LWS_INC_PATH) + find_path(XSAI_LWS_INC_PATH "libwebsockets.h") + set(SAI_LWS_INC_PATH ${XSAI_LWS_INC_PATH}) + if (NOT XSAI_LWS_INC_PATH) + message(FATAL_ERROR "Can't find lws headers") + endif() +endif() +if (NOT SAI_LWS_LIB_PATH) + find_library(XSAI_LWS_LIB_PATH NAMES "websockets") + set(SAI_LWS_LIB_PATH ${XSAI_LWS_LIB_PATH}) + if (NOT SAI_LWS_LIB_PATH) + message(FATAL_ERROR "Can't find lws lib") + endif() +endif() + +if (SAI_EXT_PTHREAD_INCLUDE_DIR) + set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES};${SAI_EXT_PTHREAD_INCLUDE_DIR}) + include_directories(${SAI_EXT_PTHREAD_INCLUDE_DIR}) + + set(CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS} " -DHAVE_STRUCT_TIMESPEC=1") +endif() + +message("LWS: header path ${SAI_LWS_INC_PATH}, lib path ${SAI_LWS_LIB_PATH}") + # # let the code know where the assets were installed # -set(CMAKE_C_FLAGS "-D LWS_DATA_DIR=\\\"${CMAKE_INSTALL_PREFIX}/${DATA_DIR}\\\" ${CMAKE_C_FLAGS}") +if (NOT MSVC) + set(CMAKE_C_FLAGS "-pthread ${CMAKE_C_FLAGS}") +endif() + +if (APPLE) + set(CMAKE_C_FLAGS "-I/usr/local/include ${CMAKE_C_FLAGS}") + link_directories("/usr/local/lib") +endif() + +if (LWS_OPENSSL_INCLUDE_DIRS) + set(CMAKE_C_FLAGS "-I\"${LWS_OPENSSL_INCLUDE_DIRS}\" ${CMAKE_C_FLAGS}") +endif() + + +set(CMAKE_C_FLAGS "-DBUILD_INFO=\\\"${LWS_BUILD_HASH}\\\" ${CMAKE_C_FLAGS}") + +CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint + main(void) {\n#if defined(LWS_HAVE_LIBCAP)\n return + 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_LIBCAP) # If we are being built as part of lws, confirm current build config supports @@ -81,61 +172,32 @@ MACRO(require_lws_config reqconfig _val result) endif() ENDMACRO() +set(CMAKE_REQUIRED_INCLUDES ${SAI_LWS_INC_PATH}) + set(requirements 1) -require_lws_config(LWS_ROLE_H1 1 requirements) -require_lws_config(LWS_ROLE_WS 1 requirements) -require_lws_config(LWS_WITHOUT_SERVER 0 requirements) -require_lws_config(LWS_WITHOUT_CLIENT 0 requirements) -require_lws_config(LWS_WITH_UNIX_SOCK 1 requirements) +require_lws_config(LWS_ROLE_H1 1 requirements) +require_lws_config(LWS_ROLE_WS 1 requirements) if (requirements) - add_executable("sai-master" ${SRCS_M}) - add_executable("sai-builder" ${SRCS_B}) - - if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) + + if (CMAKE_COMPILER_IS_GNUCC OR + CMAKE_COMPILER_IS_GNUCXX OR + (CMAKE_C_COMPILER_ID MATCHES "Clang") OR + (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) set(CMAKE_C_FLAGS "-Wall -Wsign-compare -Wignored-qualifiers -Wtype-limits -Wuninitialized -Werror -Wundef ${CMAKE_C_FLAGS}" ) endif() - if (websockets_shared) - target_link_libraries("sai-master" websockets_shared) - add_dependencies("sai-master" websockets_shared) - - target_link_libraries("sai-builder" websockets_shared) - add_dependencies("sai-builder" websockets_shared) - else() - target_link_libraries("sai-master" websockets) - target_link_libraries("sai-builder" websockets) + if (SAI_MASTER) + add_subdirectory(src/master) endif() + if (SAI_BUILDER) + add_subdirectory(src/builder) + if (NOT MSVC AND NOT WIN32) + add_subdirectory(src/device) + add_subdirectory(src/expect) + endif() + endif() + +endif(requirements) +include(CPack) -# -# libgit2 paths -# -find_path( GIT2_INC_PATH NAMES "git2.h") -find_library(GIT2_LIB_PATH NAMES "git2") - -if (GIT2_INC_PATH AND GIT2_LIB_PATH) - include_directories(BEFORE "${GIT2_INC_PATH}") -else() - message(FATAL_ERROR " Unable to find libgit2") -endif() - -target_link_libraries("sai-master" ${GIT2_LIB_PATH}) -target_link_libraries("sai-builder" ${GIT2_LIB_PATH}) - - install(TARGETS sai-master sai-builder - - LIBRARY DESTINATION "${LIB_DIR}${LIB_SUFFIX}" COMPONENT libraries - ARCHIVE DESTINATION "${LIB_DIR}${LIB_SUFFIX}" COMPONENT libraries - RUNTIME DESTINATION "${BIN_DIR}" COMPONENT libraries - PUBLIC_HEADER DESTINATION "${INCLUDE_DIR}" COMPONENT dev) - - install(FILES assets/index.html - assets/sai.css - assets/sai.js - assets/lws-common.js - assets/sai.svg - assets/favicon.ico - assets/strict-csp.svg - DESTINATION "${DATA_DIR}/sai/assets") - -endif() diff --git a/README.md b/README.md index 8a9b677..c1be368 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,293 @@ ![Sai](./assets/sai.svg) `Sai` (pronouced like 'sigh', "Trial" in Japanese) is a very lightweight -network-aware remote CI runner. It knows how to use `systemd-nspawn` and -`overlayfs` to build remote git repos on a variety of distro versions very -cheaply. +network-aware distributed CI builder and coordinating server. It knows how to +use `systemd-nspawn` and `overlayfs` to build remote git repos on a variety of +distro versions very cheaply. On small targets, it can also build on the host rootfs directly, and can also run on an RPi3-class 'buddy' to flash and collect results (eg, over serial, or gpio) from even smaller targets. -A self-assembling constellation of Sai Builders make their own connections to a -master, who then received hook notifications, distributes work concurrently over -idle builders that have the required environment, and logs results. +A self-assembling constellation of Sai Builder clients make their own +connections to one or more Sai Masters, who then receive hook notifications, +read JSON from the project describing what set of build variations and tests it +should run on which platforms, and distributes work concurrently over idle +builders that have the required environment, and logs results at the master +accessible by a web interface. + +![sai overview](./READMEs/sai-overview.png) ## General approach - - Sai builders for various platforms and scenarios are configured and run - independently... they maintain "nailed-up" connections to the master over - wss, and announce their capabilities. The builders do not need any listening - ports or other central management this way. + - Distributed "builders" run the `sai-builder` daemon from inside the build + platform they want to provide builds for (ie, inside a systemd-nspawn or VM). + + - The `sai-builder` daemons maintain nailed-up outgoing client wss connections + to a central `sai-master`, which manages the ad-hoc collection of builders + it finds, and provides a web UI (http/2 and wss2). + +![sai git flow](./READMEs/sai-ov2.png) + + - When a git repo that wants sai tests is updated, a push hook performs a POST + notiftying the Sai master of a new push event which creates an entry in an + event sqlite3 database. (The master does not need to access the repo that + sends the git notification). The hook sends the master information about the + push and the revision of `.sai.json` from the new commit in the POST body... + the master parses that JSON to fill an sqlite3 database with tasks and + commandline options for the build on platforms mentioned in `.sai.json`. - - When a git repo being monitored is updated, a hook performs a GET notiftying - the Sai master. + - Builders typically build many variations of the same push, so they use a + local git mirror only on the builder to reduce the load on the repo that + was updated. For large trees, being able to already have previous trees + as a starting point for git dramatically reduces the time compared to a full + git fetch. - - The Sai master fetches `.sai.json` from the repo and queues jobs on connected - builders that have the platform and OS versions requested for build in - the `.sai.json`. + - The master hands out waiting tasks on connected idle builders that offer the + requested platforms, which build them concurrently. The builders may be + inside a protected network along with the repos they connect to; both the + builders and the repo only make outgoing https or wss connections to the + master. - - On the builders, result channels (like stdout, stderr) with logs and - results are first listed and then streamed back to the master and the build - proceeds. Discrete files may also be streamed to the master like this. + - On the builders, result channels (like stdout, stderr, eventually others like + `/dev/acm0`) with logs and results are streamed back to the master over a wss + link as the build proceeds, and are stored in an event-specific sqlite3 + database for scalability. + + - Builders can run CTest or other tests after the build and collect the + results (CTest has the advantages it's lightweight and crossplatform). - The master makes human readable current and historical results available - in realtime over https + in realtime over https web interface + + - One `sai-builder` daemon can be configured to offer multiple instances of + independent platform build, and multiple platform builds (eg, cross + toolchains). + +## Build flow and support for embedded + +![build flow](READMEs/sai-build-test-flow.png) + +Testing is based around CTest, it can either run on the build host inside the +container, or run on a separate embedded device. In the separate case, the flow +can include steps to flash the image that was built and to observe and drive +testing via usually USB tty devices. IO on these additional ttys is logged +separately than IO from build host subprocess stdout and stderr. + +### Sharing embedded devices on the test host + +Embedded devices are actually build host-wide assets that may be called upon +and shared by different containers and different build platforms. For +example, a cross-built flash image on Centos8 and another cross-built on +Ubuntu Bionic for the same platform may want to flash and test on the same +pool of embedded devices. Even images from different build platforms for the +same kind of device may wish to flash the same embedded device, where the +device can be flashed to completely different OSes. + +Devices may be needed by post-build actions, but they are not something +a sai-builder for a platform can "own" or manage by itself. Instead they are +requested from inside the build action by another tool built with `sai-builder`, +`sai-device`, which reads shared JSON config describing the available devices +and platforms they are appropriate for. + +Rather than reserve the device when the build is spawned, the reservation +needs to happen only when the build inside the build context has completed. +That in turn means that a different sai utility has to run at that time from +inside the build process, in order that it can set things in the already- +existing subprocess environment. + +``` +"devices": [ + { + "name": "esp32-heltec1", + "type": "esp32", + "compatible": "freertos-esp32", + "description": "ESP32 8MByte SPI flash plus display", + "ttys": [ + "/dev/serial/by-path/pci-0000:03:00.3-usb-0:2:1.0-port0" + ] + } +] +``` + +Devices are logically defined inside a separate conf file +`/etc/sai/devices/conf` on the host or vm, and containers should bind a ro +mount of the file at the same place in their /. This avoids having to maintain +a bunch of different files every time a new device is added. For platform +builders based in a VM, these have a boolean relationship with IO ports, they +either must wholly own them or are unaware of them: this means they can't +participate in sharing device pools but must be allocated their own with its +own config file inside the VM listing those. + +When the build process wants to acquire an embedded device of a particular type +for testing, it runs in the building context, eg, `sai-device esp32 ctest`. + +This waits until it can flock() all the ttys of one of the given type of +configured devices ("esp32" in the example), sets up environment vars for each +`SAI_DEVICE_<ttyname>`, eg, +`SAI_DEVICE_TTY0=/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0`, +then executes the given program (`ctest` in the example) as a child process +inside the build context. Although it has an fd on the tty so it can flock() +it, `sai-device` does not read or write on the fd itself. + +Baud rate is not considered an attribute of the tty definition but something set +for each sai-expect. + +When the child process or build process ends, the locking is undone and the +device may be acquired by another waiting `sai-device` instance in the same or +different platform build context. + +The underlying locking is done hostwide using flock() on bind mounts of +the tty devices, the other containers will observe the locking no matter who +did it. + +Availability of the device node via an environment variable means that CTest +or other scripts are able to directly write to the device. + +### Logging of device tty activity + +The `sai-builder` instance opens three local listening Unix Domain Sockets for +every platform, these accept raw data which is turned into logs on the +respective logging channel and passed up to the `sai-master` for storage and +display like the other logs. + +The paths of these "log proxy" Unix Domain Sockets are exported as environment +variables to the child build and test process as follows + +Environment Var|Ch#|Meaning|Example +---|---|---|--- +SAI_LOGPROXY|3|Build progress logging|@com.warmcat.com.saib.logproxy.warmcat_com-freertos-esp32.0 +SAI_LOGPROXY_TTY0|4|Device tty0|@com.warmcat.com.saib.logproxy.warmcat_com-freertos-esp32.0.tty0 +SAI_LOGPROXY_TTY1|5|Optional Device tty1|@com.warmcat.com.saib.logproxy.warmcat_com-freertos-esp32.0.tty1 + +Because some kinds of device share the same tty for flashing the device, at +which time nothing else must be reading from the tty, tty activity is only +captured and proxied during actual user testing by `sai-expect`, which is run at +will be the CTest script. In this way, device ttys are only monitored while +test are ongoing; however the kernel will buffer traffic that nobody has read +until the next reading consumes it. - - The Sai master node also has a builder integrated. So to get started you - can do it all on one machine. +### Sai device tty logging -## Creating a selfsigned TLS cert +Devices may have multiple ttys defined, for example a device with separate ttys +and log channels for a main cpu and a coprocessor is supported. -You can create a P-521 EC key and self-signed certificate for use with Sai -master like this: +The ttys listed on devices have their own log channel index and are timestamped +according to when they were read from the tty. In the event many channels are +"talking at once", in the web UI the different log channel content appears in +different css colours and in chunks of 100 bytes or so, which tends to keep +isolated lines of logging intact. + +## Non-Linux: use /home/sai in the main rootfs + +For OSX and other cases that doesn't support overlayfs, the same flow occurs +just in the main rootfs /home/sai instead of the overlayfs /home/sai. + +It means things can only be built in the context of the main OS, but since OSX +doesn't have different distros, which is the main use of the Linux overlayfs +feature, it's still okay. + +## Builder instances + +The config JSON for sai-builder can specify the number of build instances for +each platform. These instances do not have any relationship about what they +are building, just they run in the same platform context (and are managed by +the one `sai-builder` process). They each check out their own build tree +independently, so they can be engaged building different versions or different +trees concurrently inside the platform. + +Tests have to take care to disambiguate which instance they are running on, +since the network namespace is shared between instances that are running in the +same sai-builder process on the same platform. An environment var +`SAI_INSTANCE_IDX` is available inside the each build context set to 0, 1, etc +according to the builder instance. + +For network related tests, `SAI_INSTANCE_IDX` should be referred to when +choosing, eg, a test server port so it will not conflict with what other +builders may be doing in parallel. + +## Builder git caching + +For each `<saimaster-project>`, the builder maintains a local git cache. This is +updated once when the new ref appears and then the related tests check out a +fresh image of their ref from that each time. This is very fast after the first +update, because it doesn't even involve the network but fetching from the local +filesystem. + +## systemd-nspawn support + +On Linux, it's recommended to use systemd-nspawn to provide multiple distro +environments conveniently on one machine. There are instructions for setting +up individual virtual ethernet devices managed by nmcli on the host. + +`sai-builder` also supports running inside a KVM / QEMU VM transparently as well, +eg for windows or emulated architecture VMs. + +## `sai` builder user + +Builds happen using a user `sai` and on the builder, files are only created +down `/home/sai` or `\Users\sai`. ``` -$ openssl ecparam -name secp521r1 -genkey -param_enc explicit -out sai-selfsigned-key.pem -$ echo -e "\n\n\n\n\n\n" | openssl req -new -x509 -key sai-selfsigned-key.pem -out sai-selfsigned-cert.pem +# useradd -u883 -gnogroup sai -d/home/sai -m -r ``` -There's a helper script `sudo ./scripts/gen-selfsigned-certs.sh` to do this and -install the results in `/usr/local/share/sai`, you can override the install -directory by giving it as a commandline argument to the script. +## build filesystem layout -Or alternatively you can use your own PEM certificate and key from a trusted -certifcate vendor, which matches your domain name. + - /home/sai/ + - git-mirror/ + - `remote git url`_`project name` -- individual git mirrors + - jobs/ + - `master hostname`-`platform name`-`instance index`/ + - `project_name`/ - checkouts and builds occur in here -Self-signed is secure, but your browser will make you explicitly trust it before -it will let you use it. Afterwards, your browser will notice if the certificate -is different and untrusted for that site and warn you. Because you must -generate it after install, your selfsigned cert will be unique. +## Build steps +Building sai produces two different apps, `sai-master` and `sai-builder`. +`sai-master` is a ws and http server that coordinates activities, and +`sai-builder` is run on each machine that wants to offer build services to +one or more masters. By default, both are built, but you can use a cmake +option `-DSAI_MASTER=0` and `-DSAI_BUILDER=0` to disable one or the other. + +First you must build lws with appropriate options. + +For redhat type distros, you probably need to add /usr/local/lib to the +/etc/ld.so.conf before ldconfig can rgister the new libwebsockets.so + +``` +$ git clone https://libwebsockets.org/repo/libwebsockets +$ cd libwebsockets && mkdir build && cd build && \ + cmake .. -DLWS_UNIX_SOCK=1 -DLWS_WITH_STRUCT_JSON=1 \ + -DLWS_WITH_STRUCT_SQLITE3=1 -DLWS_WITH_GENCRYPTO=1 -DLWS_WITH_SPAWN=1 \ + -DLWS_WITH_SECURE_STREAMS=1 -DLWS_WITH_THREADPOOL=1 +$ make -j && sudo make -j install && sudo ldconfig +``` + +The actual cmake options needed depends on if you are building sai-master and / or +sai-builder. + +Feature|lws options +---|--- +either|`-DLWS_WITH_STRUCT_JSON=1` `-DLWS_WITH_SECURE_STREAMS=1` +master|`-DLWS_UNIX_SOCK=1` `-DLWS_WITH_GENCRYPTO=1` `-DLWS_WITH_STRUCT_SQLITE3=1` +builder|`-DLWS_WITH_SPAWN=1` `-DLWS_WITH_THREADPOOL=1` + +Similarly the two daemons bring in different dependencies + +Feature|dependency +---|--- +either|libwebsockets +master|libsqlite3 +builder|libgit2 pthreads + +#### Linux + +``` +$ git clone https://warmcat.com/repo/sai +$ cd sai && mkdir build && cd build && cmake .. && make && make install +$ sudo cp ../scripts/sai-builder.service /etc/systemd/system +$ sudo mkdir -p /etc/sai/builder +$ sudo cp ../scripts/builder-conf /etc/sai/builder/conf +$ sudo vim /etc/sai/builder/conf +$ sudo systemctl enable sai-builder +``` diff --git a/READMEs/README-build-windows.md b/READMEs/README-build-windows.md new file mode 100644 index 0000000..e709a07 --- /dev/null +++ b/READMEs/README-build-windows.md @@ -0,0 +1,60 @@ +# Building Sai Builder for windows + +The overall flow is + + - build lws + - build libgit2 + - build sai-builder piggybacking on pthreads built for lws + +## Building lws + +Refer to [the instructions in lws](https://libwebsockets.org/git/libwebsockets/tree/READMEs/README.build-windows.md) +for how to install `cmake`, `git` and the toolchain on windows +also used in the next steps here. + +For windows + sai-builder, the following lws cmake config is suitable + +``` +> cmake .. -DLWS_HAVE_PTHREAD_H=1 -DLWS_EXT_PTHREAD_INCLUDE_DIR="C:\Program Files (x86)\pthreads\include" -DLWS_EXT_PTHREAD_LIBRARIES="C:\Program Files (x86)\pthreads\lib\x64\libpthreadGC2.a" -DLWS_WITH_MINIMAL_EXAMPLES=1 -DLWS_WITH_THREADPOOL=1 -DLWS_UNIX_SOCK=1 -DLWS_WITH_STRUCT_JSON=1 -DLWS_WITH_SPAWN=1 -DLWS_WITH_SECURE_STREAMS=1 -DLWS_WITH_DIR=1 +``` + +## Building libgit2 + +``` +> git clone https://github.com/libgit2/libgit2.git +> cd libgit2 ; mkdir build ; cd build +> cmake .. -DBUILD_CLAR=OFF +> cmake --build . --config DEBUG +``` + +I couldn't get runas to do anything useful, I opened an Admin-privileged +cmd window and did + +``` +> cd \Users\agreen\libgit2\build +> cmake --install . --config DEBUG +> exit +``` + +## Building Sai-builder + +``` +> git clone https://warmcat.com/repo/sai +> cd sai ; mkdir build ; cd build +> cmake .. -DSAI_MASTER=0 -DSAI_LWS_INC_PATH="\Users\agreen\libwebsockets\build\include" -DSAI_LWS_LIB_PATH="\Users\agreen\libwebsockets\build\lib\Debug\websockets_static.lib" -DLWS_OPENSSL_INCLUDE_DIRS="\Program Files\OpenSSL\include" -DSAI_GIT2_INC_PATH="\Program Files (x86)\libgit2\include" -DSAI_GIT2_LIB_PATH="\Program Files (x86)\libgit2\lib\git2.lib" -DSAI_EXT_PTHREAD_INCLUDE_DIR="C:\Program Files (x86)\pthreads\include" -DLWS_OPENSSL_LIBRARIES="\Program Files\OpenSSL\lib\libcrypto.lib;\Program Files\OpenSSL\lib\libssl.lib" -DSAI_EXT_PTHREAD_LIBRARIES="C:\Program Files (x86)\pthreads\lib\x64\libpthreadGC2.a" +``` + +## Configuring Sai-builder + +``` +>net user sai /add +``` + +As administrator + +``` +> mkdir \ProgramData\sai\builder +``` + +Then create the config JSON in `\ProgramData\sai\builder\conf`, using the +platform name `windows-10`. diff --git a/READMEs/README-gitolite-hook.md b/READMEs/README-gitolite-hook.md new file mode 100644 index 0000000..bf566ea --- /dev/null +++ b/READMEs/README-gitolite-hook.md @@ -0,0 +1,50 @@ +# gitolite hook + +``` +#!/bin/sh + +if [ $(git rev-parse --is-bare-repository) = true ] +then + RN=$(basename "$PWD") +else + RN=$(basename $(readlink -nf "$PWD"/..)) +fi + +RN=${RN%.git} + +echo sai update hook $RN... +rm -f .sai.json +git show $3 -- .sai.json | patch -p1 --merge +cat .sai.json + +TF=`mktemp` + +echo "{\"schema\":\"com-warmcat-sai-notification\"," > $TF +echo " \"action\":\"repo-update\"," >> $TF +echo " \"repository\":{" >> $TF +echo " \"name\":\"$RN\"" >> $TF +echo " }," >> $TF +echo " \"nonce\":\"`dd if=/dev/urandom bs=32 count=1 | sha256sum | cut -d' ' -f1`\"," >> $TF +echo " \"ref\":\"$1\"," >> $TF +echo " \"hash\":\"$3\"" >> $TF +echo -n " \"saifile\":\"" >> $TF +# disallow any nested JSON monkey business by base64-encoding it +cat .sai.json | base64 >> $TF +echo "\"" >> $TF +echo "}" >> $TF + +HM=`cat $TF | sha256hmac -k /etc/sai/private/lws-sai-notification-token | cut -d' ' -f1` + +cat $TF +echo $HM + +curl --header "authorization: sai sha256=$HM" \ + -F"file=@$TF;type=application/json" \ + -A "sai-notifier" \ + https://warmcat.com/sai/update-hook +#curl --header "X-Sai-Signature: sha256=$HM" -F"file=@$TF;name=notification;type=application/json" -A "sai-notifier" http://127.0.0.1:4444 + +rm -f $TF + +exit 0 +``` diff --git a/READMEs/README-platform-naming.md b/READMEs/README-platform-naming.md new file mode 100644 index 0000000..7d10302 --- /dev/null +++ b/READMEs/README-platform-naming.md @@ -0,0 +1,130 @@ +# Sai platform tuples + +Platform names in sai capture the OS, arch and toolchain details in a string in +a way that allows more or less specificity for matching. + +The general format is "os/arch[/toolchain]", eg, "linux-fedora/x86_64/gcc". The +elements may contain increasingly specific details by -, eg, +"linux-fedora-32/x86_64-amd", or "freertos-espidf/xl6-esp32" + +Builders should describe their platform in full detail, applications that want +to use the platforms to perform tests should only provide the detail level they +care about. + +Instead of direct string matching between what the builders offer and the +project wants to be built on, the builders are matched from the left for both +the platform and architecture individually. So, a project wanting +"linux-fedora/x86_64" will successfully match on a builder offering +"linux-fedora-32/x86_64-amd". But a project requesting "linux-fedora-31/x86_64" +will not match it. + +**NOTE:** dots may not be used in platform tuple strings + +## OS part + +The OS class name should be first and is mandatory, eg, "linux" or "freertos". +The OS name is next, eg, "linux-fedora" or "freertos-espidf". + +Builders should go on to give more details, eg, versioning if that is relevant +for the platform, eg, "fedora-32". + +Projects using `.sai.json` to specify tests may choose how much specificity to +provide, eg, just "linux-fedora" to run on whatever fedora version the builders +provide. + +### Well-known OS strings + +OS|meaning +---|--- +linux-fedora| +linux-ubuntu| +linux-debian| +linux-gentoo| +linux-centos| +linux-android| +freertos-espidf|FreeRTOS-based espressif sdk +freertos-linkit|FreeRTOS-based Mediatek linkit SDK +netbsd-OSX| +netbsd-iOS| +windows| + + +## Architecture part + +Similarly builders should specify at least both the arch and the vendor name of +the architecture, eg, "x86_64-amd", or "x86_64-intel". Projects that don't care +can just specify "x86_64", ones that do care can control what it can build on +more specifically, eg, "x86_64-amd". + +### Well-known architecture strings + +arch|meaning +---|--- +`arm32`|32-bit arm, -m0, -m0+, -m3, -m4, -a9 etc may follow +`arm32-m4-mt7697`|Mediatek linkit chip +`aarch64`|any 64-bit arm +`aarch64-a72-bcm2711`|Raspberry Pi 4 CPU +`aarch64-a72-bcm2711-rpi4`|Raspberry Pi 4 CPU in an actual RPi4 +`x86_64`|Intel 64-bit (aka amd64) +`x86_64-amd`|Intel 64-bit implemented in an AMD chip +`x86_64-intel`|Intel 64-bit implemented in an Intel chip +`xl6`|Xtensa xl6 +`xl6-esp32`|Xtensa xl6 implemented in an ESP32 +`riscv32`|32-bit RiscV +`riscv64`|64-bit RiscV + +## Toolchain part + +On many platforms there is a choice of toolchain between, eg, gcc and llvm now, +and sometimes others like Intel compiler, mingw, etc. For that reason it has to +be treated as another "dimension" in the platform tuple. + +### Well-known toolchain strings + +toolchain|meaning +---|--- +`gcc`|OS gcc version +`llvm`|OS llvm version +`msvc`|Visual Studio toolchain +`mingw32`|Mingw compiler 32-bit +`mingw64`|Mingw compiler 64-bit + +`-cross` may be appended indicating it's a cross-build rather than targeted at +the same host. + +## Examples + +Platform|Description +---|--- +`linux-fedora/x86_64`|matches any version of fedora on x86_64 the builders provide +`linux-fedora/x86_64-amd`|as above but only on an amd builder +`linux-fedora-32`|f32 on any arch +`freertos/arm32`|any freertos-based RTOS on any arm32 +`freertos-espidf/xl6-esp32/gcc-cross`|the usual esp-idf based esp32 project +`freertos-arduino/xl6-esp32/gcc-cross`|arduino OS on esp32 +`linux-ubuntu-2004/aarch64-a72-bcm2711-rpi4/gcc`|Raspberry Pi 4 + +## Sai builder conf host names + +The `"host":` member of the builder configuration also has structure. +It indicates build host platform details and the location and virtualization +situation of the `sai-builder` instance using that conf. + +Its format is `"<plat>[,<plat>]"`; the platform specification follows the +platform description above, except the toolchain part is optionally replaced by +the hostname. + +Eg, `"linux-fedora-32/riscv-qemu,linux-fedora/x86_64-amd/noi"`. + +### Indicating virtualization + +The host part of the string can indicate its virtualization by adding +a well-known suffix to the arch part, ie, indicating that the "implementor" of +the arch "device" is qemu. + +Well-known virt name|Meaning +---|--- +-nspawn|systemd-nspawn hosted by the string to the right +-qemu|qemu instance hosted by the string to the right + +Eg, `"windows-10/x86_64-amd-qemu, linux-fedora/x86_64-amd/noi"` diff --git a/READMEs/README-sai-json.md b/READMEs/README-sai-json.md new file mode 100644 index 0000000..12a6686 --- /dev/null +++ b/READMEs/README-sai-json.md @@ -0,0 +1,96 @@ +# .sai.json format + +Projects can inform Sai of what build and test variations they want to run +on push using a `.sai.json` file in the top level dir of the project. + +The git push hook isolated this file from the pushed tree and passes it to +the sai-master along with the rest of the push notification information in +a signed JSON structure. + +## Simple .sai.json file + +``` +{ + "schema": "sai-1", + + # comments are OK + # + "platforms": { + "linux-ubuntu-xenial-amd64": { + "build": "mkdir build destdir;cd build;export CCACHE_DISABLE=1;cmake .. ${cmake};make -j;make -j DESTDIR=../destdir install && ctest -j4 --output-on-failure" + } + # you can continue to list platforms here + }, + + "configurations": { + "plain": { + "cmake": "", + } + } +} +``` + +The effect of the above is a single build using a `linux-ubuntu-xenial-amd64` +platform builder with the arguments + +``` + mkdir build destdir; + cd build; + export CCACHE_DISABLE=1; + cmake ..; + make -j; + make -j DESTDIR=../destdir install && + ctest -j4 --output-on-failure +``` + +### Platforms section and default + +In the top level `"platforms"` section you describe the different build +platforms you want to build and test on... the specified platforms must have +builders that have connected themselves to the sai-master. + +If you don't give a `"default": false` entry in the platform definition, the +platform is assumed to apply to all the configurations listed afterwards. If +you do set `"default": false` on a platform, it's taken to mean you will list +configurations you want it to apply to manually and that platform is not +automatically used otherwise. + +### Configuration section + +#### cmake + +The configuration section lists build variations, the `"cmake"` entry allows +you to give additional cmake options like `-DSOMEFEATURE=1` that together define +the build variation. These are applied additionally to the platform-specific +cmake options where the `${cmake}` token appears. + +#### platforms + +You can also give a `"platforms"` entry in each configuration specifiying which +platforms should build this configuration in a comma-separated list. If you +don't give a `"platforms"` entry on a configuration, all default platforms will +be applied. You can also list additional, non-default platform names you +want to apply, like + +``` + "platforms": "windows-10, linkit-cross, linux-debian-buster-arm32" +``` + +those will be applied on top of the defaults. Other options are to disable +given default platforms: + +``` + "platforms": "not linux-ubuntu-xenial-amd64" +``` + +or to disable all default platforms and just use the specified ones: + +``` + "platforms": "none, windows-10" +``` + +#### artifacts + +You can also give a comma-separated list of build artifacts, these are +arbitrary binary files which will be uploaded to sai-master and made available +for download over https. \ No newline at end of file diff --git a/READMEs/README-systemd-nspawn.md b/READMEs/README-systemd-nspawn.md new file mode 100644 index 0000000..1f4b11a --- /dev/null +++ b/READMEs/README-systemd-nspawn.md @@ -0,0 +1,535 @@ +# Setting up and administering systemd-nspawn containers + +Sai's approach is to run `sai-builder` from inside the environment it will do +builds from. It reaches out to the sai-master instance and git repos, and +otherwise there is no ingress into the container or VM from outside. + +So with `systemd-nspawn`, it means the containers should start on the host at +boot and run `sai-builder` inside eg from a systemd service file. You have to: + + - one-time create the containers you want to build in + - set them up with a `sai` user and minimal packageset + - build libwebsockets / sai in there + - configure `sai-builder` to run from systemd when the container starts + +Systemd only runs on Linux, so this document is about how to do all those steps +for Linux hosts and Linux guests. For Windows and OSX, the approach is similar +but using an actual machine or qemu / kvm VM. + +## Create the container on the host + +This will create, furnish and enter a container for Ubuntu Xenial (2016-era +long-term stable). Other distros are basically the same with minor changes + +![surprised](../assets/surprised.png)Restrictions! Systemd with create a veth device based off your machine name +chosen here, it's limited to 11 chars. So two machines here must not have the +same first 11 chars in their name! + +![surprised](../assets/surprised.png)Restrictions! Systemd won't let your machine name have an underscore, so no +`...x86_64`. It's OK with `-`. + +Pick your machine name, in this example `xenial-amd64`. + +### Step 1: Create the initial host config for the machine + +On the host, `mkdir /etc/systemd/nspawn` and then create in +`/etc/systemd/nspawn/xenial-amd64.nspawn` + +``` +[Exec] +Boot=no +[Network] +VirtualEthernet=no +``` + +We will come back and set both of those to `yes` after we have set the container +up manually from the inside. + +### Step 2: Create the machine + +``` +Host # cd /var/lib/machines +Host # debootstrap --arch=amd64 --variant=buildd xenial xenial-amd64 http://archive.ubuntu.com/ubuntu/ +Host # systemd-nspawn -M xenial-amd64 +Spawning container xenial-amd64 on /var/lib/machines/xenial-amd64. +Press ^] three times within 1s to kill container. +root@myhost:~# +``` + +ah that's pretty nice so far, right! We have a shell up inside the container +already. + +#### Variations: Fedora Container + +Replace the debootstrap part above with + +``` +Host # dnf -y --releasever=32 --installroot=`pwd`/fedora32-x8664 install systemd passwd dnf fedora-release +``` + +#### Variations: Debian Container + +Replace the debootstrap part above with + +``` +Host # debootstrap --arch=amd64 stable buster-amd64 +``` + +#### Variations: Centos 8 + +Create a file "centos8.repo" + +``` +[centos8-base] +name=CentOS-8-Base +baseurl=http://mirror.centos.org/centos-8/8.1.1911/BaseOS/x86_64/os +gpgcheck=1 +``` + +... then... + +``` +$ dnf -c centos8.repo --releasever=8 --disablerepo=* --enablerepo=centos8-base --installroot=/var/lib/machines/centos8 groups install 'Minimal Install' +``` + +## Step 3: Install things into the container + +While we're borrowing the host network, install things we want in the +container to configure and debug it. + +(You might think you can install these at the bootstrap step, but I found this +can make trouble both in the right deps not being brought in and it violating +the ownership of `/usr/lib/.build-id` with real `root` instead of the fake +root of the inside of the container.) + +Create the sai user and home dir + +``` +root@myhost:~# useradd -u883 -gnogroup sai -d/home/sai -m -r +``` + +For redhat type systems, use `-gnobody` instead + +### Specific to Ubuntu + +We have to enable the "universe" packageset to get normal things. + +``` +Container # apt update && apt install vim && apt edit-sources && apt update +``` + +add " universe" to the end of the line in the vim session it pops up and save it. + +### Specific to Xenial + +``` +Container # apt install sudo make git gcc cmake net-tools libssl-dev vim libc6-i386 dbus systemd-container libmbedtls-dev glib2.0-dev libevent-dev libev4 libev-dev libuv1 libuv1-dev libmount-dev libsqlite3-dev libgit2-dev inetutils-ping psmisc libsqlite-dev sqlite3 libsqlite3-0 libgit2-24 libgit2-dev libmbedcrypto0 libmbedtls10 libmbedx509-0 libmbedtls-dev libev4 libev-dev libevent-2.0 libevent-dev glib-2.0 pkg-config +``` + +### Specific to Bionic + +``` +apt install sudo make git gcc cmake net-tools libssl-dev vim libc6-i386 dbus systemd-container libmbedtls-dev libev4 libev-dev libuv1 libuv1-dev libmount-dev libsqlite0-dev libgit2-dev inetutils-ping psmisc libgit2-26 libmbedcrypto1 libmbedtls10 libmbedx509-0 libmbedtls-dev libevent-2.1-6 libevent-dev libglib2.0-0 libsqlite3-0 libsqlite3-dev pkg-config libglib2.0-dev libglib2.0-0 libdbus-1-dev libdbus-1-3 +``` + +### Specific to Focal + +``` +apt install sudo make git gcc cmake net-tools libssl-dev vim libc6-i386 dbus systemd-container libmbedtls-dev libevent-2.1-7 libev4 libev-dev libuv1 libuv1-dev libmount-dev libsqlite0-dev libgit2-dev inetutils-ping psmisc libgit2-28 libmbedcrypto3 libmbedtls12 libmbedx509-0 libmbedtls-dev libev4 libev-dev libevent-2.1-7 libevent-dev libglib2.0-0 libsqlite3-0 libsqlite3-dev pkg-config libglib2.0-dev libglib2.0-0 +``` + +### Specific to Fedora + +``` +Container # dnf install sudo make git gcc gcc-c++ cmake net-tools dhclient openssl-devel vim glibc.i686 dbus mbedtls-devel glib2-devel libevent-devel libev-devel libuv-devel libmount-devel sqlite-devel libgit2 libgit2-devel dbus systemd-container vim libpkgconf procps-ng +``` + +### Specific to Centos 8 + +Centos / RHEL 8 simply doesn't provide some -devel packages like mbedtls, libgit2... + +``` +Container # dnf install sudo make git gcc gcc-c++ cmake net-tools dhclient openssl-devel vim glibc.i686 dbus glib2-devel libevent-devel libev-devel sqlite-devel libgit2 dbus systemd-container vim libpkgconf rpm-build +``` + +### Specific to Gentoo + +``` +Container # emerge dev-db/sqlite libgit2 gcc net-libs/mbedtls dev-libs/openssl cmake libuv libevent dev-libs/glib dev-libs/libev dbus pkgconf libcap +``` + +## Step 3: Configure the container private networking + +Set up a hostname for the container by editing its `/etc/hostname`, ideally to +a name like `machinename-hostname`, eg, `xenial-myhost` if your host is `myhost`. + +Configure the container's systemd-networkd to set up the private network for +the container we will switch to next. + +In the container, create `/etc/systemd/network/50-host0.network` with the +contents + +``` +[Match] +Name=host0 +[Network] +DHCP=no +IPForward=1 +Gateway=192.168.80.1 +Address=192.168.80.9/24 +``` + +If you have multiple containers, give each one a different 192.168.xx network. +Enable systemd pieces to control setting the interface, route and dns. + +You can avoid systemd-resolved if you like, `rm /etc/resolv.conf` to remove +its symlink, and then something like `echo "nameserver 1.2.3.4" > /etc/resolv.conf` +Either way at least enable `systemd-networkd` + +``` +Container # ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf +Container # systemctl enable systemd-networkd systemd-resolved +``` + +### Specific to Centos 8 + +Centos 8 has no systemd-networkd. The base install instead has `nmcli`, you +can set up an ipv4.method=static solution that way using the same network +layout as above. + +## Step 4: Switch over to systemd management of the container + +Exit the container session. + +On the host, edit `/etc/systemd/nspawn/xenial-amd64.nspawn` we created earlier +and set both `Boot` and `VirtualEthernet` to `yes` now. + +Tell the host we want this container up when the host is up, and that we want +to start it now. + +``` +Host # systemctl enable systemd-nspawn@xenial-amd64 +Host # systemctl start systemd-nspawn@xenial-amd64 +``` + +Then, we can reopen a shell prompt in the now-running container + +``` +Host # machinectl -M xenial-amd64 shell +Connected to the local host. Press ^] three times within 1s to exit session. +[root@xenial-amd64 ~]# +``` + +You can look around with ps if you have it or systemctl status and see that it +was started inside by systemd. + +## Step 5: Configure the host-side networking + +systemd will have created a matching network interface on your host named +`ve-machinename`, where machinename is your (truncated if neccessary) +machine name. + +We need to explain to Network Manager how to deal with this so the container +will get networking services cleanly. + +We will call the related NetworkManager connection `nm-...` + +``` +Host # nmcli c add type ethernet autoconnect no con-name nm-ve-xenial ifname ve-xenial-amd64 +Host # nmcli c m nm-ve-xenial ipv4.method shared +Host # nmcli c m nm-ve-xenial ipv4.addresses 192.168.80.1/24 +Host # nmcli c m nm-ve-xenial connection.zone vm +Host # nmcli c m nm-ve-xenial connection.autoconnect true +Host # nmcli c down nm-ve-xenial ; nmcli c up nm-ve-xenial +``` + +## Step 6: Restart the container + +``` +Host # systemctl stop systemd-nspawn@xenial-amd64 +Host # systemctl start systemd-nspawn@xenial-amd64 +Host # machinectl -M xenial-amd64 shell +``` + +If you hit selinux AVCs on the start step, as I did on Fedora 32, you can get +around them with three policy additions + +``` +Host # echo 'AVC avc: denied { search } for pid=1502 comm="systemd-machine" name="21786" dev="proc" ino=22140 scontext=system_u:system_r:systemd_machined_t:s0 tcontext=system_u:system_r:unconfined_service_t:s0 tclass=dir permissive=0' | audit2allow -M machine-search ; semodule -i machine-search.pp +Host # echo 'AVC avc: denied { read } for pid=1502 comm="systemd-machine" name="cgroup" dev="proc" ino=60301 scontext=system_u:system_r:systemd_machined_t:s0 tcontext=system_u:system_r:unconfined_service_t:s0 tclass=file permissive=0' | audit2allow -M machine-read ; semodule -i machine-read.pp +Host # echo 'AVC avc: denied { open } for pid=1502 comm="systemd-machine" path="/proc/21937/cgroup" dev="proc" ino=46779 scontext=system_u:system_r:systemd_machined_t:s0 tcontext=system_u:system_r:unconfined_service_t:s0 tclass=file permissive=0' | audit2allow -M machine-open ; semodule -i machine-open.pp +``` + +In the container, you should see `host0` has ben set up + +``` +Container # ifconfig host0 +host0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 + inet 192.168.80.9 netmask 255.255.255.0 broadcast 192.168.80.255 + inet6 fe80::fc27:1eff:fe53:4d93 prefixlen 64 scopeid 0x20<link> + ether fe:27:1e:53:4d:93 txqueuelen 1000 (Ethernet) + RX packets 1117 bytes 102933 (100.5 KiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 754 bytes 71331 (69.6 KiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 +``` + +Whether you can ping things depends on your host routing arrangements for its +side of the virtual ethernet. If you pointed the container to your local +resolver you need to enable that in the "vm" zone, along with masquerading. +You'll only need to do this for the first vm, afterwards it enough to tell nmcli +that the connection is in `vm` zone to inherit the functionality. + +``` +Host # firewall-cmd --permanent --new-zone vm +Host # firewall-cmd --permanent --zone vm --add-masquerade +Host # firewall-cmd --permanent --zone vm --add-port 53/udp +Host # firewall-cmd --permanent --zone vm --add-port 53/tcp +Host # systemctl restart firewalld +``` + +This might seem complicated but nmcli and firewalld (and systemd) have made +all this a lot easier to set up than a few years ago. + +## Get, build and install lws + +We're going to do it as root in the container... I produce and host this code, +you may feel its riskier in which case create a new user with sudo rights to +handle it. + +For redhat type distros, you probably need to add /usr/local/lib to the +/etc/ld.so.conf before ldconfig can rgister the new libwebsockets.so + +``` +Container # git clone https://libwebsockets.org/repo/libwebsockets +Container # cd libwebsockets && mkdir build && cd build && \ + cmake .. -DLWS_UNIX_SOCK=1 -DLWS_WITH_STRUCT_JSON=1 \ + -DLWS_WITH_STRUCT_SQLITE3=1 -DLWS_WITH_GENCRYPTO=1 -DLWS_WITH_SPAWN=1 \ + -DLWS_WITH_SECURE_STREAMS=1 -DLWS_WITH_THREADPOOL=1 +Container # make -j && make -j install && ldconfig +``` + +For OSX, you'll need to add something like `-DLWS_OPENSSL_INCLUDE_DIRS=/usr/local/opt/openssl@1.1/include -DLWS_OPENSSL_LIBRARIES="/usr/local/opt/openssl/lib/libssl.dylib;/usr/local/opt/openssl/lib/libcrypto.dylib"` to the cmake line + +### Windows: build libgit2 + +For Windows, after following the way to build lws in +`READMEs/README.build-windows.md` in the lws tree, you need to build libgit2 + +``` +$ git clone https://github.com/libgit2/libgit2 +$ cd libgit2 && mkdir build && cd build && cmake .. -DBUILD_CLAR=OFF +$ cmake --build . --config DEBUG +``` + +Then, in and Administrator-privs cmd window + +``` +$ cmake --install . --config DEBUG +``` + +### Get, build and install sai + +#### Linux + +``` +Container # cd ~ +Container # git clone https://warmcat.com/repo/sai +Container # cd sai && mkdir build && cd build && cmake .. && make && make install +Container # cp ../scripts/sai-builder.service /etc/systemd/system +Container # mkdir -p /etc/sai/builder +Container # cp ../scripts/builder-conf /etc/sai/builder/conf +Container # vim /etc/sai/builder/conf +Container # systemctl enable sai-builder +``` + +#### Windows + +``` +$ git clone https://warmcat.com/repo/sai +$ cd sai && mkdir build && cd build +$ cmake .. -DSAI_MASTER=0 -DSAI_LWS_INC_PATH="C:/Program Files (x86)/libwebsockets/include" -DSAI_LWS_LIB_PATH="C:/Program Files (x86)/libwebsockets/lib/websockets.lib" -DSAI_EXT_PTHREAD_INCLUDE_DIR="C:\Program Files (x86)\pthreads\include" -DSAI_EXT_PTHREAD_LIBRARIES="C:\Program Files (x86)\pthreads\lib\x64\libpthreadGC2.a" -DLWS_OPENSSL_INCLUDE_DIRS="C:\Program Files\OpenSSL\include" -DLWS_OPENSSL_LIBRARIES="C:\Program Files\OpenSSL\lib\libssl.lib;C:\Program Files\OpenSSL\lib\libcrypto.lib" -DSAI_GIT2_LIB_PATH="C:\Program Files (x86)\libgit2\lib\git2.lib" -DSAI_GIT2_INC_PATH="C:\Program Files (x86)\libgit2\include" +$ cmake --build . --config DEBUG +``` + +Hack the libgit2 and lws dlls into visibility (or do it a better non hacky +way if you know how...) using an Administrator cmd window + +``` +# cp "C:\Program Files (x86)\libgit2\bin\git2.dll" C:\Windows\system32 +# cp "C:\Program Files (x86)\libwebsockets\bin\websockets.dll" C:\Windows\system32 +``` + +Also in the Administrator window, copy and edit the sai-builder config + +``` +# mkdir -p C:\ProgramData\sai\builder\ +# cp ../scripts/builder-conf C:\ProgramData\sai\builder\conf +# vim C:\ProgramData\sai\builder\conf +``` + +#### OSX + +For OSX, you'll need to adapt the cmake to something like + +cmake .. -DLWS_OPENSSL_INCLUDE_DIRS=/usr/local/opt/openssl@1.1/include -DLWS_OPENSSL_LIBRARIES="/usr/local/opt/openssl/lib/libssl.dylib;/usr/local/opt/openssl/lib/libcrypto.dylib" + +Make sai-builder run as a daemon, create the file `/Library/LaunchDaemons/com.warmcat.sai-builder.plist` + +``` +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" + "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>Label</key> + <string>com.warmcat.sai-builder</string> + <key>ServiceDescription</key> + <string>Sai Builder Daemon</string> + <key>ProgramArguments</key> + <array> + <string>/usr/local/bin/sai-builder</string> + </array> + <key>RunAtLoad</key> + <true/> +</dict> +</plist> +``` + +``` +% sudo launchctl load /Library/LaunchDaemons/com.warmcat.sai-builder.plist +``` + +That's it, he should be running in the background. To stop him + +``` +% sudo launchctl stop com.warmcat.sai-builder +``` + +### Make the container start at boot + +Let's leave the container and go back to the host + +``` +Container # exit +Host # +``` + +Then, we can configure it to start on boot + +``` +Host # systemctl enable systemd-nspawn@xenial-amd64 +``` + +## Administration of containers + +The container should be up and running sai-builder, and should come back up +subsequent boots too. You can use machinectl to open a shell on the running +container like this: + +``` +Host # machinectl -M xenial-amd64 shell +Connected to the local host. Press ^] three times within 1s to exit session. +Container # ps fax + PID TTY STAT TIME COMMAND + 1 ? Ss 0:00 /lib/systemd/systemd + 14 ? Ss 0:00 /lib/systemd/systemd-journald + 22 ? Ss 0:00 /lib/systemd/systemd-logind + 23 ? Ss 0:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation + 25 ? Ssl 0:00 /usr/local/bin/sai-builder + 28 pts/0 Ss+ 0:00 /sbin/agetty --noclear --keep-baud console 115200 38400 9600 vt220 + 42 ? Ss 0:00 /lib/systemd/systemd-machined + 43 pts/1 Ss 0:00 -bin/sh + 52 pts/1 S 0:00 \_ bash + 77 pts/1 R+ 0:00 \_ ps fax +Container # +``` + +You can use journalctl etc in there to see what's happening with sai-builder +there, etc. + +## Distro age considerations + +Really old distros are not maintained any more for updates, and their tls +certs have expired so they can't do https... it's pretty hopeless. + +Xenial (16.04) has problems with cmake (not all CTest functionality) and +libgit2 (remote fetch broken on its 0.24 version). You can rapidly build +both of these by cloning, they have CMake-based flow that's easy to use. + +Package|upstream +---|--- +cmake|https://github.com/Kitware/CMake.git +libgit2|https://github.com/libgit2/libgit2.git + +Install into the default /usr/local, and remove the conflicting old distro +packages. + +## Other Linux distros + +Other modern Linux distros with systemd can be done in their own containers and +also made to start at boot... these are very lightweight compared to full VMs +and yet you can administer them using all the usual systemd goodies including +randomly opening shells into their universe if needed. + +# Flashing and testing on embedded devices + +## Exposing host assets in the containers + +For the embedded case you want to flash and test on an external board, you need +to expose typically USB tty devices inside the nspawn container safely. + +That's not so trivial or as useful as it could be and has some limitations. + +### Step 1: Bind the related devices + +Device binding inside the container is like a one-time thing at container +start, wildcards are not supported and if the device doesn't exist, the +container won't start. All this makes supporting ephemeral USB serial ports +that come and go impossible. + +If your USB tty device is always available, then you can elect to expose it to +the container by overriding the systemd service definition for your container, +eg + +``` +Host # mkdir /etc/systemd/system/systemd-nspawn@YOURMACHINE.service.d +Host # vim /etc/systemd/system/systemd-nspawn@YOURMACHINE.service.d/override.conf +``` + +and add in there something like + +``` +[Service] +DeviceAllow=char-ttyUSB rwm +DeviceAllow=char-usb_device rwm +``` + +### Step 2: loosen the permissions on the device at the host + +I wasn't able to find a way to let the container unprivileged user seem to join +the host `dialout` group. The only workaround was to allow general access to +the device node. + +Create a file `vim /etc/udev/rules.d/50-allow-nspawn-ttyUSB.rules` and add in +there + +``` +KERNEL=="ttyUSB[0-9]*",MODE="0666" +KERNEL=="ttyACM[0-9]*",MODE="0666" +``` + +### Step 3: Bind the device nodes into the container + +Edit the container's nspawn file, like `/etc/systemd/nspawn/MYMACHINE.nspawn` +and in the `[Files]` section add + +``` +Bind=/dev/serial +Bind=/dev/ttyUSB0 +``` + +This is very awkward because the device nodes like `/dev/ttyUSB0` must exist at +container start time or it will fail to start, and you can't expose them via the +`/dev/serial/by-id` symlinks either, or use wildcards since it's done as a +one-time init at container start. diff --git a/READMEs/README-using-overlayfs.md b/READMEs/README-using-overlayfs.md new file mode 100644 index 0000000..0c89448 --- /dev/null +++ b/READMEs/README-using-overlayfs.md @@ -0,0 +1,225 @@ +# Overview + +This shows how to create the layered nspawn overlay fs dirs +for use with sai-builder, from scratch with the base distro +files. + +# prerequisites + +Install `systemd-container` package containing `systemd-nspawn` tool. + +# conventions + + - `base` is the completely unchanged base dir + - `env` is an overlayfs with the default packages (like gcc, git etc) + - `session` is an overlayfs with changes from a session + - `work` contains temp files used by eth overlayfs process + - `overlay` is a mountpoint for the combined overlayfs image + + - "Build host": the actual build machine's OS + filesystem + - "overlayfs": the guest filesystem + +# 1) Build host: creating the sai user and sai-nspawn directory + +Normal \*nix + +``` +$ sudo useradd sai +$ sudo mkdir -p /home/sai/sai-nspawn +``` +OSX + +``` +$ sudo dscl . -create /Users/_sai +$ sudo dscl . -create /Users/_sai UniqueID 773 +$ sudo dscl . -create /Users/_sai PrimaryGroupID -2 +$ sudo dscl . -create /Users/_sai UserShell /bin/bash +$ sudo chown _sai:nobody /Users/sai +``` + +# 2) Build host: Creating base layers for various OSes + +** Notice we're doing this on the build host, not in an overlayfs ** + +At least on Fedora, debootstrap is a package available from the default repos, +even though it's alien to Fedora package system itself. + +## Ubuntu + +``` +# cd /home/sai/sai-nspawn +# mkdir linux-ubuntu-xenial-amd64 +# cd linux-ubuntu-xenial-amd64 +# mkdir env work +# debootstrap --arch=amd64 --variant=buildd xenial base http://archive.ubuntu.com/ubuntu/ +``` + +Ubuntu only has amd64 and i386 + +## Debian + +``` +# cd /home/sai/sai-nspawn +# mkdir linux-debian-jessie-amd64 +# cd linux-debian-jessie-amd64 +# mkdir env work +# debootstrap --arch=amd64 stable base +``` + +Debian also has arm64 arch available. + +## fedora + +``` +# cd /home/sai/sai-nspawn +# mkdir linux-fedora-32-x86_64 +# cd linux-fedora-32-x86_64 +# mkdir base env work +# dnf -y --releasever=32 --installroot=`pwd`/base install systemd passwd dnf fedora-release +``` + +If asked to import a key, accept it. + +Fedora also has aarch64 + +## OSX + +Install brew, take care to install `pkg-config` via brew along with gcc etc + +# Overlayfs: Creating the base + env overlay to prepare env for building + +Now we're going to do stuff based on mounting the overlay temporarily +rather than the initial setup, let's move to /tmp (or anywhere else +you prefer like your home dir). + +When env is the active layer, we must use a work dir in the same partition. +(Later, we'll add a throwaway layer /tmp/mysession on top of base+env, then +the work dir can be in /tmp/mywork. But when changing env the work dir +must be in the same partition.) + +``` +$ cd /tmp +$ sudo mkdir -p mywork myoverlay mysession +$ export MYBASE=/home/sai/sai-nspawn/linux-ubuntu-xenial-amd64 +$ sudo mount -t overlay -o lowerdir=$MYBASE/base,upperdir=$MYBASE/env,workdir=$MYBASE/work none /tmp/myoverlay +``` + +At this point, ./myoverlay is the merged filesystem of base + the currently +empty env overlay. Files created in ./myoverlay will not affect base, but be +isolated into ./env. We're going to use env as a layer containing all the +build tools we will need. + +Run a shell with its / in ./myoverlay... + +``` +$ sudo systemd-nspawn -D myoverlay +Spawning container myoverlay on /tmp/myoverlay. +Press ^] three times within 1s to kill container. +root@myoverlay:~# +``` + +## Overlayfs: Create sai user + +Install into env the things needed for the environment as root + +``` +# useradd -u883 -gnogroup sai -d/home/sai -m -r + (or -gnobody for fedora) +``` + +If you need to do things as root, use this to let the sai user use sudo + +``` +# echo "sai ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers +``` + +## Overlayfs: Fedora: install build env + +glibc.i686 is included so you can use prebuilt 32-bit embedded toolchains, eg, linkit + +``` +# dnf install -y sudo make git gcc gcc-c++ cmake net-tools dhclient openssl-devel vim glibc.i686 dbus +``` + +Additional things for eg, building lws can be added like + +``` +# dnf install mbedtls-devel glib2-devel libevent-devel libev-devel libuv-devel libmount-devel sqlite-devel libgit2-devel +``` + + +## Overlayfs: Debian / Ubuntu: install build env + +Ensure vim is available + +``` +# apt update && apt install vim +``` + +Then enable "universe" repo + +``` +# apt edit-sources +``` + +and add `universe` to the end of the line after `main` + +``` +# apt update +``` + +Then install general build prerequisties, libc-i386 is included so you can use +prebuilt 32-bit embedded toolchains, eg, linkit + +``` +# apt install sudo make git gcc cmake net-tools libssl-dev vim libc6-i386 dbus +``` +You probably want other things if you're building something with optional deps, +eg, these for various build options on lws + +``` +# apt install libmbedtls-dev glib2.0-dev libevent-dev libev4 libev-dev libuv1 libuv1-dev libmount-dev libsqlite3-dev libgit2-dev +``` + +### Overlayfs: Finishing with layer modifications + +Type `exit` to exit the overlay shell + +## Build host: Unmount the overlayfs + +Having left the chrooted shell, you can unmount the overlay + +``` +# umount myoverlay +``` + +The changes we make like the package installs were all captured into env + +## Build host: optionally Remount so the overlay is base + env + /tmp/mysession + +``` +# mount -t overlay -o "lowerdir=$MYBASE/env:$MYBASE/base,upperdir=mysession,workdir=mywork" none myoverlay +``` + +Enter a shell again with changes going into the throwaway overlay "session" and this time as `sai` user. +From the changes we made earlier in env layer, sai has nopassword sudo access. + +``` +# systemd-nspawn -u sai -D myoverlay +``` + +From there, you can git clone your CI projects and configure, make and make install them + + +# Uploading nspawn layers to git + +``` +$ cd linux-ubuntu-xenial-amd64+base +$ sudo git init +$ sudo git add * +$ sudo git commit -m "initial commit" +$ sudo git remote add origin ssh://git@example.com/linux-ubuntu-xenial-amd64+base +$ sudo git push origin +master:master +``` + + diff --git a/READMEs/sai-build-test-flow.png b/READMEs/sai-build-test-flow.png new file mode 100644 index 0000000..a3c439b Binary files /dev/null and b/READMEs/sai-build-test-flow.png differ diff --git a/READMEs/sai-embedded-test.png b/READMEs/sai-embedded-test.png new file mode 100644 index 0000000..e617c07 Binary files /dev/null and b/READMEs/sai-embedded-test.png differ diff --git a/READMEs/sai-ov2.png b/READMEs/sai-ov2.png new file mode 100644 index 0000000..a885094 Binary files /dev/null and b/READMEs/sai-ov2.png differ diff --git a/READMEs/sai-overview.png b/READMEs/sai-overview.png new file mode 100644 index 0000000..1577021 Binary files /dev/null and b/READMEs/sai-overview.png differ diff --git a/assets/OSX-catalina.png b/assets/OSX-catalina.png new file mode 100644 index 0000000..ed3d45a Binary files /dev/null and b/assets/OSX-catalina.png differ diff --git a/assets/OpenSans-Light.ttf b/assets/OpenSans-Light.ttf new file mode 100644 index 0000000..6580d3a Binary files /dev/null and b/assets/OpenSans-Light.ttf differ diff --git a/assets/apple.svg b/assets/apple.svg new file mode 100644 index 0000000..aebe91e --- /dev/null +++ b/assets/apple.svg @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" id="svg2951" version="1.1" viewBox="0 0 3.88606 3.88606" height="3.88606mm" width="3.88606mm"> +<defs id="defs2945" /> +<metadata id="metadata2948"> +<rdf:RDF> +<cc:Work rdf:about=""> +<dc:format>image/svg+xml</dc:format> +<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> +<dc:title></dc:title> +</cc:Work> +</rdf:RDF> +</metadata> +<g transform="translate(325.53,40.2578)" id="layer1"> +<path d="m -323.751,-38.9038 c -0.04,-0.189 -0.309,-0.45 -0.453,-0.535 0.1,-0.1 0.196,0.04 0.311,0.178 0.09,0.127 0.198,0.392 0.196,0.342 -0.146,-0.525 -0.02,-0.948 0.177,-1.046 0.228,0.02 0.167,0.81 0.03,0.88 -0.114,0.175 -0.236,0.362 -0.09,0.18 0.874,-0.627 1.373,0.333 1.27,0.848 -0.116,0.618 -0.514,1.097 -1.064,0.839 -0.147,-0.04 -0.138,-0.14 -0.263,0.02 -0.334,0.341 -0.863,0.1 -1.031,-0.209 -0.416,-0.531 -0.14,-1.674 0.704,-1.519 z" style="fill:#000000;stroke:#000000;stroke-width:0.012917px" id="path2784" /> +</g> +</svg> diff --git a/assets/arch-aarch64-a72-bcm2711-rpi4.svg b/assets/arch-aarch64-a72-bcm2711-rpi4.svg new file mode 100644 index 0000000..3193351 --- /dev/null +++ b/assets/arch-aarch64-a72-bcm2711-rpi4.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle cx="1.908" cy="1.908" r="1.908" opacity=".12" fill="#f9f9f9"/><path d="M1.94 3.38c.004-.005.022-.01.04-.01.053 0 .193-.03.283-.058a1.42 1.42 0 0 0 .894-.868 1.41 1.41 0 0 0-.067-1.099 1.48 1.48 0 0 0-.222-.322l-.07-.085c.007-.007.343-.142.345-.14s.024.03.05.06a1.6 1.6 0 0 1 .326.701c.026.13.026.43 0 .56-.096.48-.39.877-.812 1.092a1.48 1.48 0 0 1-.67.173c-.06.003-.1.001-.095-.005zm-.037-.024c0-.002.036-.012.08-.022.22-.05.427-.16.6-.326.39-.375.505-.94.29-1.44a1.3 1.3 0 0 0-.323-.442c-.028-.024-.052-.047-.052-.05a2.45 2.45 0 0 1 .277-.114c.018 0 .146.146.204.233.165.248.238.49.236.783 0 .17-.022.3-.076.453-.163.462-.577.817-1.047.9-.105.018-.187.03-.187.023zm-.036-.043a1.42 1.42 0 0 0 .425-.217 1.16 1.16 0 0 0 .11-1.711c-.043-.045-.11-.105-.15-.134l-.07-.053.138-.055a.67.67 0 0 1 .156-.05c.035.01.232.23.287.32.307.496.243 1.122-.156 1.538-.185.193-.405.315-.66.367-.15.03-.2.027-.08-.005zM.98 3.023a.33.33 0 0 1-.183-.103C.72 2.836.69 2.752.69 2.612c0-.266.143-.528.39-.718l.07-.05a.75.75 0 0 1 .098.128c-.142.1-.25.214-.305.322-.022.043-.038.08-.035.083s.026-.002.05-.01a.45.45 0 0 1 .12-.017c.223-.002.374.214.307.438-.053.172-.232.276-.406.237zm.183-.19c.046-.035.065-.08.065-.153 0-.133-.134-.207-.282-.157-.075.026-.09.047-.09.12 0 .102.04.17.12.21.058.028.14.02.19-.018zm.802-.004v-.187l-.482-.006L1.48 2.6c-.004-.036.012-.056.29-.397.292-.358.293-.358.332-.358h.04v.643h.125l-.008.15-.12.006v.372h-.176zm0-.504v-.16l-.03.034-.116.143-.102.127c-.015.017-.008.018.116.018h.13zM.483 1.744c0-.02.053-.16.086-.226.086-.172.27-.38.428-.484.22-.144.44-.21.7-.21a1.22 1.22 0 0 1 .679.196c.03.02.057.04.057.045a.77.77 0 0 1-.135.063l-.135.055-.09-.042c-.396-.184-.83-.146-1.184.103-.15.105-.293.277-.372.445-.018.037-.032.062-.032.055zm.332-.001A7.05 7.05 0 0 0 .8 1.595C.78 1.42.78 1.4.8 1.392a1.59 1.59 0 0 1 .286.214c0 .023-.04.018-.078-.01l-.043-.03-.058.03-.057.03.005.052c.004.038 0 .055-.01.06-.018.01-.03.012-.03.002zm.104-.21c0-.007-.07-.063-.077-.063s-.007.05 0 .08c.006.022.007.022.042.006.02-.01.035-.02.035-.023zm1.236.146c-.01-.007-.007-.022.018-.07l.032-.063-.08-.037-.08-.037-.03.063c-.02.046-.033.06-.044.057s-.018-.006-.02-.006c-.01 0 0-.03.062-.16l.075-.14c.005 0 .015.004.023.01s.01.02-.013.07l-.027.063a.94.94 0 0 0 .15.075c0-.002.016-.03.03-.06.035-.07.03-.066.055-.053.02.01.018.017-.048.156a.78.78 0 0 1-.08.145c-.005 0-.016-.004-.024-.01zm-1.05-.11c0-.018.01-.098.018-.178.015-.12.02-.146.035-.148s.054.04.127.13l.11.14c0 .004-.008.01-.018.012s-.03-.01-.054-.036l-.036-.04-.062.02c-.036.01-.063.025-.064.034-.01.074-.016.09-.035.096s-.02 0-.02-.027zm.143-.16c0-.002-.012-.023-.03-.047l-.035-.044-.006.03c-.004.017-.007.043-.008.058 0 .027 0 .027.038.016l.043-.014zm.525.122c-.054-.025-.094-.073-.103-.124-.014-.08.055-.18.135-.195.066-.012.18.037.18.078 0 .03-.012.032-.048.004-.044-.034-.113-.043-.156-.02-.067.034-.082.136-.03.19a.18.18 0 0 0 .139.04c.033-.007.038 0 .026.032-.01.023-.09.02-.145-.003zm-.347-.182V1.19h.067c.074 0 .102.017.115.067.008.03-.012.084-.035.093-.014.005-.012.01.01.034s.082.11.082.12c0 .003-.012.005-.026.005-.02 0-.033-.01-.053-.044-.04-.063-.067-.09-.09-.09-.02 0-.022.006-.022.065s0 .065-.023.068l-.024.003zm.122-.04c.007-.007.013-.022.013-.034 0-.03-.02-.047-.054-.047-.025 0-.03.004-.03.03 0 .017-.004.038-.006.047s0 .016.03.016c.02 0 .04-.006.048-.012z" fill="#42aa3b"/><path d="M1.983 3.337l.11-.032c.377-.12.696-.433.82-.804.162-.486.04-.988-.327-1.346l-.085-.083.28-.114.067.073a1.43 1.43 0 0 1 .35.704 1.78 1.78 0 0 1 0 .475c-.09.48-.414.88-.85 1.048a1.48 1.48 0 0 1-.338.086c-.06.005-.063.005-.025-.005zm-.134-.02c.563-.148.926-.66.876-1.236C2.7 1.8 2.578 1.55 2.37 1.35a1.37 1.37 0 0 0-.131-.113c-.024-.016-.043-.032-.043-.036a.78.78 0 0 1 .134-.062l.134-.054.03.022a1.62 1.62 0 0 1 .273.313c.207.334.245.757.1 1.126-.155.397-.52.696-.945.777-.12.023-.17.02-.076-.004zM.94 3.01C.835 2.972.764 2.9.72 2.79.69 2.713.688 2.528.715 2.425c.054-.2.196-.4.38-.537l.056-.042.045.055c.025.03.045.06.045.065s-.018.02-.04.036c-.064.042-.144.12-.196.188-.047.062-.115.19-.106.2.004.002.03-.005.058-.016a.39.39 0 0 1 .233-.004c.063.022.137.088.17.154.04.076.045.205.013.29-.027.073-.098.15-.17.184a.42.42 0 0 1-.265.015zm.21-.162c.06-.033.086-.082.086-.16s-.028-.125-.09-.16c-.07-.04-.183-.03-.258.023-.05.035-.047.165.007.238.06.08.17.106.255.06zm.826-.02l-.004-.19-.494-.006.004-.037c.004-.03.05-.094.294-.394.272-.334.293-.357.324-.357h.034v.643h.125v.145h-.125v.384h-.155zm.004-.506l-.005-.166c-.005 0-.244.295-.258.32-.006.01.022.013.128.013h.135zM.485 1.74c0-.034.093-.24.145-.327a1.52 1.52 0 0 1 .377-.384 1.26 1.26 0 0 1 1.342-.025.45.45 0 0 1 .085.06c0 .004-.06.033-.135.063l-.135.055-.09-.042c-.234-.108-.456-.14-.696-.1-.366.06-.69.3-.853.632-.022.045-.04.076-.04.067zm.318-.162c-.017-.163-.016-.19.004-.19.004 0 .07.05.145.108.117.092.134.11.12.118s-.027.003-.06-.022L.97 1.56l-.116.063v.054c0 .038-.005.057-.016.063S.82 1.728.805 1.578zm.09-.02c.02-.01.036-.02.036-.023a.4.4 0 0 0-.098-.072.27.27 0 0 0 .016.112c.005 0 .025-.008.045-.017zm1.272.122c-.013-.008-.01-.02.013-.072l.027-.063-.072-.036c-.04-.02-.077-.036-.083-.036s-.022.026-.036.058c-.02.044-.03.057-.044.054s-.017-.01-.015-.02c.005-.024.13-.28.136-.28s.013.004.02.01.007.022-.018.073l-.033.064.078.036c.043.02.082.034.087.03a.28.28 0 0 0 .033-.057c.023-.05.044-.065.056-.043a2.2 2.2 0 0 1-.131.292zM1.11 1.562c.004-.02.01-.088.018-.154.01-.118.023-.166.04-.166.005 0 .035.032.066.07l.112.135c.045.053.05.066.037.07s-.03-.006-.057-.033l-.04-.04-.062.02-.06.02-.01.053c-.007.04-.014.054-.03.057s-.02-.001-.014-.03zm.134-.143c.015-.005.012-.013-.02-.055-.043-.056-.054-.05-.054.026 0 .05 0 .05.028.043l.046-.013zm.56.122c-.108-.032-.157-.14-.106-.237s.18-.112.275-.04a.03.03 0 0 1 .012.038l-.007.023-.03-.026a.23.23 0 0 0-.07-.038c-.11-.033-.205.108-.13.196.032.038.087.058.14.05.037-.005.04-.004.032.017-.01.026-.057.032-.114.016zm-.374-.19v-.16h.06c.083 0 .116.025.116.087 0 .03-.006.05-.02.062-.02.018-.02.02.014.066l.052.077c.018.028.018.03-.007.03-.02 0-.034-.013-.064-.062-.038-.062-.062-.083-.094-.083-.013 0-.016.014-.016.073 0 .066-.004.073-.02.073s-.02-.007-.02-.16zm.113-.032c.035-.024.028-.077-.012-.09-.045-.015-.06-.004-.06.047 0 .025.004.048.007.052.012.012.04.008.064-.01z" fill="#143b8e"/><path d="M.94 3.01C.835 2.972.764 2.9.72 2.79.69 2.713.688 2.528.715 2.425c.054-.2.196-.4.38-.537l.056-.042.045.055c.025.03.045.06.045.065s-.018.02-.04.036c-.064.042-.144.12-.196.188-.047.062-.115.19-.106.2.004.002.03-.005.058-.016a.39.39 0 0 1 .233-.004c.063.022.137.088.17.154.04.076.045.205.013.29-.027.073-.098.15-.17.184a.42.42 0 0 1-.265.015zm.21-.162c.06-.033.086-.082.086-.16s-.028-.125-.09-.16c-.07-.04-.183-.03-.258.023-.05.035-.047.165.007.238.06.08.17.106.255.06zm.826-.02l-.004-.19-.494-.006.004-.037c.004-.03.05-.094.294-.394.272-.334.293-.357.324-.357h.034v.643h.125v.145h-.125v.384h-.155zm.004-.506l-.005-.166c-.005 0-.244.295-.258.32-.006.01.022.013.128.013h.135zM.803 1.578c-.017-.163-.016-.19.004-.19.004 0 .07.05.145.108.117.092.134.11.12.118s-.027.003-.06-.022L.97 1.56l-.116.063v.054c0 .038-.005.057-.016.063S.82 1.728.805 1.578zm.09-.02c.02-.01.036-.02.036-.023a.4.4 0 0 0-.098-.072.27.27 0 0 0 .016.112c.005 0 .025-.008.045-.017zm1.272.122c-.013-.008-.01-.02.013-.072l.027-.063-.072-.036c-.04-.02-.077-.036-.083-.036s-.022.026-.036.058c-.02.044-.03.057-.044.054s-.017-.01-.015-.02c.005-.024.13-.28.136-.28s.013.004.02.01.007.022-.018.073l-.033.064.078.036c.043.02.082.034.087.03a.28.28 0 0 0 .033-.057c.023-.05.044-.065.056-.043a2.2 2.2 0 0 1-.131.292zM1.11 1.562c.004-.02.01-.088.018-.154.01-.118.023-.166.04-.166.005 0 .035.032.066.07l.112.135c.045.053.05.066.037.07s-.03-.006-.057-.033l-.04-.04-.062.02-.06.02-.01.053c-.007.04-.014.054-.03.057s-.02-.001-.014-.03zm.134-.143c.015-.005.012-.013-.02-.055-.043-.056-.054-.05-.054.026 0 .05 0 .05.028.043l.046-.013zm.56.122c-.108-.032-.157-.14-.106-.237s.18-.112.275-.04a.03.03 0 0 1 .012.038l-.007.023-.03-.026a.23.23 0 0 0-.07-.038c-.11-.033-.205.108-.13.196.032.038.087.058.14.05.037-.005.04-.004.032.017-.01.026-.057.032-.114.016zm-.374-.19v-.16h.06c.083 0 .116.025.116.087 0 .03-.006.05-.02.062-.02.018-.02.02.014.066l.052.077c.018.028.018.03-.007.03-.02 0-.034-.013-.064-.062-.038-.062-.062-.083-.094-.083-.013 0-.016.014-.016.073 0 .066-.004.073-.02.073s-.02-.007-.02-.16zm.113-.032c.035-.024.028-.077-.012-.09-.045-.015-.06-.004-.06.047 0 .025.004.048.007.052.012.012.04.008.064-.01z" fill="#242021"/></svg> diff --git a/assets/arch-aarch64.svg b/assets/arch-aarch64.svg new file mode 100644 index 0000000..3193351 --- /dev/null +++ b/assets/arch-aarch64.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle cx="1.908" cy="1.908" r="1.908" opacity=".12" fill="#f9f9f9"/><path d="M1.94 3.38c.004-.005.022-.01.04-.01.053 0 .193-.03.283-.058a1.42 1.42 0 0 0 .894-.868 1.41 1.41 0 0 0-.067-1.099 1.48 1.48 0 0 0-.222-.322l-.07-.085c.007-.007.343-.142.345-.14s.024.03.05.06a1.6 1.6 0 0 1 .326.701c.026.13.026.43 0 .56-.096.48-.39.877-.812 1.092a1.48 1.48 0 0 1-.67.173c-.06.003-.1.001-.095-.005zm-.037-.024c0-.002.036-.012.08-.022.22-.05.427-.16.6-.326.39-.375.505-.94.29-1.44a1.3 1.3 0 0 0-.323-.442c-.028-.024-.052-.047-.052-.05a2.45 2.45 0 0 1 .277-.114c.018 0 .146.146.204.233.165.248.238.49.236.783 0 .17-.022.3-.076.453-.163.462-.577.817-1.047.9-.105.018-.187.03-.187.023zm-.036-.043a1.42 1.42 0 0 0 .425-.217 1.16 1.16 0 0 0 .11-1.711c-.043-.045-.11-.105-.15-.134l-.07-.053.138-.055a.67.67 0 0 1 .156-.05c.035.01.232.23.287.32.307.496.243 1.122-.156 1.538-.185.193-.405.315-.66.367-.15.03-.2.027-.08-.005zM.98 3.023a.33.33 0 0 1-.183-.103C.72 2.836.69 2.752.69 2.612c0-.266.143-.528.39-.718l.07-.05a.75.75 0 0 1 .098.128c-.142.1-.25.214-.305.322-.022.043-.038.08-.035.083s.026-.002.05-.01a.45.45 0 0 1 .12-.017c.223-.002.374.214.307.438-.053.172-.232.276-.406.237zm.183-.19c.046-.035.065-.08.065-.153 0-.133-.134-.207-.282-.157-.075.026-.09.047-.09.12 0 .102.04.17.12.21.058.028.14.02.19-.018zm.802-.004v-.187l-.482-.006L1.48 2.6c-.004-.036.012-.056.29-.397.292-.358.293-.358.332-.358h.04v.643h.125l-.008.15-.12.006v.372h-.176zm0-.504v-.16l-.03.034-.116.143-.102.127c-.015.017-.008.018.116.018h.13zM.483 1.744c0-.02.053-.16.086-.226.086-.172.27-.38.428-.484.22-.144.44-.21.7-.21a1.22 1.22 0 0 1 .679.196c.03.02.057.04.057.045a.77.77 0 0 1-.135.063l-.135.055-.09-.042c-.396-.184-.83-.146-1.184.103-.15.105-.293.277-.372.445-.018.037-.032.062-.032.055zm.332-.001A7.05 7.05 0 0 0 .8 1.595C.78 1.42.78 1.4.8 1.392a1.59 1.59 0 0 1 .286.214c0 .023-.04.018-.078-.01l-.043-.03-.058.03-.057.03.005.052c.004.038 0 .055-.01.06-.018.01-.03.012-.03.002zm.104-.21c0-.007-.07-.063-.077-.063s-.007.05 0 .08c.006.022.007.022.042.006.02-.01.035-.02.035-.023zm1.236.146c-.01-.007-.007-.022.018-.07l.032-.063-.08-.037-.08-.037-.03.063c-.02.046-.033.06-.044.057s-.018-.006-.02-.006c-.01 0 0-.03.062-.16l.075-.14c.005 0 .015.004.023.01s.01.02-.013.07l-.027.063a.94.94 0 0 0 .15.075c0-.002.016-.03.03-.06.035-.07.03-.066.055-.053.02.01.018.017-.048.156a.78.78 0 0 1-.08.145c-.005 0-.016-.004-.024-.01zm-1.05-.11c0-.018.01-.098.018-.178.015-.12.02-.146.035-.148s.054.04.127.13l.11.14c0 .004-.008.01-.018.012s-.03-.01-.054-.036l-.036-.04-.062.02c-.036.01-.063.025-.064.034-.01.074-.016.09-.035.096s-.02 0-.02-.027zm.143-.16c0-.002-.012-.023-.03-.047l-.035-.044-.006.03c-.004.017-.007.043-.008.058 0 .027 0 .027.038.016l.043-.014zm.525.122c-.054-.025-.094-.073-.103-.124-.014-.08.055-.18.135-.195.066-.012.18.037.18.078 0 .03-.012.032-.048.004-.044-.034-.113-.043-.156-.02-.067.034-.082.136-.03.19a.18.18 0 0 0 .139.04c.033-.007.038 0 .026.032-.01.023-.09.02-.145-.003zm-.347-.182V1.19h.067c.074 0 .102.017.115.067.008.03-.012.084-.035.093-.014.005-.012.01.01.034s.082.11.082.12c0 .003-.012.005-.026.005-.02 0-.033-.01-.053-.044-.04-.063-.067-.09-.09-.09-.02 0-.022.006-.022.065s0 .065-.023.068l-.024.003zm.122-.04c.007-.007.013-.022.013-.034 0-.03-.02-.047-.054-.047-.025 0-.03.004-.03.03 0 .017-.004.038-.006.047s0 .016.03.016c.02 0 .04-.006.048-.012z" fill="#42aa3b"/><path d="M1.983 3.337l.11-.032c.377-.12.696-.433.82-.804.162-.486.04-.988-.327-1.346l-.085-.083.28-.114.067.073a1.43 1.43 0 0 1 .35.704 1.78 1.78 0 0 1 0 .475c-.09.48-.414.88-.85 1.048a1.48 1.48 0 0 1-.338.086c-.06.005-.063.005-.025-.005zm-.134-.02c.563-.148.926-.66.876-1.236C2.7 1.8 2.578 1.55 2.37 1.35a1.37 1.37 0 0 0-.131-.113c-.024-.016-.043-.032-.043-.036a.78.78 0 0 1 .134-.062l.134-.054.03.022a1.62 1.62 0 0 1 .273.313c.207.334.245.757.1 1.126-.155.397-.52.696-.945.777-.12.023-.17.02-.076-.004zM.94 3.01C.835 2.972.764 2.9.72 2.79.69 2.713.688 2.528.715 2.425c.054-.2.196-.4.38-.537l.056-.042.045.055c.025.03.045.06.045.065s-.018.02-.04.036c-.064.042-.144.12-.196.188-.047.062-.115.19-.106.2.004.002.03-.005.058-.016a.39.39 0 0 1 .233-.004c.063.022.137.088.17.154.04.076.045.205.013.29-.027.073-.098.15-.17.184a.42.42 0 0 1-.265.015zm.21-.162c.06-.033.086-.082.086-.16s-.028-.125-.09-.16c-.07-.04-.183-.03-.258.023-.05.035-.047.165.007.238.06.08.17.106.255.06zm.826-.02l-.004-.19-.494-.006.004-.037c.004-.03.05-.094.294-.394.272-.334.293-.357.324-.357h.034v.643h.125v.145h-.125v.384h-.155zm.004-.506l-.005-.166c-.005 0-.244.295-.258.32-.006.01.022.013.128.013h.135zM.485 1.74c0-.034.093-.24.145-.327a1.52 1.52 0 0 1 .377-.384 1.26 1.26 0 0 1 1.342-.025.45.45 0 0 1 .085.06c0 .004-.06.033-.135.063l-.135.055-.09-.042c-.234-.108-.456-.14-.696-.1-.366.06-.69.3-.853.632-.022.045-.04.076-.04.067zm.318-.162c-.017-.163-.016-.19.004-.19.004 0 .07.05.145.108.117.092.134.11.12.118s-.027.003-.06-.022L.97 1.56l-.116.063v.054c0 .038-.005.057-.016.063S.82 1.728.805 1.578zm.09-.02c.02-.01.036-.02.036-.023a.4.4 0 0 0-.098-.072.27.27 0 0 0 .016.112c.005 0 .025-.008.045-.017zm1.272.122c-.013-.008-.01-.02.013-.072l.027-.063-.072-.036c-.04-.02-.077-.036-.083-.036s-.022.026-.036.058c-.02.044-.03.057-.044.054s-.017-.01-.015-.02c.005-.024.13-.28.136-.28s.013.004.02.01.007.022-.018.073l-.033.064.078.036c.043.02.082.034.087.03a.28.28 0 0 0 .033-.057c.023-.05.044-.065.056-.043a2.2 2.2 0 0 1-.131.292zM1.11 1.562c.004-.02.01-.088.018-.154.01-.118.023-.166.04-.166.005 0 .035.032.066.07l.112.135c.045.053.05.066.037.07s-.03-.006-.057-.033l-.04-.04-.062.02-.06.02-.01.053c-.007.04-.014.054-.03.057s-.02-.001-.014-.03zm.134-.143c.015-.005.012-.013-.02-.055-.043-.056-.054-.05-.054.026 0 .05 0 .05.028.043l.046-.013zm.56.122c-.108-.032-.157-.14-.106-.237s.18-.112.275-.04a.03.03 0 0 1 .012.038l-.007.023-.03-.026a.23.23 0 0 0-.07-.038c-.11-.033-.205.108-.13.196.032.038.087.058.14.05.037-.005.04-.004.032.017-.01.026-.057.032-.114.016zm-.374-.19v-.16h.06c.083 0 .116.025.116.087 0 .03-.006.05-.02.062-.02.018-.02.02.014.066l.052.077c.018.028.018.03-.007.03-.02 0-.034-.013-.064-.062-.038-.062-.062-.083-.094-.083-.013 0-.016.014-.016.073 0 .066-.004.073-.02.073s-.02-.007-.02-.16zm.113-.032c.035-.024.028-.077-.012-.09-.045-.015-.06-.004-.06.047 0 .025.004.048.007.052.012.012.04.008.064-.01z" fill="#143b8e"/><path d="M.94 3.01C.835 2.972.764 2.9.72 2.79.69 2.713.688 2.528.715 2.425c.054-.2.196-.4.38-.537l.056-.042.045.055c.025.03.045.06.045.065s-.018.02-.04.036c-.064.042-.144.12-.196.188-.047.062-.115.19-.106.2.004.002.03-.005.058-.016a.39.39 0 0 1 .233-.004c.063.022.137.088.17.154.04.076.045.205.013.29-.027.073-.098.15-.17.184a.42.42 0 0 1-.265.015zm.21-.162c.06-.033.086-.082.086-.16s-.028-.125-.09-.16c-.07-.04-.183-.03-.258.023-.05.035-.047.165.007.238.06.08.17.106.255.06zm.826-.02l-.004-.19-.494-.006.004-.037c.004-.03.05-.094.294-.394.272-.334.293-.357.324-.357h.034v.643h.125v.145h-.125v.384h-.155zm.004-.506l-.005-.166c-.005 0-.244.295-.258.32-.006.01.022.013.128.013h.135zM.803 1.578c-.017-.163-.016-.19.004-.19.004 0 .07.05.145.108.117.092.134.11.12.118s-.027.003-.06-.022L.97 1.56l-.116.063v.054c0 .038-.005.057-.016.063S.82 1.728.805 1.578zm.09-.02c.02-.01.036-.02.036-.023a.4.4 0 0 0-.098-.072.27.27 0 0 0 .016.112c.005 0 .025-.008.045-.017zm1.272.122c-.013-.008-.01-.02.013-.072l.027-.063-.072-.036c-.04-.02-.077-.036-.083-.036s-.022.026-.036.058c-.02.044-.03.057-.044.054s-.017-.01-.015-.02c.005-.024.13-.28.136-.28s.013.004.02.01.007.022-.018.073l-.033.064.078.036c.043.02.082.034.087.03a.28.28 0 0 0 .033-.057c.023-.05.044-.065.056-.043a2.2 2.2 0 0 1-.131.292zM1.11 1.562c.004-.02.01-.088.018-.154.01-.118.023-.166.04-.166.005 0 .035.032.066.07l.112.135c.045.053.05.066.037.07s-.03-.006-.057-.033l-.04-.04-.062.02-.06.02-.01.053c-.007.04-.014.054-.03.057s-.02-.001-.014-.03zm.134-.143c.015-.005.012-.013-.02-.055-.043-.056-.054-.05-.054.026 0 .05 0 .05.028.043l.046-.013zm.56.122c-.108-.032-.157-.14-.106-.237s.18-.112.275-.04a.03.03 0 0 1 .012.038l-.007.023-.03-.026a.23.23 0 0 0-.07-.038c-.11-.033-.205.108-.13.196.032.038.087.058.14.05.037-.005.04-.004.032.017-.01.026-.057.032-.114.016zm-.374-.19v-.16h.06c.083 0 .116.025.116.087 0 .03-.006.05-.02.062-.02.018-.02.02.014.066l.052.077c.018.028.018.03-.007.03-.02 0-.034-.013-.064-.062-.038-.062-.062-.083-.094-.083-.013 0-.016.014-.016.073 0 .066-.004.073-.02.073s-.02-.007-.02-.16zm.113-.032c.035-.024.028-.077-.012-.09-.045-.015-.06-.004-.06.047 0 .025.004.048.007.052.012.012.04.008.064-.01z" fill="#242021"/></svg> diff --git a/assets/arch-arm32-m4-mt7697-usi.svg b/assets/arch-arm32-m4-mt7697-usi.svg new file mode 100644 index 0000000..8575247 --- /dev/null +++ b/assets/arch-arm32-m4-mt7697-usi.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle cx="1.908" cy="1.908" r="1.908" opacity=".42" fill="#ccc"/><g fill="#088dad"><path d="M.623 1.882l.296-.766.215.002.162.38.164.386c.002.006-.004.006-.097.005l-.1-.001-.06-.15H.87l-.055.15H.62zm.524-.286l-.06-.15-.06-.138c-.004.007-.105.287-.105.29s.047.003.114.003c.107 0 .113 0 .11-.006zm.36-.092v-.387l.192.001.22.01c.04.012.068.028.097.056.083.08.09.215.015.292a.37.37 0 0 1-.07.052c.012.01.043.04.056.06a.98.98 0 0 1 .071.142l.04.093.023.052.007.016h-.203l-.018-.04-.04-.09c-.046-.11-.076-.155-.112-.173-.017-.008-.023-.01-.057-.01h-.038v.314h-.186zm.323-.074c.04-.012.06-.036.063-.075.003-.03-.004-.05-.022-.068s-.04-.027-.116-.027h-.06v.175h.06c.04 0 .065-.002.077-.005zm.382.075v-.387h.162l.118.142.12.142a5.16 5.16 0 0 0 .121-.142l.118-.142h.162v.774h-.186l-.002-.482-.2.242H2.61c-.015-.001-.016-.002-.114-.12l-.1-.12-.002.482H2.21z"/><path d="M.624 1.888c0-.002.067-.175.15-.386l.15-.383.212-.002.327.767c.003.006-.002.007-.095.007h-.098l-.06-.153H.87l-.055.15-.096.001c-.053.001-.095 0-.095-.002zm.468-.437l-.063-.15c-.001.002-.026.067-.054.145l-.054.15c-.002.006.005.006.114.006h.117zm.42.053v-.387h.16l.197.004c.103.01.178.07.206.162.01.032.01.084-.001.117a.2.2 0 0 1-.095.114c-.012.007-.023.014-.023.015.036.024.06.056.083.098a6.77 6.77 0 0 1 .118.261 1.3 1.3 0 0 1-.098.002H1.96l-.01-.017a2.48 2.48 0 0 1-.039-.088c-.078-.184-.103-.212-.19-.212h-.028v.317H1.51zm.338-.08c.032-.017.046-.042.046-.08 0-.05-.023-.076-.075-.085a.57.57 0 0 0-.074-.004h-.052v.18l.07-.001c.064-.001.072-.002.085-.01zm.367.08v-.387h.156l.12.144.122.143a7.06 7.06 0 0 0 .122-.144l.12-.143h.156v.774h-.182l-.002-.485-.1.122c-.097.117-.1.122-.113.122s-.016-.004-.113-.122l-.1-.122-.002.484h-.182z"/></g><text y="-121.044" x="-677.022" xml:space="preserve" font-size="1.065" font-family="A-OTF Shin Go Pro" letter-spacing="0" word-spacing="0"><tspan y="3.02" x="1.108">32</tspan></text></svg> \ No newline at end of file diff --git a/assets/arch-arm32.svg b/assets/arch-arm32.svg new file mode 100644 index 0000000..8575247 --- /dev/null +++ b/assets/arch-arm32.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle cx="1.908" cy="1.908" r="1.908" opacity=".42" fill="#ccc"/><g fill="#088dad"><path d="M.623 1.882l.296-.766.215.002.162.38.164.386c.002.006-.004.006-.097.005l-.1-.001-.06-.15H.87l-.055.15H.62zm.524-.286l-.06-.15-.06-.138c-.004.007-.105.287-.105.29s.047.003.114.003c.107 0 .113 0 .11-.006zm.36-.092v-.387l.192.001.22.01c.04.012.068.028.097.056.083.08.09.215.015.292a.37.37 0 0 1-.07.052c.012.01.043.04.056.06a.98.98 0 0 1 .071.142l.04.093.023.052.007.016h-.203l-.018-.04-.04-.09c-.046-.11-.076-.155-.112-.173-.017-.008-.023-.01-.057-.01h-.038v.314h-.186zm.323-.074c.04-.012.06-.036.063-.075.003-.03-.004-.05-.022-.068s-.04-.027-.116-.027h-.06v.175h.06c.04 0 .065-.002.077-.005zm.382.075v-.387h.162l.118.142.12.142a5.16 5.16 0 0 0 .121-.142l.118-.142h.162v.774h-.186l-.002-.482-.2.242H2.61c-.015-.001-.016-.002-.114-.12l-.1-.12-.002.482H2.21z"/><path d="M.624 1.888c0-.002.067-.175.15-.386l.15-.383.212-.002.327.767c.003.006-.002.007-.095.007h-.098l-.06-.153H.87l-.055.15-.096.001c-.053.001-.095 0-.095-.002zm.468-.437l-.063-.15c-.001.002-.026.067-.054.145l-.054.15c-.002.006.005.006.114.006h.117zm.42.053v-.387h.16l.197.004c.103.01.178.07.206.162.01.032.01.084-.001.117a.2.2 0 0 1-.095.114c-.012.007-.023.014-.023.015.036.024.06.056.083.098a6.77 6.77 0 0 1 .118.261 1.3 1.3 0 0 1-.098.002H1.96l-.01-.017a2.48 2.48 0 0 1-.039-.088c-.078-.184-.103-.212-.19-.212h-.028v.317H1.51zm.338-.08c.032-.017.046-.042.046-.08 0-.05-.023-.076-.075-.085a.57.57 0 0 0-.074-.004h-.052v.18l.07-.001c.064-.001.072-.002.085-.01zm.367.08v-.387h.156l.12.144.122.143a7.06 7.06 0 0 0 .122-.144l.12-.143h.156v.774h-.182l-.002-.485-.1.122c-.097.117-.1.122-.113.122s-.016-.004-.113-.122l-.1-.122-.002.484h-.182z"/></g><text y="-121.044" x="-677.022" xml:space="preserve" font-size="1.065" font-family="A-OTF Shin Go Pro" letter-spacing="0" word-spacing="0"><tspan y="3.02" x="1.108">32</tspan></text></svg> \ No newline at end of file diff --git a/assets/arch-freertos.svg b/assets/arch-freertos.svg new file mode 100644 index 0000000..1f73977 --- /dev/null +++ b/assets/arch-freertos.svg @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + id="svg16387" + version="1.1" + viewBox="0 0 3.8163 3.8163" + height="3.8163mm" + width="3.8163mm"> +<defs + id="defs16381" /> +<metadata + id="metadata16384"> +<rdf:RDF> +<cc:Work + rdf:about=""> +<dc:format>image/svg+xml</dc:format> +<dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> +<dc:title></dc:title> +</cc:Work> +</rdf:RDF> +</metadata> +<g + transform="translate(-68.3959,-56.2998)" + id="layer1"> +<g + transform="translate(744.086,158.913)" + style="display:inline" + id="g16363"> +<circle + r="1.90815" + cy="-100.705" + cx="-673.782" + id="path2695-6-36-3-8-7-3-6-6-2-0-9-5" + style="display:inline;opacity:0.420955;fill:#f2f2f2;stroke:none;stroke-width:0.068301;stroke-linecap:round" /> +<g + transform="matrix(0.005642,0,0,0.005642,-670.229,-102.096)" + id="g16315"> +<path + style="fill:#ffffff;stroke:none;stroke-width:3.99999;stroke-linecap:round;stroke-linejoin:round;paint-order:markers fill stroke" + d="m -937.285,325.773 569.292,39.05 19.537,-21.257 12.645,-169.989 -8.84,-15.064 -526.531,-13.597 -23.445,12.043 -55.353,151.915 z" + id="path16308" /> +<g + id="g16300" + transform="translate(108.604,65.6306)"> +<path + style="fill:#8ac552;stroke-width:0.264583" + d="m -496.878,307.954 c -3.424,-0.284 -13.552,-1.026 -18.124,-1.327 -1.966,-0.129 -5.239,-0.358 -7.276,-0.51 -2.037,-0.151 -11.205,-0.813 -20.373,-1.47 -9.168,-0.657 -19.05,-1.373 -21.96,-1.59 -5.713,-0.426 -10.671,-0.774 -16.934,-1.189 -4.757,-0.315 -9.966,-0.705 -14.287,-1.068 -1.746,-0.146 -5.08,-0.391 -7.408,-0.543 -2.329,-0.152 -5.84,-0.391 -7.806,-0.53 -1.963,-0.139 -5.178,-0.363 -7.143,-0.497 -5.856,-0.399 -11.039,-0.777 -14.552,-1.06 -3.945,-0.318 -8.618,-0.656 -16.669,-1.204 -3.273,-0.223 -8.274,-0.583 -11.113,-0.8 -4.63,-0.355 -25.723,-1.874 -34.925,-2.516 -12.149,-0.848 -14.917,-1.047 -27.913,-2.005 -3.056,-0.225 -6.985,-0.509 -8.732,-0.63 -1.746,-0.121 -5.913,-0.414 -9.26,-0.65 -3.347,-0.237 -7.633,-0.538 -9.525,-0.671 -1.892,-0.133 -5.879,-0.428 -8.864,-0.658 -2.981,-0.229 -7.09,-0.528 -9.128,-0.665 -2.037,-0.137 -5.31,-0.366 -7.276,-0.51 -6.506,-0.475 -29.378,-2.116 -37.041,-2.658 -4.148,-0.293 -9.029,-0.648 -10.848,-0.788 -3.669,-0.283 -7.657,-0.573 -18.653,-1.355 -6.664,-0.473 -9.857,-0.702 -22.093,-1.586 -1.965,-0.141 -5.12,-0.366 -7.012,-0.498 -8.498,-0.596 -15.348,-1.09 -18.52,-1.336 -4.541,-0.352 -13.924,-1.029 -18.389,-1.326 -1.965,-0.131 -4.405,-0.305 -5.424,-0.387 -3.216,-0.259 -15.787,-1.172 -25.532,-1.854 -5.166,-0.362 -11,-0.776 -12.965,-0.921 -1.964,-0.145 -5.239,-0.385 -7.276,-0.533 -2.037,-0.148 -5.311,-0.39 -7.276,-0.537 -1.964,-0.147 -5.417,-0.387 -7.673,-0.534 -2.255,-0.147 -6.363,-0.446 -9.128,-0.664 -2.765,-0.218 -7.646,-0.579 -10.848,-0.802 -3.201,-0.223 -7.306,-0.519 -9.126,-0.659 -1.82,-0.139 -5.12,-0.365 -7.34,-0.503 -2.22,-0.138 -4.42,-0.318 -4.9,-0.4 -0.47,-0.08 -2.76,-0.264 -5.09,-0.404 -15.87,-0.949 -17.76,-1.204 -22.92,-3.085 -6.58,-2.4 -11.95,-8.31 -14.04,-15.436 -0.15,-0.509 -0.41,-1.093 -0.57,-1.297 -0.22,-0.26 -0.31,-2.643 -0.31,-7.938 0,-6.639 0.1,-7.691 0.47,-8.59 0.25,-0.564 0.46,-1.244 0.46,-1.513 0,-0.268 0.16,-0.879 0.37,-1.356 1.32,-3.13 1.79,-4.343 2.62,-6.822 0.37,-1.092 0.92,-2.64 1.23,-3.44 1.12,-2.877 1.37,-3.584 1.59,-4.366 0.11,-0.436 0.4,-1.151 0.64,-1.587 0.23,-0.437 0.42,-0.961 0.43,-1.165 0,-0.204 0.28,-0.978 0.63,-1.72 0.35,-0.742 0.72,-1.765 0.83,-2.274 0.1,-0.51 0.36,-1.161 0.56,-1.449 0.2,-0.286 0.36,-0.744 0.36,-1.017 0,-0.272 0.17,-0.812 0.38,-1.198 0.21,-0.386 0.64,-1.476 0.95,-2.422 0.31,-0.946 0.66,-1.839 0.77,-1.984 0.12,-0.146 0.28,-0.563 0.36,-0.926 0.22,-0.99 1.09,-3.374 1.87,-5.126 0.38,-0.855 0.7,-1.808 0.7,-2.117 0,-0.309 0.19,-0.794 0.4,-1.076 0.21,-0.283 0.39,-0.685 0.39,-0.893 0,-0.316 1.27,-3.769 2.11,-5.737 0.12,-0.291 0.29,-0.768 0.37,-1.059 0.27,-1.037 1.17,-3.464 1.85,-4.993 0.39,-0.855 0.7,-1.814 0.7,-2.13 0,-0.317 0.11,-0.689 0.24,-0.827 0.25,-0.255 0.93,-1.981 1.34,-3.427 0.43,-1.474 0.97,-2.793 1.28,-3.104 0.17,-0.17 0.31,-0.612 0.31,-0.984 0,-0.37 0.18,-1.015 0.4,-1.432 0.22,-0.417 0.4,-0.893 0.4,-1.058 0,-0.166 0.15,-0.599 0.34,-0.962 0.19,-0.364 0.62,-1.436 0.95,-2.382 0.34,-0.945 0.7,-1.958 0.81,-2.249 0.11,-0.291 0.47,-1.243 0.79,-2.116 0.32,-0.873 0.77,-2.064 0.99,-2.646 0.76,-1.99 1.63,-4.467 1.99,-5.689 0.14,-0.509 0.36,-1.045 0.48,-1.19 0.12,-0.146 0.47,-1.039 0.77,-1.985 0.31,-0.945 0.68,-1.958 0.81,-2.249 0.34,-0.724 1.05,-2.74 1.31,-3.704 0.11,-0.436 0.52,-1.441 0.9,-2.232 0.39,-0.791 0.7,-1.625 0.7,-1.852 0,-0.228 0.18,-0.755 0.4,-1.171 0.22,-0.417 0.4,-0.92 0.4,-1.118 0,-0.198 0.23,-0.869 0.51,-1.491 0.55,-1.234 1.99,-5.046 2.22,-5.894 0.38,-1.417 1.25,-3.619 1.93,-4.936 0.2,-0.386 0.36,-0.963 0.36,-1.282 0,-0.319 0.17,-0.896 0.38,-1.282 0.21,-0.386 0.99,-2.429 1.75,-4.539 0.75,-2.11 1.47,-4.074 1.6,-4.365 0.29,-0.677 0.99,-2.663 1.33,-3.761 0.14,-0.468 0.37,-0.921 0.51,-1.008 0.14,-0.09 0.26,-0.485 0.26,-0.885 0,-0.4 0.17,-0.958 0.38,-1.241 0.22,-0.282 0.4,-0.657 0.4,-0.832 0,-0.78 3.33,-7.0967 4.18,-7.9797 0.19,-0.1983 0.74,-0.9722 1.22,-1.7198 1.16,-1.7898 8.545,-8.8998 9.254,-8.8998 0.09,0 0.755,-0.4202 1.47,-0.9337 3.34,-2.3996 9.474,-5.3011 13.121,-6.2067 1.31,-0.3252 2.501,-0.6807 2.646,-0.7901 0.146,-0.1094 0.979,-0.276 1.852,-0.3702 0.873,-0.094 2.473,-0.2888 3.556,-0.4323 1.303,-0.1727 5.329,-0.1686 11.907,0.012 5.465,0.1502 17.021,0.3914 25.68,0.536 8.658,0.1446 19.076,0.3704 23.151,0.5018 4.075,0.1314 10.861,0.2474 15.081,0.2578 4.22,0.01 14.46,0.1908 22.754,0.4009 8.295,0.2102 21.154,0.4516 28.575,0.5365 7.422,0.085 13.97,0.2089 14.552,0.2756 0.583,0.067 9.036,0.2555 18.786,0.4195 9.75,0.164 19.156,0.3311 20.902,0.3714 1.746,0.04 10.557,0.1976 19.579,0.3498 9.022,0.1522 21.524,0.4031 27.781,0.5576 6.258,0.1545 18.521,0.4235 27.253,0.5976 8.731,0.1742 16.827,0.346 17.991,0.3817 2.44,0.075 16.119,0.3144 28.046,0.4913 4.511,0.067 13.859,0.2382 20.77,0.3807 6.913,0.1424 15.843,0.2678 19.844,0.2786 4.003,0.011 9.48,0.1284 12.17,0.2613 2.694,0.1329 10.848,0.312 18.124,0.3981 7.276,0.086 17.458,0.2788 22.622,0.4284 5.168,0.1496 16.418,0.3785 25.003,0.5087 8.586,0.1301 19.778,0.3538 24.871,0.4971 5.093,0.1432 14.261,0.318 20.373,0.3883 19.365,0.2229 34.481,0.6783 36.38,1.0959 0.945,0.2079 2.405,0.455 3.244,0.5491 0.839,0.094 1.585,0.2659 1.656,0.3818 0.07,0.1158 0.635,0.2943 1.255,0.3967 0.616,0.1023 1.357,0.3099 1.645,0.4612 0.286,0.1514 0.881,0.3676 1.323,0.4804 5.318,1.3635 11.264,5.0095 16.113,9.8796 1.728,1.736 3.302,3.5439 3.501,4.0169 0.198,0.474 0.473,0.931 0.611,1.016 0.14,0.09 0.418,0.481 0.622,0.878 0.206,0.398 0.807,1.521 1.341,2.497 0.532,0.976 0.969,1.907 0.969,2.068 0,0.161 0.314,0.97 0.695,1.797 1.882,4.059 1.972,7.331 0.641,23.597 -0.228,2.766 -0.675,8.948 -1.342,18.521 -0.137,1.965 -0.492,6.846 -0.791,10.848 -0.582,7.84 -1.175,16.095 -1.59,22.225 -0.143,2.11 -0.495,6.873 -0.78,10.583 -0.897,11.614 -1.056,13.732 -1.466,19.712 -0.22,3.201 -0.516,7.13 -0.659,8.731 -0.146,1.601 -0.439,5.411 -0.656,8.467 -0.22,3.056 -0.521,7.104 -0.675,8.996 -0.336,4.185 -0.643,8.081 -1.188,15.213 -1.304,16.971 -2.233,22.155 -4.956,27.649 -3.138,6.331 -11.025,13.721 -19.116,17.912 -7.948,4.117 -16.51,5.76 -25.929,4.977 z m 26.3,-19.461 c 1.077,-0.225 2.167,-0.521 2.423,-0.658 0.54,-0.289 1.76,-3.343 1.95,-4.887 0.07,-0.582 0.199,-2.157 0.281,-3.499 0.08,-1.343 0.203,-2.593 0.272,-2.779 0.07,-0.185 0.302,-2.717 0.519,-5.628 0.537,-7.22 0.897,-11.652 1.071,-13.229 0.08,-0.728 0.318,-3.645 0.53,-6.482 0.211,-2.838 0.563,-7.124 0.78,-9.525 0.22,-2.401 0.577,-6.628 0.794,-9.393 0.217,-2.765 0.637,-7.587 0.936,-10.716 0.299,-3.128 0.715,-8.01 0.924,-10.848 0.209,-2.837 0.442,-5.635 0.521,-6.217 0.191,-1.447 0.868,-9.255 1.328,-15.346 0.209,-2.765 0.564,-7.111 0.789,-9.657 0.225,-2.547 0.521,-6.059 0.656,-7.806 0.304,-3.898 1.257,-13.577 1.598,-16.272 0.14,-1.091 0.376,-3.591 0.521,-5.556 0.283,-3.73 0.807,-9.88 1.458,-17.065 0.212,-2.329 0.455,-5.484 0.54,-7.012 0.09,-1.528 0.262,-3.254 0.391,-3.836 0.127,-0.582 0.257,-2.131 0.284,-3.442 0.03,-1.31 0.148,-3.096 0.267,-3.968 0.357,-2.604 1.098,-12.044 1.087,-13.817 0,-1.8253 -0.905,-3.8859 -1.974,-4.5534 -1.878,-1.1739 -4.677,-1.3145 -39.616,-1.9891 -28.016,-0.541 -33.305,-0.6534 -38.364,-0.8149 -2.4,-0.077 -10.2,-0.2552 -17.331,-0.3969 -7.13,-0.1417 -18.142,-0.3718 -24.473,-0.5114 -6.329,-0.1396 -15.378,-0.3187 -20.109,-0.398 -4.728,-0.079 -9.967,-0.2012 -11.641,-0.2708 -3.078,-0.1279 -6.565,-0.2009 -31.883,-0.6677 -7.712,-0.1422 -18.367,-0.378 -23.68,-0.5241 -5.31,-0.146 -16.383,-0.3851 -24.606,-0.5312 -8.221,-0.146 -16.317,-0.3212 -17.992,-0.3891 -1.672,-0.068 -7.387,-0.1914 -12.7,-0.2743 -5.31,-0.083 -11.681,-0.2044 -14.155,-0.2699 -2.474,-0.066 -10.628,-0.2384 -18.124,-0.3842 -7.493,-0.1458 -15.412,-0.3268 -17.595,-0.4022 -3.707,-0.128 -14.457,-0.351 -63.389,-1.3153 -10.707,-0.211 -24.042,-0.5039 -29.633,-0.6509 -15.052,-0.3958 -35.658,-0.8288 -82.264,-1.7287 -10.987,-0.2121 -21.941,-0.4496 -24.342,-0.5276 -2.401,-0.078 -6.211,-0.2007 -8.467,-0.2724 -2.255,-0.072 -7.275,-0.151 -11.155,-0.1762 l -7.053,-0.046 -1.839,1.0924 c -1.012,0.6008 -2.029,1.3828 -2.262,1.7379 -0.411,0.6279 -1.769,3.9766 -2.697,6.651 -0.754,2.1751 -1.621,4.6131 -2.588,7.276 -0.5,1.383 -1.53,4.24 -2.29,6.35 -0.75,2.11 -1.65,4.551 -1.99,5.424 -0.34,0.873 -0.8,2.123 -1.02,2.778 -0.22,0.655 -1.37,3.81 -2.56,7.012 -1.19,3.201 -2.32,6.297 -2.51,6.879 -0.51,1.56 -1.73,4.947 -1.99,5.556 -0.13,0.291 -1.09,2.911 -2.14,5.821 -1.87,5.225 -2.27,6.327 -4.45,12.303 -1.08,2.952 -3.87,10.655 -5.23,14.42 -0.37,1.019 -1.07,2.924 -1.56,4.233 -0.49,1.31 -1.05,2.858 -1.26,3.44 -0.2,0.582 -0.93,2.606 -1.62,4.498 -0.68,1.891 -1.76,4.868 -2.38,6.614 -1.95,5.437 -4.49,12.425 -7.75,21.299 -0.51,1.383 -1.37,3.764 -1.92,5.292 -0.55,1.528 -1.5,4.147 -2.12,5.821 -0.61,1.673 -1.63,4.471 -2.26,6.217 -0.63,1.747 -1.51,4.188 -1.97,5.424 -0.45,1.237 -1.61,4.393 -2.56,7.012 -0.96,2.619 -2.01,5.477 -2.34,6.35 -0.77,2.005 -1.05,3.883 -0.7,4.553 0.15,0.282 0.73,0.824 1.29,1.204 0.85,0.586 1.47,0.735 4.05,0.972 1.67,0.154 4.05,0.285 5.29,0.29 1.24,0 3.08,0.116 4.1,0.246 1.95,0.25 3.84,0.39 10.98,0.811 6.33,0.374 8.27,0.508 11.38,0.785 1.53,0.136 3.91,0.315 5.29,0.399 8.09,0.485 15.417,0.948 20.769,1.312 11.878,0.809 18.015,1.224 19.843,1.339 5.702,0.36 14.437,0.959 17.198,1.179 1.747,0.14 4.723,0.324 6.615,0.41 1.892,0.09 5.106,0.322 7.144,0.525 2.037,0.203 5.311,0.437 7.276,0.519 1.964,0.08 4.941,0.262 6.614,0.399 5.193,0.423 10.322,0.781 13.494,0.941 1.673,0.09 5.245,0.321 7.937,0.526 2.693,0.205 7.931,0.56 11.642,0.789 3.711,0.228 8.89,0.589 11.51,0.8 2.619,0.212 6.965,0.507 9.657,0.656 2.692,0.149 7.097,0.44 9.789,0.648 2.693,0.207 6.145,0.447 7.673,0.534 1.528,0.09 3.493,0.207 4.366,0.269 0.873,0.06 2.659,0.181 3.969,0.265 1.309,0.08 3.81,0.267 5.556,0.406 1.746,0.14 6.092,0.435 9.657,0.656 3.566,0.221 8.923,0.576 11.907,0.789 2.983,0.213 9.114,0.629 13.626,0.926 4.511,0.296 12.546,0.833 17.859,1.193 5.313,0.36 10.61,0.715 11.774,0.79 1.164,0.07 4.498,0.31 7.408,0.521 2.911,0.212 8.388,0.572 12.171,0.801 3.784,0.228 8.189,0.536 9.79,0.683 1.6,0.147 4.857,0.378 7.239,0.513 8.4,0.475 13.427,0.806 16.176,1.063 1.53,0.144 5.101,0.383 7.938,0.532 2.839,0.149 6.112,0.382 7.276,0.517 1.164,0.135 3.902,0.316 6.085,0.402 2.183,0.09 4.268,0.209 4.63,0.273 0.366,0.06 3.699,0.298 7.409,0.52 3.712,0.222 6.985,0.457 7.279,0.522 0.293,0.06 2.137,0.192 4.101,0.282 1.963,0.09 4.759,0.278 6.215,0.416 1.455,0.138 4.725,0.363 7.268,0.499 6.069,0.324 16.528,1.022 17.867,1.192 0.582,0.07 3.143,0.252 5.689,0.396 2.548,0.145 6.143,0.378 7.993,0.518 1.846,0.14 4.823,0.333 6.614,0.429 1.789,0.1 3.318,0.212 3.397,0.259 0.08,0.05 1.569,0.158 3.308,0.25 5.273,0.278 10.53,0.642 11.231,0.777 0.365,0.07 3.104,0.259 6.086,0.42 2.984,0.161 5.9,0.335 6.482,0.386 0.582,0.05 2.905,0.215 5.162,0.364 2.257,0.148 5.887,0.393 8.07,0.543 2.18,0.151 6.823,0.459 10.316,0.685 3.492,0.226 7.959,0.538 9.922,0.693 1.966,0.154 5.477,0.379 7.805,0.499 2.328,0.121 5.781,0.341 7.673,0.491 1.892,0.149 4.63,0.337 6.085,0.416 2.305,0.126 6.21,0.433 10.187,0.8 1.156,0.108 4.212,0.276 10.186,0.563 1.601,0.08 4.162,0.251 5.689,0.388 1.529,0.136 4.744,0.37 7.144,0.52 2.399,0.149 6.688,0.443 9.525,0.653 11.906,0.882 16.92,1.208 18.917,1.229 1.165,0.01 2.831,0.111 3.705,0.22 0.873,0.108 1.767,0.214 1.984,0.235 0.217,0.02 1.278,-0.145 2.355,-0.369 z m -36.619,-34.794 c -1.746,-0.112 -6.508,-0.396 -10.583,-0.629 -4.075,-0.234 -9.194,-0.536 -11.377,-0.671 -2.183,-0.135 -7.064,-0.433 -10.848,-0.66 -3.784,-0.228 -8.784,-0.535 -11.113,-0.683 -2.328,-0.147 -6.315,-0.385 -8.863,-0.527 -2.545,-0.143 -6.593,-0.381 -8.996,-0.529 -2.4,-0.149 -7.281,-0.445 -10.848,-0.66 -3.564,-0.214 -8.445,-0.508 -10.848,-0.655 -18.931,-1.155 -26.926,-1.641 -30.691,-1.866 -2.4,-0.143 -7.343,-0.44 -10.981,-0.66 -3.638,-0.219 -8.577,-0.509 -10.98,-0.645 -2.4,-0.135 -6.21,-0.367 -8.467,-0.516 -2.254,-0.149 -6.183,-0.394 -8.731,-0.545 -2.545,-0.15 -6.593,-0.397 -8.996,-0.547 -11.027,-0.69 -21.949,-1.353 -26.061,-1.581 -2.477,-0.137 -7.477,-0.431 -11.113,-0.654 -3.638,-0.222 -8.638,-0.52 -11.112,-0.661 -2.474,-0.142 -6.403,-0.378 -8.731,-0.525 -2.329,-0.147 -7.329,-0.447 -11.113,-0.666 -3.783,-0.219 -8.723,-0.516 -10.98,-0.66 -2.254,-0.144 -7.136,-0.44 -10.848,-0.659 -3.709,-0.218 -8.591,-0.519 -10.848,-0.668 -2.254,-0.148 -6.125,-0.384 -8.599,-0.523 -2.474,-0.14 -6.403,-0.37 -8.731,-0.511 -2.328,-0.141 -6.437,-0.381 -9.128,-0.533 -5.363,-0.302 -11.47,-0.692 -16.801,-1.073 -1.892,-0.135 -5.047,-0.312 -7.012,-0.394 -1.964,-0.08 -5.179,-0.26 -7.143,-0.398 -1.965,-0.137 -5.656,-0.375 -8.203,-0.528 -11.715,-0.707 -17.918,-1.086 -21.828,-1.335 -2.328,-0.149 -8.281,-0.504 -13.229,-0.79 -4.948,-0.286 -10.901,-0.639 -13.229,-0.784 -2.328,-0.146 -6.257,-0.383 -8.731,-0.528 -2.474,-0.144 -6.463,-0.381 -8.864,-0.526 -2.401,-0.144 -7.521,-0.443 -11.377,-0.664 -3.856,-0.221 -9.631,-0.58 -12.832,-0.798 -3.202,-0.217 -8.083,-0.518 -10.848,-0.67 -12.413,-0.678 -14.555,-0.835 -14.86,-1.089 -0.241,-0.2 -0.253,-0.452 -0.05,-0.976 0.153,-0.389 1.039,-2.903 1.968,-5.587 0.93,-2.684 1.799,-4.99 1.933,-5.124 0.148,-0.148 1.987,-0.126 4.698,0.06 2.45,0.164 6.776,0.421 9.614,0.572 2.838,0.151 7.065,0.382 9.393,0.515 2.328,0.132 6.376,0.36 8.996,0.506 5.262,0.295 11.793,0.681 18.256,1.079 2.328,0.144 6.555,0.386 9.393,0.538 2.837,0.152 7.064,0.387 9.392,0.523 14.526,0.847 21.269,1.232 32.28,1.845 5.238,0.292 11.31,0.65 13.493,0.796 3.778,0.254 8.307,0.513 18.653,1.068 2.547,0.137 6.655,0.365 9.129,0.507 2.473,0.142 6.641,0.378 9.26,0.525 5.329,0.297 11.726,0.685 17.859,1.083 2.257,0.147 6.602,0.392 9.658,0.546 3.056,0.153 7.342,0.384 9.525,0.512 12.61,0.743 27.916,1.622 32.279,1.855 2.765,0.148 6.932,0.385 9.26,0.526 4.043,0.246 12.145,0.71 23.019,1.32 2.765,0.155 10.147,0.582 16.404,0.948 6.258,0.367 13.343,0.774 15.743,0.905 2.402,0.13 7.522,0.426 11.377,0.656 3.858,0.231 9.155,0.531 11.774,0.668 2.619,0.136 6.609,0.369 8.864,0.517 2.256,0.148 6.305,0.387 8.995,0.532 5.715,0.308 12.637,0.704 18.786,1.075 2.402,0.145 7.461,0.439 11.245,0.653 3.783,0.214 8.903,0.511 11.377,0.659 2.474,0.148 7.773,0.446 11.774,0.662 8.588,0.464 15.758,0.875 18.52,1.062 1.741,0.117 15.865,0.912 30.692,1.726 2.694,0.148 7.633,0.443 10.98,0.655 3.347,0.213 6.311,0.388 6.583,0.389 0.455,0 0.487,0.162 0.347,1.789 -0.162,1.886 -0.511,4.906 -1.048,9.062 l -0.325,2.513 -1.191,0.03 c -0.654,0.02 -2.619,-0.06 -4.366,-0.175 z m -494.553,-22.72 c -1.48,-0.08 -2.77,-0.235 -2.87,-0.334 -0.18,-0.18 0.26,-1.59 1.57,-5.077 0.38,-0.999 0.69,-1.845 0.69,-1.88 0,-0.03 -1.05,-0.144 -2.32,-0.244 -6.78,-0.533 -10.25,-2.486 -11.39,-6.408 -0.83,-2.856 -0.27,-5.037 4.25,-16.638 0.46,-1.164 0.95,-2.262 1.11,-2.439 0.22,-0.25 1.3,-0.265 4.76,-0.07 2.47,0.141 4.6,0.369 4.73,0.507 0.26,0.254 0.23,0.359 -1.03,3.587 -2.24,5.779 -2.53,6.78 -2.21,7.733 0.39,1.171 1.75,1.812 4.4,2.069 l 1.904,0.186 0.536,-1.158 c 0.295,-0.636 0.968,-2.348 1.494,-3.803 0.527,-1.455 1.095,-2.787 1.262,-2.96 0.228,-0.236 1.166,-0.255 3.744,-0.08 1.892,0.131 3.827,0.243 4.3,0.248 0.603,0 0.86,0.135 0.86,0.428 0,0.425 -1.08,3.598 -2.091,6.14 -0.305,0.769 -0.555,1.438 -0.555,1.487 0,0.136 7.394,0.64 18.124,1.237 5.384,0.299 9.87,0.625 9.969,0.724 0.169,0.169 -2.763,8.943 -3.618,10.83 l -0.39,0.86 h -2.039 c -1.121,0 -3.356,-0.117 -4.965,-0.26 -1.609,-0.142 -5.486,-0.394 -8.615,-0.558 -3.128,-0.164 -7.236,-0.385 -9.128,-0.49 -3.677,-0.205 -4.101,-0.163 -4.101,0.407 0,0.201 -0.287,1.111 -0.638,2.022 -0.352,0.911 -0.844,2.222 -1.094,2.914 l -0.456,1.256 -1.75,-0.04 c -0.963,-0.02 -2.963,-0.114 -4.443,-0.199 z m 441.372,-8.638 c -10.118,-0.693 -19.389,-2.077 -28.437,-4.244 -2.318,-0.555 -7.284,-1.94 -8.377,-2.336 -0.212,-0.08 -0.146,-0.641 0.222,-1.915 0.286,-0.993 0.619,-2.163 0.741,-2.6 0.691,-2.493 5.559,-18.743 5.638,-18.824 0.05,-0.05 1.217,0.426 2.588,1.064 1.37,0.639 3.027,1.347 3.68,1.573 0.656,0.227 2.085,0.731 3.175,1.12 5.371,1.916 11.401,3.277 18.786,4.24 4.217,0.55 13.52,0.781 16.801,0.418 8.826,-0.979 13.271,-4.21 13.573,-9.865 0.13,-2.485 -0.384,-3.822 -2.233,-5.8 -2.4,-2.566 -7.901,-5.166 -15.574,-7.361 -8.133,-2.328 -16.039,-4.872 -18.388,-5.918 -6.477,-2.883 -9.483,-4.714 -12.6,-7.675 -4.442,-4.221 -6.453,-8.933 -6.45,-15.131 0,-14.235 9.451,-24.724 26.8,-29.765 4.976,-1.446 10.959,-2.529 17.782,-3.22 4.715,-0.476 19.86,-0.397 25.4,0.133 5.144,0.493 12.703,1.55 15.534,2.173 1.061,0.233 2.405,0.524 2.987,0.647 1.72,0.363 4.921,1.245 5.075,1.398 0.08,0.08 -0.233,1.726 -0.688,3.663 -0.458,1.937 -1.074,4.594 -1.371,5.903 -0.542,2.389 -1.979,8.568 -2.339,10.054 -0.24,1.001 -0.42,0.987 -4.117,-0.329 -7.59,-2.702 -15.515,-3.969 -26.326,-4.209 -9.236,-0.206 -14.107,0.458 -18.068,2.461 -5.075,2.568 -6.255,8.065 -2.447,11.401 2.513,2.201 6.556,3.737 18.282,6.947 8.578,2.348 12.793,3.678 16.918,5.339 16.295,6.56 22.402,17.416 18.499,32.895 -2.799,11.106 -11.035,19.212 -24.188,23.804 -10.398,3.63 -25.021,5.047 -40.878,3.959 z m -118.136,-6.353 c -13.814,-1.113 -25.324,-4.476 -33.245,-9.713 -6.001,-3.966 -10.025,-8.372 -12.878,-14.106 -2.796,-5.618 -3.884,-10.63 -3.884,-17.901 0,-17.16 7.335,-34.435 19.333,-45.535 8.676,-8.027 20.818,-13.599 33.981,-15.597 1.455,-0.221 3.659,-0.558 4.895,-0.749 3.116,-0.483 20.351,-0.491 23.812,-0.01 10.348,1.434 14.743,2.492 21.035,5.063 7.58,3.095 14.187,8.307 18.224,14.372 6.501,9.772 7.895,23.268 4.011,38.822 -1.894,7.588 -5.432,15.037 -10.173,21.431 -1.812,2.443 -6.138,6.955 -8.623,8.99 -1.651,1.352 -6.368,4.768 -6.585,4.768 -0.05,0 -1.085,0.572 -2.308,1.272 -7.114,4.073 -15.819,6.831 -26.561,8.413 -2.508,0.37 -18.018,0.724 -21.034,0.481 z m 13.612,-21.15 c 4.342,-0.699 8.687,-2.429 12.383,-4.932 2.273,-1.539 6.413,-5.734 8.215,-8.323 2.749,-3.952 5.453,-10.055 6.662,-15.045 1.106,-4.566 1.305,-6.351 1.313,-11.913 0,-4.959 -0.06,-5.797 -0.593,-7.805 -2.365,-8.842 -8.07,-13.884 -17.497,-15.458 -4.36,-0.729 -10.649,-0.442 -14.703,0.669 -2.36,0.647 -6.479,2.555 -8.599,3.983 -7.416,4.997 -12.567,13.174 -15.311,24.3 -2.617,10.612 -1.416,20.929 3.103,26.643 3.504,4.432 7.745,6.809 14.06,7.881 2.794,0.474 8.02,0.474 10.967,0 z m -115.345,13.878 c -1.6,-0.114 -5.588,-0.342 -8.863,-0.508 -9.933,-0.502 -18.603,-1.057 -18.745,-1.199 -0.129,-0.128 2.823,-11.162 4.99,-18.649 0.225,-0.781 3.48,-12.592 4.92,-17.86 0.239,-0.873 0.719,-2.599 1.067,-3.836 0.348,-1.237 0.766,-2.725 0.928,-3.307 0.163,-0.582 0.401,-1.416 0.529,-1.852 0.127,-0.437 0.942,-3.414 1.81,-6.615 0.868,-3.201 1.688,-6.178 1.82,-6.615 0.135,-0.436 0.561,-1.984 0.95,-3.439 0.387,-1.455 0.879,-3.241 1.088,-3.969 0.577,-1.983 0.791,-2.757 2.196,-7.875 1.267,-4.618 1.28,-4.701 0.727,-4.814 -0.307,-0.06 -5.085,-0.254 -10.615,-0.423 -15.32,-0.471 -16.945,-0.572 -16.87,-1.043 0.12,-0.763 5.054,-17.508 5.201,-17.655 0.172,-0.172 6.183,-0.133 11.008,0.07 3.577,0.151 12.676,0.436 33.602,1.054 7.35,0.216 17.052,0.511 21.564,0.653 4.511,0.143 11.093,0.34 14.628,0.438 3.535,0.1 6.496,0.288 6.578,0.421 0.08,0.134 0.03,0.74 -0.135,1.347 -0.159,0.607 -1.016,4.139 -1.91,7.85 -2.194,9.111 -2.194,9.11 -2.784,9.231 -0.5,0.103 -16.367,-0.368 -24.14,-0.716 -2.233,-0.1 -4.17,-0.114 -4.303,-0.03 -0.134,0.08 -0.531,1.273 -0.883,2.646 -1.574,6.157 -4.39,16.999 -5.972,23.001 -0.709,2.692 -2.027,7.753 -2.926,11.245 -0.9,3.493 -2.088,8.077 -2.638,10.187 -0.553,2.11 -1.659,6.396 -2.463,9.525 -1.601,6.243 -2.514,9.738 -4.313,16.536 -0.656,2.474 -1.259,4.796 -1.339,5.159 -0.209,0.938 -0.585,1.329 -1.233,1.286 -0.309,-0.02 -1.873,-0.13 -3.474,-0.244 z m -68.659,-3.733 c -1.674,-0.109 -7.865,-0.444 -13.759,-0.742 -15.23,-0.771 -15.065,-0.755 -15.202,-1.432 -0.06,-0.307 -0.181,-2.464 -0.265,-4.792 -0.391,-10.859 -0.986,-19.204 -1.631,-22.886 -1.207,-6.888 -3.745,-10.199 -8.698,-11.347 -1.438,-0.333 -6.974,-0.809 -9.533,-0.818 h -1.594 l -6.319,19.116 c -3.476,10.514 -6.399,19.209 -6.497,19.323 -0.1,0.113 -2.43,0.08 -5.183,-0.07 -2.753,-0.154 -8.22,-0.454 -12.149,-0.667 -13.341,-0.722 -12.568,-0.648 -12.568,-1.204 0,-0.422 2.415,-7.592 3.57,-10.6 0.203,-0.528 0.84,-2.374 1.417,-4.101 0.577,-1.728 1.66,-4.868 2.407,-6.978 0.748,-2.11 1.699,-4.848 2.114,-6.085 0.416,-1.237 1.493,-4.333 2.393,-6.879 0.901,-2.547 1.918,-5.464 2.26,-6.483 0.341,-1.018 1.412,-4.114 2.378,-6.879 0.967,-2.765 2.828,-8.122 4.137,-11.906 1.309,-3.784 2.86,-8.248 3.445,-9.922 0.586,-1.673 1.639,-4.709 2.341,-6.747 3.286,-9.544 5.261,-15.169 5.657,-16.115 l 0.433,-1.034 4.957,0.177 c 2.726,0.1 9.005,0.292 13.952,0.431 39.429,1.111 42.424,1.338 49.245,3.73 12.474,4.375 16.421,13.339 11.207,25.453 -2.795,6.495 -8.033,12.117 -14.178,15.219 -3.335,1.684 -7.463,3.072 -11.216,3.772 -0.946,0.177 -1.809,0.4 -1.917,0.496 -0.108,0.09 0.487,0.488 1.323,0.872 4.139,1.903 5.795,5.137 6.673,13.037 0.146,1.309 0.38,3.214 0.521,4.233 0.847,6.129 1.41,10.604 2.117,16.811 0.438,3.851 0.922,8.014 1.075,9.251 0.493,3.979 0.602,5.743 0.363,5.86 -0.128,0.06 -1.603,0.02 -3.276,-0.08 z m -25.251,-60.034 c 6.321,-0.879 10.71,-3.548 12.765,-7.763 2.25,-4.615 1.47,-8.162 -2.226,-10.132 -3.689,-1.966 -7.611,-2.633 -17.066,-2.899 l -5.981,-0.168 -3.346,10.06 c -1.84,5.533 -3.345,10.249 -3.345,10.48 0,0.346 0.413,0.436 2.315,0.509 5.75,0.219 15.029,0.171 16.884,-0.09 z m -86.007,54.242 c -1.6,-0.114 -6.601,-0.399 -11.112,-0.635 -4.511,-0.236 -9.886,-0.543 -11.945,-0.682 -2.059,-0.14 -4.797,-0.256 -6.085,-0.258 -1.289,0 -2.513,-0.112 -2.721,-0.243 -0.314,-0.199 -0.116,-0.957 1.156,-4.428 0.844,-2.304 1.729,-4.604 1.967,-5.111 l 0.431,-0.924 3.043,0.07 3.041,0.07 -0.793,-0.595 c -3.613,-2.705 -4.219,-5.906 -2.015,-10.633 0.974,-2.089 1.224,-2.378 2.015,-2.328 2.449,0.156 9.307,0.923 9.444,1.057 0.101,0.1 -0.232,1.017 -0.741,2.041 -0.977,1.967 -1.368,4.106 -1.054,5.778 0.422,2.25 3.172,3.923 7.209,4.386 1.287,0.148 5.138,0.393 8.557,0.545 6.713,0.299 8.732,0.483 8.732,0.794 0,0.109 -0.31,1.017 -0.689,2.016 -0.379,1 -0.906,2.473 -1.171,3.273 -1.043,3.149 -1.905,5.439 -2.165,5.755 -0.296,0.357 -0.702,0.361 -5.104,0.05 z m -8.466,-25.785 c -4.998,-1.1 -8.387,-3.6 -9.752,-7.195 -0.798,-2.103 -0.864,-5.109 -0.173,-7.883 1.72,-6.9 5.926,-12.042 12.174,-14.887 3.013,-1.372 4.412,-1.794 8.052,-2.431 1.903,-0.333 8.793,-0.367 8.993,-0.04 0.128,0.207 -0.302,1.516 -2.358,7.177 -0.344,0.946 -1.161,3.208 -1.817,5.027 -0.656,1.819 -1.672,4.612 -2.258,6.207 -0.586,1.594 -1.012,2.953 -0.946,3.019 0.07,0.07 0.8,-0.03 1.632,-0.219 3.74,-0.837 6.571,-3.874 8.342,-8.95 0.738,-2.117 0.804,-2.595 0.812,-5.851 v -3.546 l 4.205,0.166 c 2.312,0.09 4.317,0.235 4.455,0.32 0.386,0.238 0.295,4.251 -0.149,6.571 -2.017,10.537 -8.709,18.503 -18.274,21.75 -3.391,1.152 -9.543,1.517 -12.945,0.769 z m 4.578,-11.968 c 2.303,-6.34 3.756,-10.484 3.756,-10.708 0,-0.18 -0.542,-0.228 -1.521,-0.137 -4.08,0.383 -6.802,2.499 -7.879,6.126 -0.685,2.304 0.08,3.789 2.521,4.896 1.747,0.792 2.793,0.733 3.123,-0.177 z m 10.209,-26.241 c -2.197,-0.351 -4.999,-1.315 -6.27,-2.157 -1.707,-1.13 -3.301,-2.963 -4.081,-4.694 -0.934,-2.07 -0.972,-5.627 -0.09,-8.513 2.886,-9.45 10.275,-14.955 21.434,-15.971 3.439,-0.313 5.913,-0.327 6.806,-0.04 0.333,0.108 0.338,0.09 -1.365,4.765 -0.529,1.455 -1.476,4.075 -2.103,5.821 -0.627,1.746 -1.684,4.69 -2.348,6.541 -0.665,1.852 -1.126,3.415 -1.025,3.475 0.348,0.203 2.997,-0.621 4.132,-1.286 1.806,-1.058 3.284,-2.745 4.404,-5.026 1.699,-3.461 2.301,-7.004 1.747,-10.284 l -0.218,-1.289 4.372,0.106 c 2.405,0.06 4.468,0.202 4.585,0.319 0.355,0.356 0.181,4.402 -0.283,6.587 -2.326,10.955 -9.691,18.805 -19.929,21.243 -2.053,0.489 -7.752,0.724 -9.767,0.402 z m 4.251,-13.38 c 0.48,-1.31 1.363,-3.715 1.963,-5.346 0.6,-1.63 1.092,-3.029 1.092,-3.108 0,-0.289 -2.545,-0.13 -3.742,0.234 -2.76,0.837 -4.973,3.06 -5.51,5.535 -0.534,2.455 0.436,4.011 3.013,4.831 2.018,0.643 2.212,0.511 3.184,-2.146 z" + id="path16306" /> +<path + style="fill:#48b049;stroke-width:0.264583" + d="m -492.909,307.31 c -0.146,-0.05 -2.527,-0.236 -5.292,-0.402 -2.765,-0.167 -6.397,-0.416 -8.07,-0.555 -3.378,-0.281 -4.968,-0.397 -8.863,-0.652 -1.455,-0.1 -3.061,-0.22 -3.572,-0.279 -0.508,-0.06 -1.998,-0.17 -3.307,-0.248 -2.569,-0.155 -5.559,-0.366 -7.673,-0.544 -0.728,-0.06 -4.776,-0.35 -8.996,-0.643 -4.22,-0.293 -9.16,-0.652 -10.98,-0.797 -1.818,-0.145 -5.213,-0.384 -7.541,-0.531 -2.328,-0.146 -5.543,-0.383 -7.144,-0.526 -1.6,-0.142 -4.873,-0.38 -7.276,-0.529 -2.399,-0.149 -5.019,-0.326 -5.821,-0.395 -3.167,-0.27 -11.654,-0.902 -14.155,-1.054 -4.675,-0.282 -6.593,-0.42 -7.54,-0.541 -0.508,-0.06 -2.831,-0.244 -5.16,-0.398 -7.355,-0.485 -36.475,-2.585 -38.497,-2.777 -0.653,-0.06 -3.154,-0.234 -5.556,-0.382 -2.4,-0.148 -5.733,-0.388 -7.408,-0.534 -2.686,-0.235 -6.192,-0.49 -12.965,-0.943 -2.042,-0.137 -2.736,-0.19 -8.599,-0.659 -1.892,-0.152 -4.331,-0.333 -5.424,-0.404 -3.495,-0.227 -13.298,-0.904 -15.213,-1.051 -1.019,-0.08 -5.067,-0.373 -8.996,-0.656 -3.929,-0.282 -8.512,-0.627 -10.187,-0.767 -1.672,-0.14 -4.233,-0.324 -5.688,-0.409 -1.455,-0.09 -3.18,-0.214 -3.837,-0.284 -0.653,-0.07 -2.976,-0.243 -5.159,-0.383 -7.21,-0.464 -15.327,-1.052 -16.536,-1.2 -0.654,-0.08 -3.154,-0.258 -5.557,-0.396 -4.945,-0.284 -9.567,-0.611 -16.668,-1.182 -2.691,-0.217 -6.858,-0.52 -9.261,-0.674 -2.4,-0.155 -4.9,-0.334 -5.556,-0.399 -0.654,-0.06 -4.763,-0.358 -9.128,-0.65 -4.366,-0.292 -8.295,-0.588 -8.732,-0.657 -0.436,-0.07 -3.71,-0.308 -7.276,-0.531 -3.565,-0.223 -6.958,-0.458 -7.54,-0.523 -1.114,-0.124 -3.528,-0.302 -7.276,-0.534 -4.912,-0.305 -6.942,-0.45 -11.642,-0.832 -2.692,-0.219 -5.669,-0.447 -6.614,-0.507 -1.868,-0.119 -2.256,-0.147 -12.833,-0.924 -3.929,-0.288 -8.156,-0.589 -9.393,-0.669 -3.531,-0.228 -9.049,-0.649 -9.921,-0.757 -0.437,-0.05 -2.223,-0.179 -3.969,-0.278 -1.746,-0.1 -3.651,-0.227 -4.233,-0.284 -0.583,-0.06 -3.023,-0.228 -5.424,-0.378 -2.402,-0.15 -4.842,-0.33 -5.424,-0.401 -0.582,-0.07 -3.083,-0.252 -5.557,-0.404 -2.473,-0.153 -5.093,-0.33 -5.82,-0.394 -0.728,-0.06 -2.335,-0.184 -3.572,-0.266 -1.237,-0.08 -3.619,-0.268 -5.292,-0.411 -1.674,-0.144 -5.662,-0.433 -8.864,-0.644 -3.201,-0.21 -7.249,-0.501 -8.995,-0.646 -1.747,-0.145 -5.14,-0.393 -7.541,-0.551 -8.62,-0.567 -11.791,-0.793 -12.832,-0.914 -0.582,-0.07 -3.321,-0.247 -6.086,-0.399 -2.765,-0.151 -5.444,-0.333 -5.953,-0.402 -1.092,-0.151 -13.617,-1.083 -17.992,-1.339 -1.673,-0.1 -3.221,-0.211 -3.439,-0.25 -0.358,-0.06 -1.42,-0.143 -7.144,-0.533 -5.235,-0.357 -6.492,-0.459 -6.622,-0.535 -0.1,-0.05 -1.45,-0.155 -3.04,-0.243 -1.6,-0.09 -3.68,-0.216 -4.63,-0.287 -0.94,-0.07 -4.22,-0.3 -7.27,-0.509 -5.79,-0.396 -10.68,-0.75 -16.89,-1.222 -2.01,-0.153 -4.51,-0.452 -5.56,-0.665 -1.04,-0.213 -2.37,-0.46 -2.95,-0.548 -0.58,-0.09 -1.21,-0.276 -1.39,-0.418 -0.18,-0.142 -0.75,-0.179 -1.27,-0.08 -0.53,0.1 -1.09,0.06 -1.26,-0.08 -0.17,-0.141 -0.85,-0.466 -1.51,-0.723 -4.02,-1.575 -9.32,-7.383 -10.97,-12.016 -1.79,-5.064 -1.73,-4.614 -1.73,-11.244 0,-5.421 0.1,-6.346 0.53,-7.806 0.29,-0.911 0.53,-1.891 0.53,-2.177 0,-0.286 0.18,-0.836 0.4,-1.222 0.4,-0.724 0.47,-0.899 2.36,-6.284 0.62,-1.761 1.21,-3.328 1.3,-3.483 0.1,-0.155 0.64,-1.572 1.19,-3.149 1.46,-4.114 3.2,-8.811 3.64,-9.822 0.2,-0.478 0.37,-1.076 0.37,-1.33 0,-0.253 0.16,-0.696 0.36,-0.983 0.2,-0.287 0.6,-1.236 0.91,-2.109 1.22,-3.54 2.66,-7.47 3.09,-8.467 0.26,-0.582 0.56,-1.416 0.68,-1.852 0.24,-0.892 1.45,-4.216 1.83,-5.027 0.32,-0.677 2.13,-5.827 2.13,-6.053 0,-0.09 0.23,-0.675 0.51,-1.292 0.29,-0.617 0.76,-1.836 1.05,-2.709 0.3,-0.874 0.78,-2.243 1.09,-3.043 1.15,-3.06 1.66,-4.462 2.69,-7.409 0.59,-1.673 1.16,-3.161 1.27,-3.307 0.12,-0.145 0.63,-1.515 1.15,-3.043 0.52,-1.527 1.19,-3.432 1.5,-4.233 0.31,-0.8 0.69,-1.812 0.85,-2.249 0.15,-0.436 0.38,-0.907 0.51,-1.045 0.12,-0.138 0.23,-0.458 0.23,-0.711 0,-0.252 0.37,-1.389 0.82,-2.526 0.91,-2.313 1.95,-5.158 3.15,-8.63 0.45,-1.281 1.1,-2.97 1.46,-3.754 0.36,-0.783 0.65,-1.663 0.65,-1.954 0,-0.292 0.12,-0.604 0.27,-0.694 0.14,-0.09 0.26,-0.399 0.26,-0.686 0,-0.288 0.29,-1.084 0.63,-1.771 0.35,-0.686 0.82,-1.843 1.05,-2.57 0.55,-1.759 2.24,-6.457 2.45,-6.798 0.1,-0.149 0.68,-1.716 1.3,-3.483 0.62,-1.767 1.5,-4.225 1.96,-5.462 0.45,-1.237 1.16,-3.202 1.58,-4.366 0.41,-1.164 0.96,-2.593 1.2,-3.175 0.25,-0.582 0.74,-1.927 1.09,-2.989 0.66,-1.997 2.8,-6.4846 3.45,-7.2181 0.21,-0.2297 0.37,-0.5022 0.37,-0.6056 0,-0.1032 0.82,-1.2845 1.83,-2.6249 1.67,-2.2274 3.45,-4.0833 6.645,-6.9333 1.663,-1.4819 3.457,-2.5824 2.699,-1.6555 -0.56,0.6857 -1.971,3.7154 -2.71,5.8197 -0.422,1.2012 -1.196,3.3746 -1.724,4.8298 -0.52,1.4552 -1.1,3.1221 -1.29,3.7039 -0.19,0.582 -0.59,1.654 -0.89,2.382 -0.31,0.727 -1.29,3.346 -2.18,5.82 -2.31,6.398 -2.75,7.609 -3.84,10.584 -0.53,1.455 -1.31,3.598 -1.72,4.762 -0.41,1.164 -1.29,3.605 -1.97,5.424 -0.67,1.819 -1.47,4.022 -1.76,4.895 -1,2.95 -2.36,6.612 -2.65,7.112 -0.15,0.274 -0.43,1.048 -0.62,1.72 -0.19,0.672 -0.85,2.532 -1.45,4.133 -0.61,1.6 -1.38,3.684 -1.71,4.63 -0.33,0.946 -1.1,3.089 -1.72,4.762 -1.17,3.167 -3.19,8.739 -4.65,12.833 -0.95,2.663 -4.56,12.601 -6.34,17.462 -0.64,1.746 -1.77,4.842 -2.51,6.879 -0.75,2.038 -1.63,4.478 -1.98,5.424 -0.34,0.946 -1.1,3.03 -1.68,4.63 -0.58,1.601 -1.56,4.28 -2.18,5.954 -0.62,1.673 -1.51,4.114 -1.98,5.423 -0.46,1.31 -1.44,3.989 -2.18,5.954 -1.53,4.135 -3.29,8.952 -5.1,14.023 -0.7,1.964 -1.64,4.524 -2.09,5.688 -1.18,3.034 -1.61,4.863 -1.61,6.815 0,1.703 0,1.718 1.27,2.898 1.36,1.275 1.82,1.394 6.41,1.682 3.31,0.208 13.66,0.903 17.33,1.165 2.03,0.145 5.55,0.389 7.8,0.542 2.26,0.153 5.53,0.396 7.28,0.539 1.74,0.144 5.14,0.376 7.54,0.516 2.4,0.141 5.26,0.319 6.35,0.397 4.246,0.303 9.845,0.67 14.02,0.919 2.402,0.143 5.854,0.383 7.673,0.532 3.253,0.267 11.112,0.817 18.918,1.323 2.183,0.141 5.457,0.372 7.276,0.514 1.819,0.141 4.379,0.322 5.689,0.402 7.831,0.479 14.976,0.959 16.007,1.076 0.655,0.07 4.167,0.312 7.805,0.53 3.638,0.218 7.27,0.455 8.07,0.528 2.124,0.194 8.04,0.62 14.684,1.058 10.795,0.711 17.699,1.175 23.548,1.582 3.202,0.222 8.56,0.581 11.907,0.798 9.964,0.645 20.063,1.344 24.738,1.712 2.913,0.23 6.218,0.453 13.891,0.937 1.164,0.07 4.617,0.309 7.673,0.524 3.056,0.215 6.508,0.452 7.673,0.527 15.063,0.974 21.904,1.433 25.664,1.722 1.892,0.145 4.691,0.33 6.218,0.412 1.529,0.08 3.196,0.189 3.704,0.238 0.511,0.05 2.892,0.221 5.292,0.379 2.402,0.159 6.212,0.41 8.466,0.559 2.257,0.149 4.877,0.329 5.821,0.399 4.935,0.369 9.835,0.702 13.626,0.926 2.329,0.138 5.305,0.366 6.615,0.506 1.31,0.141 4.059,0.332 6.112,0.425 2.05,0.09 4.193,0.212 4.762,0.266 0.566,0.05 2.998,0.224 5.398,0.378 2.402,0.153 5.794,0.393 7.54,0.533 1.747,0.14 4.665,0.323 6.483,0.406 1.82,0.08 4.378,0.264 5.688,0.402 1.31,0.138 4.466,0.372 7.012,0.52 5.596,0.326 10.816,0.678 15.61,1.053 1.966,0.153 6.429,0.451 9.922,0.662 3.492,0.211 6.887,0.445 7.541,0.521 0.656,0.08 2.262,0.192 3.571,0.259 4.453,0.228 12.69,0.797 15.666,1.083 0.612,0.06 3.173,0.23 5.689,0.382 2.516,0.151 7.136,0.453 10.263,0.67 3.13,0.218 7.536,0.52 9.79,0.671 2.257,0.151 6.649,0.453 9.766,0.671 9.416,0.659 17.115,1.167 21.719,1.434 2.403,0.139 4.842,0.317 5.424,0.395 1.164,0.156 9.263,0.723 18.918,1.325 3.421,0.213 7.826,0.507 9.789,0.652 6.729,0.497 14.949,1.049 17.974,1.206 1.664,0.09 3.101,0.203 3.193,0.26 0.09,0.06 1.648,0.172 3.456,0.256 1.809,0.08 4.781,0.27 6.598,0.411 1.821,0.142 5.451,0.381 8.07,0.531 2.619,0.151 5.773,0.383 7.012,0.517 1.238,0.134 4.034,0.312 6.217,0.394 2.183,0.08 4.98,0.267 6.218,0.409 1.95,0.224 7.078,0.597 12.965,0.943 2.825,0.166 6.561,-0.339 8.122,-1.098 2.416,-1.175 4.673,-4.189 5.332,-7.125 0.164,-0.728 0.476,-2.097 0.69,-3.043 0.217,-0.946 0.516,-3.565 0.664,-5.821 0.148,-2.255 0.442,-5.887 0.651,-8.07 0.212,-2.182 0.516,-5.754 0.68,-7.937 0.162,-2.183 0.466,-6.052 0.677,-8.599 0.212,-2.547 0.498,-6.118 0.638,-7.937 0.138,-1.819 0.498,-6.046 0.802,-9.393 0.301,-3.347 0.661,-7.633 0.799,-9.525 0.137,-1.892 0.368,-4.571 0.516,-5.953 0.145,-1.383 0.458,-5.133 0.698,-8.335 0.519,-6.976 0.744,-9.855 1.268,-16.139 0.219,-2.62 0.524,-6.311 0.68,-8.202 0.153,-1.892 0.452,-5.226 0.658,-7.409 0.209,-2.183 0.453,-5.219 0.54,-6.747 0.09,-1.528 0.265,-3.611 0.394,-4.63 0.13,-1.018 0.307,-3.207 0.397,-4.864 0.09,-1.657 0.268,-3.563 0.395,-4.235 0.127,-0.673 0.304,-2.294 0.394,-3.603 0.09,-1.309 0.33,-4.284 0.534,-6.612 0.207,-2.329 0.561,-6.794 0.791,-9.922 0.233,-3.129 0.598,-7.594 0.815,-9.922 0.215,-2.328 0.511,-5.781 0.654,-7.673 0.145,-1.892 0.378,-4.749 0.524,-6.35 0.143,-1.601 0.336,-4.939 0.431,-7.418 0.196,-5.0452 0.217,-4.9494 -1.678,-7.1769 -1.33,-1.5637 -2.884,-2.4039 -5.371,-2.9061 -1.923,-0.3885 -11.602,-0.7416 -31.353,-1.1439 -7.276,-0.1482 -15.311,-0.33 -17.859,-0.4041 -2.545,-0.074 -11.655,-0.2518 -20.241,-0.3952 -8.585,-0.1433 -16.92,-0.3205 -18.521,-0.3937 -1.6,-0.073 -6.897,-0.1977 -11.774,-0.2766 -16.562,-0.2683 -34.983,-0.6547 -50.138,-1.0517 -2.619,-0.069 -8.988,-0.1901 -14.155,-0.2699 -5.165,-0.08 -10.346,-0.1964 -11.51,-0.259 -1.164,-0.063 -7.712,-0.1804 -14.552,-0.2616 -6.839,-0.081 -13.388,-0.2043 -14.552,-0.2738 -2.556,-0.1525 -10.006,-0.333 -27.252,-0.6602 -26.718,-0.507 -52.509,-1.0454 -56.092,-1.1709 -2.037,-0.071 -7.99,-0.1944 -13.229,-0.2735 -10.289,-0.1553 -15.777,-0.2666 -32.544,-0.6599 -5.966,-0.14 -14.003,-0.3185 -17.859,-0.3967 -9.363,-0.1898 -16.821,-0.3501 -31.089,-0.6681 -23.99,-0.5346 -36.449,-0.7907 -45.243,-0.9302 -4.948,-0.078 -13.342,-0.2553 -18.653,-0.3931 -5.312,-0.1377 -13.289,-0.3155 -17.728,-0.395 -4.438,-0.079 -10.272,-0.2003 -12.964,-0.2686 -9.942,-0.2521 -16.504,-0.3885 -25.797,-0.5363 -5.239,-0.083 -12.918,-0.2639 -17.066,-0.4014 -4.147,-0.1375 -9.267,-0.2542 -11.377,-0.2594 l -3.836,-0.01 0.786,-0.5869 c 1.391,-1.039 6.274,-3.0056 9.268,-3.733 1.601,-0.3889 3.327,-0.8521 3.836,-1.0294 1.121,-0.3902 27.259,-0.3297 42.202,0.098 5.238,0.1499 13.215,0.3273 17.727,0.3943 4.511,0.067 13.262,0.2418 19.446,0.3884 6.185,0.1467 15.829,0.3274 21.432,0.4016 5.602,0.074 17.925,0.3045 27.384,0.5117 9.459,0.2072 21.484,0.4475 26.723,0.534 5.239,0.087 11.192,0.2049 13.229,0.2631 7.15,0.2045 26.776,0.5744 62.574,1.1792 8.951,0.1513 21.095,0.3973 26.988,0.5468 5.895,0.1495 18.872,0.3825 28.839,0.5178 9.97,0.1352 19.196,0.3155 20.506,0.4006 1.309,0.085 10.358,0.2876 20.108,0.45 9.75,0.1624 18.918,0.3281 20.373,0.3681 1.455,0.04 7.707,0.1408 13.89,0.2241 15.899,0.214 24.911,0.3974 27.517,0.5599 2.262,0.1411 34.904,0.7378 63.897,1.1682 28.07,0.4166 50.684,1.0906 54.107,1.6126 2.064,0.3145 4.136,0.822 5.961,1.4604 0.588,0.2052 1.305,0.373 1.596,0.373 0.291,0 0.762,0.1557 1.05,0.3461 0.286,0.1903 1.236,0.5723 2.109,0.8488 0.873,0.2765 1.775,0.6566 2.005,0.8446 0.231,0.188 0.519,0.3418 0.643,0.3418 0.416,0 2.464,1.0794 2.786,1.4684 0.175,0.211 0.421,0.3837 0.546,0.3837 0.481,0 3.098,1.8616 4.923,3.5005 2.35,2.1091 5.633,5.6964 5.633,6.1514 0,0.186 0.178,0.484 0.392,0.662 0.214,0.179 0.553,0.64 0.751,1.025 0.196,0.385 0.794,1.498 1.329,2.474 0.531,0.976 0.968,1.91 0.968,2.074 0,0.165 0.355,1.067 0.788,2.005 1.413,3.049 1.882,7.746 1.363,13.594 -0.283,3.158 -0.595,7.321 -1.098,14.552 -0.487,7.025 -1.048,14.829 -1.304,18.124 -0.545,7.017 -1.403,18.72 -2.003,27.384 -0.217,3.129 -0.511,7.117 -0.654,8.864 -0.143,1.746 -0.442,5.675 -0.661,8.731 -0.9,12.469 -2.069,28.165 -2.514,33.734 -0.145,1.819 -0.444,5.748 -0.667,8.731 -0.727,9.764 -0.941,12.53 -1.188,15.346 -0.134,1.528 -0.314,3.671 -0.396,4.763 -0.723,9.465 -2.059,15.777 -4.271,20.183 -3.566,7.106 -11.451,14.346 -20.206,18.555 -5.543,2.665 -9.083,3.652 -13.359,3.725 -1.601,0.03 -3.863,0.125 -5.027,0.218 -1.164,0.09 -2.236,0.124 -2.381,0.07 z m -16.537,-53.88 c -2.677,-0.199 -4.783,-0.329 -19.182,-1.18 -3.638,-0.215 -8.4,-0.509 -10.583,-0.654 -2.183,-0.145 -7.123,-0.442 -10.98,-0.659 -7.745,-0.437 -13.888,-0.806 -20.109,-1.209 -2.254,-0.146 -10.112,-0.62 -17.462,-1.053 -11.843,-0.699 -18.288,-1.086 -35.058,-2.107 -9.361,-0.57 -13.425,-0.814 -19.843,-1.194 -3.638,-0.215 -8.401,-0.511 -10.584,-0.656 -2.183,-0.146 -7.183,-0.444 -11.112,-0.663 -8.988,-0.501 -15.142,-0.876 -19.712,-1.203 -1.963,-0.141 -5.715,-0.373 -8.334,-0.517 -5.768,-0.316 -8.274,-0.463 -13.626,-0.802 -2.254,-0.143 -7.316,-0.437 -11.245,-0.653 -3.929,-0.216 -8.811,-0.515 -10.848,-0.666 -2.037,-0.15 -7.752,-0.508 -12.7,-0.795 -4.948,-0.287 -12.983,-0.763 -17.859,-1.057 -4.874,-0.295 -11.84,-0.713 -15.478,-0.929 -3.638,-0.215 -8.639,-0.515 -11.113,-0.666 -2.474,-0.15 -6.522,-0.386 -8.996,-0.524 -10.305,-0.576 -13.828,-0.792 -15.081,-0.926 -0.728,-0.08 -3.406,-0.253 -5.953,-0.388 -2.547,-0.135 -7.726,-0.432 -11.509,-0.659 -3.784,-0.228 -8.785,-0.527 -11.113,-0.666 -2.328,-0.139 -6.079,-0.372 -8.334,-0.52 -3.67,-0.24 -7.757,-0.487 -22.093,-1.336 -2.328,-0.138 -6.138,-0.366 -8.467,-0.508 -5.169,-0.313 -28.044,-1.692 -39.952,-2.408 -4.802,-0.289 -10.577,-0.642 -12.832,-0.785 -2.256,-0.143 -7.316,-0.439 -11.245,-0.657 -9.465,-0.526 -11.031,-0.675 -10.905,-1.034 0.06,-0.161 0.916,-2.674 1.908,-5.585 1.792,-5.25 2.136,-6.054 2.498,-5.831 0.103,0.06 2.56,0.241 5.46,0.393 4.873,0.256 12.381,0.672 23.926,1.327 2.546,0.145 7.606,0.441 11.244,0.659 3.638,0.217 8.818,0.518 11.51,0.667 5.4,0.3 11.461,0.648 18.521,1.063 2.546,0.15 9.869,0.571 16.271,0.936 6.403,0.365 14.678,0.841 18.389,1.058 3.711,0.217 8.89,0.516 11.509,0.664 2.62,0.149 6.727,0.382 9.129,0.519 2.401,0.137 7.58,0.43 11.509,0.651 3.929,0.221 9.227,0.525 11.774,0.675 18.534,1.094 22.831,1.34 32.147,1.843 2.619,0.142 7.8,0.444 11.509,0.671 3.712,0.227 8.713,0.523 11.113,0.656 2.402,0.134 7.522,0.426 11.377,0.649 3.857,0.224 9.096,0.523 11.641,0.667 2.548,0.143 7.787,0.442 11.642,0.666 3.858,0.223 12.311,0.704 18.785,1.068 11.092,0.625 14.833,0.839 25.4,1.455 11.253,0.657 13.96,0.811 18.521,1.057 2.694,0.145 7.933,0.448 11.642,0.673 3.712,0.226 8.652,0.517 10.98,0.647 2.329,0.131 7.567,0.427 11.642,0.658 4.074,0.231 9.432,0.532 11.906,0.668 2.474,0.136 7.356,0.428 10.848,0.648 3.493,0.221 8.612,0.521 11.377,0.667 4.964,0.264 7.808,0.425 21.035,1.189 3.712,0.215 10.914,0.629 16.007,0.921 5.093,0.293 14.56,0.841 21.034,1.219 6.477,0.378 13.351,0.752 15.277,0.832 2.106,0.09 3.591,0.264 3.726,0.442 0.214,0.28 -0.31,5.79 -1.016,10.681 l -0.315,2.183 -2.419,-0.04 c -1.33,-0.02 -3.669,-0.131 -5.199,-0.245 z m -491.264,-22.551 c -2.7,-0.184 -3.77,-0.351 -3.77,-0.587 0,-0.181 0.52,-1.767 1.15,-3.523 l 1.16,-3.194 -0.69,-0.129 c -0.38,-0.07 -1.64,-0.208 -2.79,-0.305 -5.04,-0.421 -8.35,-2.027 -9.74,-4.726 -0.76,-1.486 -0.91,-4.266 -0.35,-6.64 0.44,-1.899 4.75,-13.295 5.19,-13.741 0.25,-0.256 8.73,0.195 9.21,0.489 0.19,0.122 -0.85,3.183 -2.35,6.902 -1.45,3.585 -1.38,4.805 0.31,5.806 0.9,0.528 4.941,0.997 5.419,0.628 0.326,-0.252 0.333,-0.268 1.925,-4.565 0.59,-1.591 1.184,-3.005 1.321,-3.141 0.242,-0.243 8.385,0.153 8.652,0.42 0.07,0.07 -0.522,1.836 -1.311,3.928 -0.79,2.092 -1.36,3.879 -1.268,3.97 0.09,0.09 3.68,0.361 7.973,0.599 4.293,0.238 9.77,0.549 12.171,0.692 2.401,0.142 5.107,0.297 6.014,0.343 0.906,0.05 1.716,0.153 1.8,0.237 0.195,0.195 -3.527,10.818 -3.943,11.255 -0.242,0.254 -1.123,0.273 -3.878,0.08 -1.961,-0.135 -6.065,-0.379 -9.121,-0.544 -3.056,-0.164 -7.021,-0.41 -8.812,-0.546 -1.79,-0.136 -4.058,-0.248 -5.04,-0.248 h -1.784 l -0.338,0.992 c -0.186,0.546 -0.755,2.094 -1.264,3.44 -0.795,2.098 -1.009,2.441 -1.502,2.404 -0.316,-0.02 -2.271,-0.159 -4.344,-0.3 z m 38.959,-31.954 c -2.037,-0.112 -5.966,-0.337 -8.731,-0.5 -7.374,-0.433 -19.427,-1.057 -20.572,-1.064 -0.733,0 -0.992,-0.116 -0.992,-0.431 0,-0.423 2.986,-8.511 3.493,-9.462 0.244,-0.458 0.511,-0.483 3.479,-0.327 l 3.215,0.169 -0.926,-0.726 c -3.003,-2.355 -4.004,-4.463 -3.452,-7.269 0.296,-1.503 1.878,-5.321 2.316,-5.592 0.231,-0.142 9.841,0.764 10.022,0.946 0.06,0.06 -0.326,1.055 -0.863,2.206 -0.821,1.76 -0.979,2.386 -0.994,3.944 -0.02,2.259 0.518,3.338 2.254,4.509 2.096,1.412 5.117,1.805 18.432,2.395 2.073,0.09 3.77,0.278 3.77,0.412 0,0.135 -0.643,2.057 -1.43,4.27 -0.786,2.214 -1.633,4.65 -1.882,5.414 l -0.453,1.389 -1.491,-0.04 c -0.82,-0.02 -3.158,-0.131 -5.195,-0.243 z m -5.159,-25.433 c -5.435,-0.676 -9.33,-3.071 -11.086,-6.816 -0.623,-1.327 -0.688,-1.77 -0.684,-4.642 0,-2.87 0.08,-3.404 0.834,-5.556 2.394,-6.872 7.189,-11.622 14.111,-13.979 3.189,-1.086 6.104,-1.518 10.283,-1.522 2.09,0 3.864,0.06 3.941,0.135 0.08,0.08 -0.609,2.197 -1.524,4.71 -0.915,2.514 -2.175,5.999 -2.801,7.745 -0.626,1.746 -1.518,4.187 -1.982,5.424 -0.464,1.237 -0.902,2.585 -0.974,2.995 -0.128,0.735 -0.116,0.743 0.895,0.559 4.348,-0.788 7.68,-4.119 9.35,-9.345 0.778,-2.437 1.113,-5.448 0.809,-7.289 -0.142,-0.865 -0.205,-1.626 -0.138,-1.692 0.235,-0.235 8.206,0 8.458,0.241 0.473,0.467 0.174,5.205 -0.5,7.936 -1.365,5.531 -3.85,10.068 -7.508,13.708 -4.294,4.273 -9.315,6.655 -15.657,7.43 -2.53,0.309 -3.041,0.305 -5.827,-0.04 z m 4.817,-16.979 c 1.259,-3.457 2.01,-5.881 1.867,-6.025 -0.377,-0.377 -3.605,0.185 -5.163,0.898 -4.403,2.016 -6.179,7.535 -3.124,9.711 1.078,0.767 2.641,1.32 3.553,1.255 0.741,-0.05 0.813,-0.199 2.867,-5.839 z m 9.735,-21.361 c -6.09,-0.682 -9.655,-2.717 -11.469,-6.546 -0.785,-1.659 -0.833,-1.932 -0.812,-4.672 0.02,-2.658 0.102,-3.121 0.948,-5.338 1.218,-3.197 2.363,-5.076 4.451,-7.311 3.359,-3.595 7.525,-5.761 13.356,-6.945 2.052,-0.416 9.443,-0.688 9.775,-0.359 0.09,0.09 -0.213,1.157 -0.666,2.381 -0.452,1.224 -0.98,2.702 -1.172,3.284 -0.192,0.582 -0.902,2.547 -1.577,4.366 -2.291,6.169 -3.574,9.852 -3.574,10.253 0,0.611 1.856,0.267 3.847,-0.714 4.311,-2.122 7.137,-7.827 6.952,-14.034 l -0.08,-2.776 3.969,0.09 c 2.446,0.06 4.109,0.205 4.335,0.39 0.458,0.377 0.259,4.184 -0.359,6.847 -1.552,6.696 -4.444,11.53 -9.25,15.464 -2.475,2.027 -7.055,4.34 -9.808,4.953 -1.628,0.363 -6.388,1.015 -6.747,0.924 -0.07,-0.02 -1.025,-0.134 -2.116,-0.256 z m 3.117,-13.864 c 0.634,-1.746 1.562,-4.252 2.061,-5.57 0.499,-1.316 0.907,-2.494 0.907,-2.615 0,-0.273 -2.962,-0.02 -4.242,0.359 -1.672,0.498 -3.367,1.786 -4.401,3.342 -0.824,1.242 -1.01,1.777 -1.099,3.16 -0.101,1.587 -0.06,1.711 0.786,2.638 0.908,0.99 3.281,2.048 4.316,1.923 0.407,-0.05 0.768,-0.749 1.672,-3.237 z m -42.717,-38.4088 c 0.1,-0.097 0.256,-0.097 0.353,0 0.1,0.097 0.02,0.1764 -0.176,0.1764 -0.194,0 -0.274,-0.079 -0.177,-0.1764 z" + id="path16304" /> +<path + style="fill:#0a1811;stroke-width:0.264583" + d="m -485.578,302.093 c 0.07,-0.198 0.289,-0.359 0.495,-0.359 0.207,0 0.461,-0.117 0.561,-0.261 0.103,-0.143 0.532,-0.274 0.955,-0.291 0.421,-0.02 0.903,-0.115 1.069,-0.218 0.312,-0.192 0.916,-0.349 2.289,-0.594 0.436,-0.08 0.913,-0.229 1.058,-0.336 0.146,-0.108 1.04,-0.47 1.985,-0.805 2.214,-0.786 6.635,-2.962 7.538,-3.71 0.174,-0.145 0.394,-0.185 0.489,-0.09 0.1,0.1 0.175,0.06 0.175,-0.09 0,-0.146 0.267,-0.39 0.595,-0.541 0.905,-0.419 5.236,-3.735 6.284,-4.811 0.51,-0.524 1.283,-1.261 1.72,-1.638 1.153,-0.998 3.886,-4.095 4.357,-4.939 0.223,-0.399 0.49,-0.789 0.593,-0.867 0.103,-0.08 0.429,-0.673 0.72,-1.323 0.293,-0.649 0.772,-1.538 1.069,-1.975 0.293,-0.436 0.534,-0.999 0.534,-1.25 0,-0.25 0.344,-1.322 0.762,-2.381 0.9,-2.282 1.815,-6.644 2.011,-9.598 0.08,-1.164 0.256,-3.545 0.399,-5.292 0.143,-1.746 0.323,-4.068 0.403,-5.159 0.08,-1.091 0.195,-2.401 0.261,-2.91 0.06,-0.51 0.178,-1.938 0.252,-3.175 0.349,-5.845 0.476,-7.554 0.569,-7.707 0.06,-0.09 0.225,-2.116 0.375,-4.498 0.151,-2.383 0.331,-5.046 0.397,-5.919 0.07,-0.873 0.246,-3.195 0.394,-5.16 0.26,-3.414 0.421,-5.43 0.67,-8.334 0.06,-0.728 0.235,-3.407 0.389,-5.953 0.153,-2.547 0.333,-5.047 0.402,-5.556 0.07,-0.51 0.307,-3.486 0.529,-6.615 0.222,-3.129 0.519,-6.879 0.656,-8.334 0.141,-1.456 0.498,-6.04 0.797,-10.187 0.299,-4.147 0.59,-8.017 0.651,-8.599 0.06,-0.582 0.182,-2.189 0.272,-3.572 0.355,-5.458 0.675,-9.712 0.797,-10.583 0.05,-0.364 0.161,-1.674 0.251,-2.911 0.09,-1.236 0.212,-2.784 0.275,-3.439 0.06,-0.655 0.185,-2.322 0.27,-3.704 0.08,-1.383 0.389,-5.431 0.675,-8.996 0.288,-3.565 0.574,-7.137 0.637,-7.938 0.06,-0.8 0.191,-2.526 0.281,-3.836 0.09,-1.31 0.214,-2.896 0.275,-3.525 0.635,-6.702 0.698,-9.422 0.262,-11.525 -0.238,-1.147 -0.379,-2.141 -0.31,-2.209 0.495,-0.495 1.471,2.109 2.12,5.655 0.476,2.599 0.41,4.432 -0.543,15.44 -0.362,4.191 -0.733,9.34 -1.41,19.58 -0.331,4.988 -1.14,15.742 -1.863,24.738 -0.153,1.892 -0.389,5.404 -0.526,7.805 -0.138,2.401 -0.315,4.842 -0.392,5.424 -0.08,0.582 -0.256,2.726 -0.394,4.763 -0.696,10.169 -1.085,15.445 -1.209,16.404 -0.08,0.582 -0.315,3.678 -0.529,6.879 -0.215,3.202 -0.506,7.131 -0.643,8.731 -0.141,1.601 -0.437,5.649 -0.662,8.996 -0.225,3.347 -0.529,7.395 -0.672,8.996 -0.145,1.601 -0.383,4.875 -0.529,7.276 -0.143,2.401 -0.325,4.663 -0.4,5.027 -0.08,0.364 -0.248,2.269 -0.383,4.234 -0.484,7.091 -0.81,10.742 -1.082,12.17 -0.156,0.801 -0.402,2.11 -0.548,2.911 -0.527,2.863 -1.905,7.042 -2.91,8.821 -0.564,0.995 -1.024,1.9 -1.024,2.011 0,0.343 -3.17,4.477 -4.699,6.129 -2.186,2.361 -5.115,4.599 -7.409,5.661 -1.127,0.521 -2.505,1.181 -3.064,1.466 -0.56,0.285 -1.185,0.518 -1.389,0.518 -0.206,0 -0.373,0.119 -0.373,0.265 0,0.145 -0.129,0.264 -0.291,0.264 -0.158,0 -0.666,0.167 -1.124,0.37 -1.553,0.69 -3.21,1.051 -4.935,1.076 -0.944,0.01 -1.868,0.117 -2.048,0.229 -0.219,0.138 -0.277,0.08 -0.177,-0.183 0.122,-0.317 -0.06,-0.362 -0.997,-0.243 -0.627,0.08 -1.262,0.223 -1.408,0.318 -0.145,0.09 -1.262,0.21 -2.479,0.257 -1.775,0.07 -2.191,0.01 -2.096,-0.274 z m -21.751,-48.654 c -1.966,-0.114 -5.654,-0.339 -8.202,-0.5 -2.545,-0.161 -6.593,-0.412 -8.996,-0.557 -2.4,-0.145 -6.27,-0.386 -8.599,-0.535 -4.323,-0.277 -9.207,-0.571 -17.198,-1.034 -7.577,-0.44 -17.777,-1.053 -22.092,-1.326 -2.255,-0.144 -8.15,-0.499 -13.097,-0.79 -4.948,-0.291 -10.901,-0.653 -13.23,-0.805 -2.328,-0.152 -7.268,-0.453 -10.98,-0.669 -3.709,-0.216 -9.604,-0.569 -13.097,-0.785 -3.492,-0.215 -8.493,-0.511 -11.112,-0.658 -2.619,-0.146 -7.559,-0.447 -10.98,-0.669 -3.419,-0.222 -8.3,-0.52 -10.848,-0.662 -2.546,-0.141 -6.535,-0.377 -8.864,-0.523 -2.328,-0.146 -7.329,-0.446 -11.112,-0.666 -3.784,-0.219 -8.546,-0.514 -10.584,-0.653 -2.037,-0.14 -6.024,-0.376 -8.863,-0.525 -2.837,-0.148 -6.885,-0.387 -8.996,-0.53 -2.109,-0.143 -5.919,-0.385 -8.467,-0.538 -2.545,-0.152 -9.451,-0.571 -15.346,-0.93 -5.892,-0.359 -15.774,-0.948 -21.96,-1.31 -6.183,-0.362 -13.089,-0.777 -15.346,-0.924 -2.254,-0.147 -6.183,-0.389 -8.731,-0.537 -2.545,-0.149 -7.607,-0.45 -11.245,-0.668 -3.638,-0.218 -8.638,-0.515 -11.112,-0.66 -9.119,-0.534 -17.836,-1.059 -21.828,-1.314 -2.256,-0.145 -6.185,-0.39 -8.732,-0.546 -2.546,-0.155 -8.499,-0.519 -13.229,-0.809 -4.729,-0.289 -10.536,-0.639 -12.905,-0.777 -2.368,-0.139 -7.368,-0.437 -11.112,-0.662 -3.744,-0.226 -8.712,-0.518 -11.04,-0.65 -2.329,-0.131 -6.019,-0.364 -8.202,-0.518 -4.027,-0.283 -10.037,-0.64 -26.723,-1.588 -5.093,-0.29 -10.987,-0.647 -13.097,-0.795 -2.11,-0.147 -6.099,-0.392 -8.864,-0.545 -2.765,-0.153 -6.694,-0.391 -8.731,-0.529 -2.037,-0.138 -5.282,-0.329 -7.21,-0.426 -2.286,-0.115 -3.505,-0.278 -3.504,-0.47 0,-0.274 3.652,-11.046 3.79,-11.184 0.06,-0.06 4.987,0.189 13.803,0.701 2.329,0.135 7.567,0.431 11.642,0.657 4.075,0.226 9.194,0.527 11.377,0.668 2.183,0.141 6.35,0.377 9.26,0.523 2.911,0.147 6.066,0.331 7.012,0.409 1.628,0.135 5.546,0.359 16.139,0.922 2.547,0.136 6.417,0.364 8.599,0.508 3.86,0.255 5.778,0.366 21.035,1.22 4.002,0.223 10.252,0.583 13.89,0.797 7.015,0.415 13.203,0.765 20.77,1.174 2.547,0.138 7.786,0.432 11.642,0.655 3.856,0.222 8.141,0.468 9.525,0.546 1.384,0.08 4.239,0.256 6.35,0.395 2.111,0.14 7.35,0.435 11.642,0.656 4.294,0.222 9.652,0.521 11.906,0.667 4.299,0.277 14.459,0.871 22.754,1.331 2.694,0.149 6.623,0.381 8.731,0.516 2.112,0.136 5.385,0.317 7.276,0.404 1.892,0.09 6.059,0.327 9.261,0.533 3.201,0.206 10.226,0.614 15.61,0.906 5.384,0.293 11.756,0.653 14.155,0.8 2.403,0.147 6.628,0.388 9.393,0.535 2.765,0.147 6.874,0.384 9.128,0.527 2.257,0.143 7.435,0.439 11.51,0.657 4.074,0.218 9.374,0.516 11.774,0.663 2.402,0.146 7.522,0.443 11.377,0.658 7.188,0.402 13.25,0.761 20.24,1.199 2.183,0.137 5.697,0.316 7.806,0.399 2.111,0.08 5.326,0.261 7.143,0.395 4.186,0.31 10.84,0.719 17.463,1.073 2.839,0.152 7.183,0.391 9.657,0.532 2.474,0.141 6.583,0.366 9.128,0.501 2.548,0.134 7.549,0.426 11.113,0.648 3.566,0.223 8.744,0.53 11.509,0.684 2.765,0.153 10.266,0.576 16.669,0.94 6.403,0.364 13.716,0.779 16.253,0.923 2.538,0.143 4.67,0.32 4.744,0.392 0.127,0.128 -0.503,5.971 -1.13,10.445 l -0.314,2.249 -0.913,0.03 c -0.503,0.02 -2.519,-0.06 -4.485,-0.174 z m -493.931,-22.76 c -1.65,-0.111 -3.05,-0.258 -3.12,-0.328 -0.11,-0.112 0.18,-0.959 1.9,-5.55 0.21,-0.549 0.38,-1.104 0.38,-1.234 0,-0.131 -0.92,-0.307 -2.05,-0.393 -6.32,-0.482 -9.66,-1.922 -11.05,-4.762 -1.36,-2.781 -1.09,-5.599 1.05,-11.134 0.62,-1.601 1.69,-4.379 2.38,-6.173 l 1.26,-3.262 1.23,0.143 c 0.67,0.08 2.74,0.219 4.6,0.312 1.86,0.09 3.37,0.25 3.37,0.349 0,0.1 -0.57,1.665 -1.26,3.479 -0.7,1.815 -1.55,4.145 -1.9,5.179 l -0.63,1.88 0.51,0.898 c 0.71,1.226 2.02,1.805 4.54,2 2.453,0.189 2.26,0.352 3.39,-2.867 0.374,-1.066 0.99,-2.688 1.369,-3.603 0.63,-1.524 0.746,-1.654 1.371,-1.536 0.376,0.07 2.261,0.209 4.189,0.306 2.346,0.117 3.505,0.275 3.502,0.477 0,0.165 -0.597,1.894 -1.323,3.841 -0.725,1.947 -1.319,3.628 -1.319,3.736 0,0.267 2.302,0.465 10.716,0.925 3.929,0.214 9.287,0.508 11.906,0.653 2.619,0.145 4.911,0.266 5.093,0.269 0.503,0 0.418,0.445 -0.7,3.588 -0.567,1.595 -1.423,4.017 -1.901,5.383 l -0.871,2.484 -3.059,-0.154 c -7.055,-0.354 -16.978,-0.948 -23.362,-1.398 l -2.047,-0.144 -0.385,0.878 c -0.213,0.482 -0.822,2.038 -1.355,3.457 l -0.969,2.58 -1.229,-0.04 c -0.676,-0.02 -2.578,-0.13 -4.226,-0.24 z m 41.758,-31.752 c -5.214,-0.337 -19.492,-1.118 -27.842,-1.523 -2.435,-0.118 -4.509,-0.297 -4.609,-0.397 -0.1,-0.1 -0.05,-0.449 0.104,-0.776 0.465,-0.967 3.243,-8.357 3.243,-8.626 0,-0.438 0.606,-0.481 3.683,-0.259 1.661,0.12 3.09,0.145 3.175,0.06 0.08,-0.09 -0.204,-0.416 -0.641,-0.726 -4.182,-2.972 -4.85,-5.967 -2.447,-10.973 0.443,-0.922 0.966,-1.739 1.162,-1.814 0.363,-0.139 9.394,0.686 9.6,0.877 0.06,0.06 -0.136,0.609 -0.439,1.226 -1.591,3.242 -1.871,5.457 -0.929,7.341 1.319,2.637 3.906,3.579 10.913,3.973 7.875,0.444 13.047,0.783 13.126,0.862 0.04,0.04 -0.564,1.856 -1.343,4.035 -0.779,2.179 -1.64,4.617 -1.912,5.417 -0.576,1.689 -0.259,1.603 -4.844,1.307 z m -8.655,-25.759 c -4.694,-0.897 -7.906,-2.923 -9.473,-5.975 -1.664,-3.241 -1.511,-7.384 0.447,-12.124 3.081,-7.457 9.521,-12.246 18.383,-13.672 2.416,-0.389 8.842,-0.555 9.161,-0.236 0.09,0.09 -0.08,0.773 -0.377,1.521 -0.649,1.64 -3.789,10.303 -4.473,12.34 -0.269,0.801 -0.971,2.743 -1.56,4.318 -0.59,1.574 -1.072,2.988 -1.072,3.142 0,0.186 0.466,0.22 1.373,0.1 4.245,-0.563 7.726,-4.202 9.511,-9.94 0.508,-1.632 0.617,-2.567 0.615,-5.263 V 144.1 l 3.902,0.149 c 2.146,0.08 4.024,0.226 4.173,0.319 0.362,0.223 0.347,3.8 -0.02,5.809 -0.484,2.616 -2.058,7.299 -3.074,9.144 -1.459,2.648 -2.386,3.907 -4.54,6.162 -3.243,3.394 -7.515,5.891 -12.139,7.096 -2.682,0.698 -8.181,0.897 -10.832,0.39 z m 4.083,-11.068 c 0.141,-0.182 0.738,-1.7 1.327,-3.374 0.589,-1.673 1.492,-4.21 2.007,-5.637 1.082,-2.997 1.081,-2.999 -1.471,-2.732 -3.892,0.407 -6.992,2.988 -7.884,6.565 -0.389,1.563 -0.05,2.709 1.144,3.867 1.388,1.345 4.255,2.116 4.877,1.311 z m 11.187,-27.086 c -5.365,-0.674 -9.179,-2.896 -10.849,-6.318 -0.533,-1.092 -0.663,-1.817 -0.743,-4.157 -0.08,-2.502 -0.01,-3.09 0.607,-5.027 2.222,-6.921 6.824,-11.539 13.935,-13.98 3.177,-1.091 6.303,-1.564 10.373,-1.57 h 3.403 l -0.177,0.728 c -0.1,0.4 -0.875,2.632 -1.729,4.96 -4.254,11.604 -5.333,14.753 -5.125,14.955 0.348,0.34 2.562,-0.171 4.086,-0.944 3.179,-1.61 5.928,-5.867 6.755,-10.458 0.373,-2.072 0.342,-4.866 -0.07,-5.93 -0.127,-0.334 0.153,-0.401 1.686,-0.401 1.013,0 2.93,0.08 4.262,0.172 l 2.422,0.172 v 2.192 c 0,2.61 -0.495,5.139 -1.694,8.646 -2.815,8.235 -10.088,14.711 -18.525,16.492 -1.918,0.405 -6.96,0.681 -8.62,0.472 z m 2.851,-11.146 c 0.142,-0.401 1.054,-2.915 2.027,-5.588 0.973,-2.674 1.708,-5.017 1.635,-5.208 -0.336,-0.875 -5.252,0.362 -6.991,1.759 -2.185,1.755 -3.413,4.999 -2.615,6.909 0.608,1.454 2.898,2.793 4.853,2.837 0.678,0.02 0.882,-0.117 1.091,-0.709 z m 510.062,-14.505 c -0.1,-0.388 -0.116,-0.765 -0.05,-0.838 0.07,-0.07 0.212,0.185 0.31,0.573 0.1,0.388 0.119,0.765 0.05,0.837 -0.07,0.07 -0.212,-0.184 -0.31,-0.572 z m -0.68,-1.833 c 0,-0.07 0.06,-0.133 0.132,-0.133 0.07,0 0.133,0.06 0.133,0.133 0,0.07 -0.06,0.132 -0.133,0.132 -0.07,0 -0.132,-0.06 -0.132,-0.132 z" + id="path16302" /> +</g> +</g> +</g> +</g> +</svg> diff --git a/assets/arch-riscv.svg b/assets/arch-riscv.svg new file mode 100644 index 0000000..659b4e0 --- /dev/null +++ b/assets/arch-riscv.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle cx="1.908" cy="1.908" r="1.908" opacity=".42" fill="#f9f9f9"/><path d="M.63 2.073v-1.06l.334.003.385.018c.157.047.26.15.3.298a.59.59 0 0 1 0 .224c-.02.076-.058.142-.11.196s-.094.08-.172.107c-.052.02-.053.02-.314.022l-.262.004-.005.17.895 1.06.015.02H.63v-1.06zm1.634 1.05l.363-.502.38-.53.117-.16c.008-.01.01.086.01.596v.606h-.438c-.35 0-.436-.002-.43-.01zm-.32-.26l-.505-.598-.06-.075c.003-.002.022-.006.045-.01a.75.75 0 0 0 .357-.171c.055-.05.12-.136.154-.2a.78.78 0 0 0 .083-.338c.01-.22-.067-.433-.212-.59a.82.82 0 0 0-.193-.143.66.66 0 0 0-.138-.05c-.03-.007-.055-.015-.06-.02A27.8 27.8 0 0 1 2.27.666l.86.002.005.638-.098.137-.07.1-.477.668-.242.34-.267.346c-.005 0-.02-.014-.033-.03z" fill="#f5b118"/><path d="M.63 2.073v-1.06l.334.003.385.018c.157.047.26.15.3.298a.59.59 0 0 1 0 .224c-.02.076-.058.142-.11.196s-.094.08-.172.107c-.052.02-.053.02-.314.022l-.262.004-.005.17.895 1.06.015.02H.63v-1.06zm1.634 1.05l.363-.502.38-.53.117-.16c.008-.01.01.086.01.596v.606h-.438c-.35 0-.436-.002-.43-.01z" fill="#686e93"/><path d="M.635 3.128C.633 3.125.63 2.65.63 2.072V1.02H.95l.37.01a.42.42 0 0 1 .253.153c.16.215.086.53-.15.646-.086.042-.108.045-.386.048l-.253.003v.178l.088.105.768.91.04.05c.004.007-.1.01-.518.01-.288 0-.526-.002-.528-.005zm1.63 0a.37.37 0 0 1 .034-.047l.785-1.093.043-.058v.6l-.003.6h-.43a10.96 10.96 0 0 1-.43-.004z" fill="#273170"/></svg> \ No newline at end of file diff --git a/assets/arch-riscv64-virt.svg b/assets/arch-riscv64-virt.svg new file mode 100644 index 0000000..659b4e0 --- /dev/null +++ b/assets/arch-riscv64-virt.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle cx="1.908" cy="1.908" r="1.908" opacity=".42" fill="#f9f9f9"/><path d="M.63 2.073v-1.06l.334.003.385.018c.157.047.26.15.3.298a.59.59 0 0 1 0 .224c-.02.076-.058.142-.11.196s-.094.08-.172.107c-.052.02-.053.02-.314.022l-.262.004-.005.17.895 1.06.015.02H.63v-1.06zm1.634 1.05l.363-.502.38-.53.117-.16c.008-.01.01.086.01.596v.606h-.438c-.35 0-.436-.002-.43-.01zm-.32-.26l-.505-.598-.06-.075c.003-.002.022-.006.045-.01a.75.75 0 0 0 .357-.171c.055-.05.12-.136.154-.2a.78.78 0 0 0 .083-.338c.01-.22-.067-.433-.212-.59a.82.82 0 0 0-.193-.143.66.66 0 0 0-.138-.05c-.03-.007-.055-.015-.06-.02A27.8 27.8 0 0 1 2.27.666l.86.002.005.638-.098.137-.07.1-.477.668-.242.34-.267.346c-.005 0-.02-.014-.033-.03z" fill="#f5b118"/><path d="M.63 2.073v-1.06l.334.003.385.018c.157.047.26.15.3.298a.59.59 0 0 1 0 .224c-.02.076-.058.142-.11.196s-.094.08-.172.107c-.052.02-.053.02-.314.022l-.262.004-.005.17.895 1.06.015.02H.63v-1.06zm1.634 1.05l.363-.502.38-.53.117-.16c.008-.01.01.086.01.596v.606h-.438c-.35 0-.436-.002-.43-.01z" fill="#686e93"/><path d="M.635 3.128C.633 3.125.63 2.65.63 2.072V1.02H.95l.37.01a.42.42 0 0 1 .253.153c.16.215.086.53-.15.646-.086.042-.108.045-.386.048l-.253.003v.178l.088.105.768.91.04.05c.004.007-.1.01-.518.01-.288 0-.526-.002-.528-.005zm1.63 0a.37.37 0 0 1 .034-.047l.785-1.093.043-.058v.6l-.003.6h-.43a10.96 10.96 0 0 1-.43-.004z" fill="#273170"/></svg> \ No newline at end of file diff --git a/assets/arch-x86_64-amd.svg b/assets/arch-x86_64-amd.svg new file mode 100644 index 0000000..39eaa23 --- /dev/null +++ b/assets/arch-x86_64-amd.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle cx="1.908" cy="1.908" r="1.908" opacity=".12" fill="#f9f9f9"/><path d="M1.285 1.4l-.607.59v1.15H1.92l.528-.572-1.155.018zM.735.784h2.327v2.36l-.594-.594V1.382H1.296z" paint-order="markers fill stroke" fill="#099b39"/></svg> diff --git a/assets/arch-x86_64-intel-i3.svg b/assets/arch-x86_64-intel-i3.svg new file mode 100644 index 0000000..450e32c --- /dev/null +++ b/assets/arch-x86_64-intel-i3.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle r="1.908" cy="1.908" cx="1.908" opacity=".12" fill="#f9f9f9"/><path d="M1.346 2.84c-.166-.02-.32-.06-.43-.11a.97.97 0 0 1-.233-.16C.61 2.494.55 2.384.53 2.276a.62.62 0 0 1-.007-.152.47.47 0 0 1 .019-.157c.023-.077.07-.17.117-.236a.99.99 0 0 1 .161-.174c.005 0 .007.012.007.036 0 .033-.002.038-.024.063a.71.71 0 0 0-.168.509c.03.24.214.4.54.466.13.026.188.03.384.03s.27-.006.422-.03a2.63 2.63 0 0 0 .727-.223L2.8 2.36a.59.59 0 0 1 .003.105l-.002.107-.06.03c-.224.11-.534.197-.828.235a4.28 4.28 0 0 1-.568.005zm.9-.583c-.053-.008-.104-.036-.142-.077-.048-.052-.07-.117-.07-.21 0-.124.054-.22.15-.266.035-.017.045-.02.102-.02.08-.003.116.01.165.054.057.052.086.128.086.225v.054h-.36l.004.022c.006.033.02.056.05.076.02.015.033.018.066.02.047.003.098-.013.12-.038l.014-.015.043.04c.023.022.043.043.044.046.003.013-.07.063-.11.075-.047.014-.113.02-.16.012zm.144-.366c-.01-.054-.048-.09-.102-.09s-.098.038-.107.09l-.004.022h.217zm-1.378.358c-.03-.01-.067-.045-.08-.075-.012-.025-.013-.04-.015-.177l.002-.23.004-.08h.142v.568H1.05c-.01 0-.026-.004-.037-.008zm.18-.28v-.28h.16c.178 0 .2.003.24.036.014.01.03.033.04.053l.017.034.005.44-.147-.003-.005-.415-.016-.012c-.014-.01-.024-.012-.084-.012h-.068v.443H1.19zm.66.27a.16.16 0 0 1-.086-.088c-.01-.026-.01-.065-.01-.324v-.295l.14.003.005.153h.107v.12h-.108v.146c0 .175-.002.17.063.173l.042.002.005.123h-.064a.32.32 0 0 1-.091-.01zm1.005-.066V2.1l.037-.024c.02-.013.057-.044.082-.07.128-.127.18-.29.143-.44-.053-.208-.244-.35-.544-.406-.213-.04-.455-.035-.71.012a2.59 2.59 0 0 0-.647.201l-.09.04c-.003 0-.005-.013-.005-.028v-.028l.063-.034a2.43 2.43 0 0 1 .902-.277 2.81 2.81 0 0 1 .483.006c.26.04.44.123.565.255a.51.51 0 0 1 .14.363c0 .23-.14.446-.36.558l-.052.023c-.004 0-.006-.028-.006-.076zm-.13.065c-.04-.01-.075-.037-.097-.078-.01-.02-.01-.04-.01-.362v-.34h.144l.002.393c.002.438.006.398-.037.388zM.92 1.548v-.07h.144v.138H.92zm1.957.012c-.035-.03-.027-.08.015-.096.026-.01.046-.006.065.017.024.028.016.07-.016.086-.024.012-.044.01-.064-.007zm.05-.001c.005-.003 0-.01-.01-.022L2.9 1.518v.016c-.01.016-.012.002-.012-.016v-.033h.02c.023 0 .033.013.023.03-.005.007-.004.014.003.024l.01.014.008-.014c.01-.02.01-.03-.004-.05s-.043-.02-.064-.006-.02.05.002.068.03.015.042.008zm-.004-.055c-.02-.006-.024-.003-.024 0 .02.006.024.003.024 0z" fill="#abd0e7"/><path d="M1.42 2.845c-.195-.016-.39-.063-.51-.122-.11-.054-.225-.142-.28-.22-.035-.048-.076-.137-.092-.197-.02-.07-.023-.214-.01-.284.012-.062.05-.16.078-.213.033-.058.103-.148.16-.205l.05-.05v.037c0 .037 0 .038-.038.087-.064.084-.103.165-.136.28-.013.044-.014.063-.014.147 0 .09 0 .098.02.15.01.03.03.07.04.092.027.046.095.12.143.153.1.07.24.12.42.148.1.016.366.02.507.01.208-.015.483-.07.665-.135l.14-.05a1.6 1.6 0 0 0 .142-.065l.097-.05v.208l-.035.018c-.262.132-.635.23-.972.257a4.63 4.63 0 0 1-.378.002zM1.02 2.25c-.036-.01-.06-.03-.078-.062l-.017-.028-.005-.464h.144v.28c0 .316.005.288-.046.273zm1.2.001c-.11-.03-.17-.11-.182-.24-.002-.023-.002-.063.002-.088.014-.103.06-.172.142-.212.04-.02.05-.023.103-.025s.063 0 .092.01c.098.037.152.124.158.26l.003.064h-.362l.003.025c.004.028.016.047.048.073.02.016.028.018.066.02.05.003.093-.01.118-.032l.017-.015.016.012c.01.007.03.025.045.04l.03.03-.033.028c-.05.042-.088.054-.17.056-.044.001-.078-.001-.096-.006zm.173-.352c0-.026-.022-.064-.047-.08s-.08-.02-.107-.005c-.03.016-.06.062-.06.093 0 .012.003.012.108.012h.108zm-1.202.076v-.278h.172c.194 0 .2.001.24.046s.044.056.044.295v.212l-.14.003-.005-.42-.014-.01c-.012-.01-.024-.01-.088-.01h-.074v.443H1.19zm.69.274c-.06-.014-.098-.046-.116-.096-.012-.03-.012-.045-.012-.322v-.29h.144v.156h.108L2 1.806l-.105.003v.148c0 .147 0 .148.014.16s.02.013.054.013h.04L2 2.248l-.054.001c-.03 0-.06 0-.066-.002zm.978-.073v-.077l.03-.02c.043-.028.115-.097.146-.14.036-.05.072-.128.084-.18.013-.06.01-.17-.008-.218a.48.48 0 0 0-.242-.278 1.06 1.06 0 0 0-.361-.113c-.1-.016-.396-.016-.505 0-.31.044-.608.13-.837.246l-.045.02c-.002 0-.003-.012-.003-.026V1.36l.07-.04c.253-.14.554-.234.88-.275a2.96 2.96 0 0 1 .475.003c.204.03.354.082.478.164.052.035.13.112.163.16a.56.56 0 0 1 .019.562.68.68 0 0 1-.279.283l-.05.025-.017.006v-.077zm-.125.065a.14.14 0 0 1-.106-.082c-.006-.015-.008-.087-.008-.357v-.338h.144v.388l-.005.39c-.002.001-.014.001-.025-.002zM.92 1.545V1.48h.144v.132H.92zm1.967.02c-.05-.028-.03-.105.025-.105.028 0 .046.013.054.038.016.048-.035.09-.08.067zm.06-.018c.027-.027.014-.07-.024-.08-.03-.007-.06.026-.054.058s.052.047.078.02zm-.057-.033c0-.03 0-.03.017-.03s.035.014.025.027c-.005.006-.005.012 0 .02-.016-.006-.02-.012-.024-.012-.006.02-.01.024-.012.024s-.006-.013-.006-.03zm.035-.01c-.017-.007-.023-.004-.023.002 0 .01.02.01.023-.002z" fill="#509dcf"/><path d="M1.448 2.845l-.11-.01c-.23-.028-.393-.08-.532-.173-.145-.096-.23-.21-.267-.365a.82.82 0 0 1-.012-.236c.022-.156.1-.305.23-.437l.06-.063.002.035c.002.033 0 .036-.024.064-.06.07-.12.184-.15.293-.02.07-.022.204-.006.27.06.246.29.392.69.437.086.01.35.01.463 0 .196-.017.465-.074.64-.135l.14-.05c.024-.008.085-.036.136-.062l.092-.047v.203l-.04.02c-.363.182-.873.282-1.312.257zm.784-.595c-.09-.017-.157-.086-.183-.183-.012-.045-.012-.143 0-.19.025-.097.088-.163.18-.186s.174-.002.234.064c.045.05.066.105.072.197l.004.06h-.36v.022c0 .033.022.067.057.088.026.016.035.018.072.018.045 0 .095-.017.114-.04.01-.01.012-.01.054.028l.044.04-.022.02c-.066.058-.165.08-.264.062zm.162-.355c-.006-.063-.062-.107-.125-.097-.047.008-.092.057-.092.102v.022l.218-.003zm-1.39.346C.978 2.23.953 2.206.937 2.176c-.012-.023-.013-.03-.015-.253l-.002-.23h.144v.556h-.02c-.01 0-.03-.004-.04-.01zm.185-.272v-.275l.175.002c.168.002.176.002.2.015.014.007.033.023.043.036.035.043.037.055.037.287v.21h-.137V1.84l-.014-.017c-.014-.017-.016-.017-.09-.02l-.075-.002v.444H1.19zm.65.26c-.036-.017-.052-.033-.07-.073-.014-.03-.014-.03-.015-.324l-.002-.295h.138v.156h.108v.107l-.052.002-.052.002-.002.138.002.15c.007.028.024.037.066.037h.04v.114h-.064c-.055 0-.07-.002-.094-.014zm1.02-.06c0-.07 0-.074.014-.08.03-.014.11-.083.145-.13.043-.055.086-.137.102-.196a.47.47 0 0 0-.003-.215c-.06-.192-.216-.317-.482-.382-.118-.03-.17-.034-.374-.034l-.272.01c-.28.04-.572.123-.772.22L1.15 1.4l-.03.014v-.05l.067-.037c.265-.147.57-.24.902-.278.103-.01.35-.01.443.003.387.053.63.205.712.443a.73.73 0 0 1 .022.102c.01.075.008.13-.013.218s-.105.23-.18.3c-.057.052-.18.132-.204.132-.005 0-.007-.02-.007-.074zm-.15.06a.12.12 0 0 1-.059-.041c-.03-.038-.03-.035-.03-.393v-.335h.137v.778h-.013c-.007 0-.023-.004-.035-.008zM.92 1.545V1.48h.144v.132H.92zm1.963.02c-.01-.004-.025-.035-.025-.053s.03-.05.05-.05c.034 0 .056.022.057.056s-.02.052-.052.052c-.012 0-.026-.002-.03-.004zm.063-.015c.02-.02.02-.054-.002-.07s-.047-.016-.065.002-.02.044-.002.065.05.022.07.003zm-.057-.03c0-.016.002-.028.005-.028 0 .02.002.024.01.024s.016.007.022.015c-.016-.013-.03-.012-.033.004zm.033-.016z" fill="#0071ba"/></svg> diff --git a/assets/arch-x86_64-intel.svg b/assets/arch-x86_64-intel.svg new file mode 100644 index 0000000..450e32c --- /dev/null +++ b/assets/arch-x86_64-intel.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle r="1.908" cy="1.908" cx="1.908" opacity=".12" fill="#f9f9f9"/><path d="M1.346 2.84c-.166-.02-.32-.06-.43-.11a.97.97 0 0 1-.233-.16C.61 2.494.55 2.384.53 2.276a.62.62 0 0 1-.007-.152.47.47 0 0 1 .019-.157c.023-.077.07-.17.117-.236a.99.99 0 0 1 .161-.174c.005 0 .007.012.007.036 0 .033-.002.038-.024.063a.71.71 0 0 0-.168.509c.03.24.214.4.54.466.13.026.188.03.384.03s.27-.006.422-.03a2.63 2.63 0 0 0 .727-.223L2.8 2.36a.59.59 0 0 1 .003.105l-.002.107-.06.03c-.224.11-.534.197-.828.235a4.28 4.28 0 0 1-.568.005zm.9-.583c-.053-.008-.104-.036-.142-.077-.048-.052-.07-.117-.07-.21 0-.124.054-.22.15-.266.035-.017.045-.02.102-.02.08-.003.116.01.165.054.057.052.086.128.086.225v.054h-.36l.004.022c.006.033.02.056.05.076.02.015.033.018.066.02.047.003.098-.013.12-.038l.014-.015.043.04c.023.022.043.043.044.046.003.013-.07.063-.11.075-.047.014-.113.02-.16.012zm.144-.366c-.01-.054-.048-.09-.102-.09s-.098.038-.107.09l-.004.022h.217zm-1.378.358c-.03-.01-.067-.045-.08-.075-.012-.025-.013-.04-.015-.177l.002-.23.004-.08h.142v.568H1.05c-.01 0-.026-.004-.037-.008zm.18-.28v-.28h.16c.178 0 .2.003.24.036.014.01.03.033.04.053l.017.034.005.44-.147-.003-.005-.415-.016-.012c-.014-.01-.024-.012-.084-.012h-.068v.443H1.19zm.66.27a.16.16 0 0 1-.086-.088c-.01-.026-.01-.065-.01-.324v-.295l.14.003.005.153h.107v.12h-.108v.146c0 .175-.002.17.063.173l.042.002.005.123h-.064a.32.32 0 0 1-.091-.01zm1.005-.066V2.1l.037-.024c.02-.013.057-.044.082-.07.128-.127.18-.29.143-.44-.053-.208-.244-.35-.544-.406-.213-.04-.455-.035-.71.012a2.59 2.59 0 0 0-.647.201l-.09.04c-.003 0-.005-.013-.005-.028v-.028l.063-.034a2.43 2.43 0 0 1 .902-.277 2.81 2.81 0 0 1 .483.006c.26.04.44.123.565.255a.51.51 0 0 1 .14.363c0 .23-.14.446-.36.558l-.052.023c-.004 0-.006-.028-.006-.076zm-.13.065c-.04-.01-.075-.037-.097-.078-.01-.02-.01-.04-.01-.362v-.34h.144l.002.393c.002.438.006.398-.037.388zM.92 1.548v-.07h.144v.138H.92zm1.957.012c-.035-.03-.027-.08.015-.096.026-.01.046-.006.065.017.024.028.016.07-.016.086-.024.012-.044.01-.064-.007zm.05-.001c.005-.003 0-.01-.01-.022L2.9 1.518v.016c-.01.016-.012.002-.012-.016v-.033h.02c.023 0 .033.013.023.03-.005.007-.004.014.003.024l.01.014.008-.014c.01-.02.01-.03-.004-.05s-.043-.02-.064-.006-.02.05.002.068.03.015.042.008zm-.004-.055c-.02-.006-.024-.003-.024 0 .02.006.024.003.024 0z" fill="#abd0e7"/><path d="M1.42 2.845c-.195-.016-.39-.063-.51-.122-.11-.054-.225-.142-.28-.22-.035-.048-.076-.137-.092-.197-.02-.07-.023-.214-.01-.284.012-.062.05-.16.078-.213.033-.058.103-.148.16-.205l.05-.05v.037c0 .037 0 .038-.038.087-.064.084-.103.165-.136.28-.013.044-.014.063-.014.147 0 .09 0 .098.02.15.01.03.03.07.04.092.027.046.095.12.143.153.1.07.24.12.42.148.1.016.366.02.507.01.208-.015.483-.07.665-.135l.14-.05a1.6 1.6 0 0 0 .142-.065l.097-.05v.208l-.035.018c-.262.132-.635.23-.972.257a4.63 4.63 0 0 1-.378.002zM1.02 2.25c-.036-.01-.06-.03-.078-.062l-.017-.028-.005-.464h.144v.28c0 .316.005.288-.046.273zm1.2.001c-.11-.03-.17-.11-.182-.24-.002-.023-.002-.063.002-.088.014-.103.06-.172.142-.212.04-.02.05-.023.103-.025s.063 0 .092.01c.098.037.152.124.158.26l.003.064h-.362l.003.025c.004.028.016.047.048.073.02.016.028.018.066.02.05.003.093-.01.118-.032l.017-.015.016.012c.01.007.03.025.045.04l.03.03-.033.028c-.05.042-.088.054-.17.056-.044.001-.078-.001-.096-.006zm.173-.352c0-.026-.022-.064-.047-.08s-.08-.02-.107-.005c-.03.016-.06.062-.06.093 0 .012.003.012.108.012h.108zm-1.202.076v-.278h.172c.194 0 .2.001.24.046s.044.056.044.295v.212l-.14.003-.005-.42-.014-.01c-.012-.01-.024-.01-.088-.01h-.074v.443H1.19zm.69.274c-.06-.014-.098-.046-.116-.096-.012-.03-.012-.045-.012-.322v-.29h.144v.156h.108L2 1.806l-.105.003v.148c0 .147 0 .148.014.16s.02.013.054.013h.04L2 2.248l-.054.001c-.03 0-.06 0-.066-.002zm.978-.073v-.077l.03-.02c.043-.028.115-.097.146-.14.036-.05.072-.128.084-.18.013-.06.01-.17-.008-.218a.48.48 0 0 0-.242-.278 1.06 1.06 0 0 0-.361-.113c-.1-.016-.396-.016-.505 0-.31.044-.608.13-.837.246l-.045.02c-.002 0-.003-.012-.003-.026V1.36l.07-.04c.253-.14.554-.234.88-.275a2.96 2.96 0 0 1 .475.003c.204.03.354.082.478.164.052.035.13.112.163.16a.56.56 0 0 1 .019.562.68.68 0 0 1-.279.283l-.05.025-.017.006v-.077zm-.125.065a.14.14 0 0 1-.106-.082c-.006-.015-.008-.087-.008-.357v-.338h.144v.388l-.005.39c-.002.001-.014.001-.025-.002zM.92 1.545V1.48h.144v.132H.92zm1.967.02c-.05-.028-.03-.105.025-.105.028 0 .046.013.054.038.016.048-.035.09-.08.067zm.06-.018c.027-.027.014-.07-.024-.08-.03-.007-.06.026-.054.058s.052.047.078.02zm-.057-.033c0-.03 0-.03.017-.03s.035.014.025.027c-.005.006-.005.012 0 .02-.016-.006-.02-.012-.024-.012-.006.02-.01.024-.012.024s-.006-.013-.006-.03zm.035-.01c-.017-.007-.023-.004-.023.002 0 .01.02.01.023-.002z" fill="#509dcf"/><path d="M1.448 2.845l-.11-.01c-.23-.028-.393-.08-.532-.173-.145-.096-.23-.21-.267-.365a.82.82 0 0 1-.012-.236c.022-.156.1-.305.23-.437l.06-.063.002.035c.002.033 0 .036-.024.064-.06.07-.12.184-.15.293-.02.07-.022.204-.006.27.06.246.29.392.69.437.086.01.35.01.463 0 .196-.017.465-.074.64-.135l.14-.05c.024-.008.085-.036.136-.062l.092-.047v.203l-.04.02c-.363.182-.873.282-1.312.257zm.784-.595c-.09-.017-.157-.086-.183-.183-.012-.045-.012-.143 0-.19.025-.097.088-.163.18-.186s.174-.002.234.064c.045.05.066.105.072.197l.004.06h-.36v.022c0 .033.022.067.057.088.026.016.035.018.072.018.045 0 .095-.017.114-.04.01-.01.012-.01.054.028l.044.04-.022.02c-.066.058-.165.08-.264.062zm.162-.355c-.006-.063-.062-.107-.125-.097-.047.008-.092.057-.092.102v.022l.218-.003zm-1.39.346C.978 2.23.953 2.206.937 2.176c-.012-.023-.013-.03-.015-.253l-.002-.23h.144v.556h-.02c-.01 0-.03-.004-.04-.01zm.185-.272v-.275l.175.002c.168.002.176.002.2.015.014.007.033.023.043.036.035.043.037.055.037.287v.21h-.137V1.84l-.014-.017c-.014-.017-.016-.017-.09-.02l-.075-.002v.444H1.19zm.65.26c-.036-.017-.052-.033-.07-.073-.014-.03-.014-.03-.015-.324l-.002-.295h.138v.156h.108v.107l-.052.002-.052.002-.002.138.002.15c.007.028.024.037.066.037h.04v.114h-.064c-.055 0-.07-.002-.094-.014zm1.02-.06c0-.07 0-.074.014-.08.03-.014.11-.083.145-.13.043-.055.086-.137.102-.196a.47.47 0 0 0-.003-.215c-.06-.192-.216-.317-.482-.382-.118-.03-.17-.034-.374-.034l-.272.01c-.28.04-.572.123-.772.22L1.15 1.4l-.03.014v-.05l.067-.037c.265-.147.57-.24.902-.278.103-.01.35-.01.443.003.387.053.63.205.712.443a.73.73 0 0 1 .022.102c.01.075.008.13-.013.218s-.105.23-.18.3c-.057.052-.18.132-.204.132-.005 0-.007-.02-.007-.074zm-.15.06a.12.12 0 0 1-.059-.041c-.03-.038-.03-.035-.03-.393v-.335h.137v.778h-.013c-.007 0-.023-.004-.035-.008zM.92 1.545V1.48h.144v.132H.92zm1.963.02c-.01-.004-.025-.035-.025-.053s.03-.05.05-.05c.034 0 .056.022.057.056s-.02.052-.052.052c-.012 0-.026-.002-.03-.004zm.063-.015c.02-.02.02-.054-.002-.07s-.047-.016-.065.002-.02.044-.002.065.05.022.07.003zm-.057-.03c0-.016.002-.028.005-.028 0 .02.002.024.01.024s.016.007.022.015c-.016-.013-.03-.012-.033.004zm.033-.016z" fill="#0071ba"/></svg> diff --git a/assets/arch-xl6-esp32.svg b/assets/arch-xl6-esp32.svg new file mode 100644 index 0000000..83cc4d8 --- /dev/null +++ b/assets/arch-xl6-esp32.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle cx="1.908" cy="1.908" r="1.908" opacity=".12" fill="#f9f9f9"/><path d="M.553 1.545l.288.45.026.737 1.015.463.99-.465.012-.677.377-.547-.5-.246L2.32.95 1.812.884l-.4.09-.297.18-.164-.017-.048.14.005.087z" fill="#fff" paint-order="markers fill stroke"/><path d="M1.84 3.257c-.003-.003-.23-.115-.506-.25L.823 2.75C.812 2.74.812 2.73.81 2.396l-.003-.343-.154-.232-.16-.245c-.01-.018-.008-.038.003-.054a1.48 1.48 0 0 1 .262-.13c.02 0 .043.02.048.04.01.035-.004.047-.1.094l-.09.046c-.003.002.263.403.267.403a15.77 15.77 0 0 0 .292-.141L1.5 1.66c.212-.18.523-.183.742-.007.04.03.094.06.332.18l.292.135c.004-.004.06-.083.124-.175l.192-.277-.15-.073-.157-.085c-.02-.036.003-.078.042-.078a2.64 2.64 0 0 1 .387.188c.01.02.007.044-.015.077l-.19.274-.168.24-.003.343-.012.35c-.005.005-.24.12-.52.26l-.554.245zm-.02-.41v-.274l-.016-.01c-.037-.02-.88-.432-.885-.432s-.004.124-.004.277l.002.277.447.22.452.217c.002-.001.004-.125.004-.276zm.423-.55l.516-.254-.27-.136-.316-.173c-.088-.07-.18-.103-.295-.103a.45.45 0 0 0-.311.114c-.024.02-.09.056-.306.162l-.275.138a79.84 79.84 0 0 0 .882.435 24.5 24.5 0 0 0 .377-.183zm-.423.052c-.075-.016-.14-.068-.172-.137-.018-.038-.02-.044-.02-.108s0-.07.02-.11a.25.25 0 0 1 .397-.074c.15.14.077.392-.125.43-.04.007-.06.007-.1-.002zm.117-.115c.087-.045.11-.15.046-.22-.04-.047-.092-.063-.147-.047-.11.032-.142.174-.055.248.044.038.107.046.156.02zm.5-.547c-.1-.032-.152-.127-.127-.225.012-.046.017-.04-.082-.088a.79.79 0 0 0-.232-.074c-.068-.01-.222-.008-.286.007-.148.033-.265.094-.397.208-.043.037-.062.04-.09.014-.012-.012-.017-.023-.017-.037s.007-.026.046-.064c.11-.107.268-.188.437-.225.083-.018.273-.02.356-.003a.97.97 0 0 1 .308.12l.043.027.033-.01c.046-.015.102-.01.143.012.13.07.13.253 0 .323-.025.014-.04.017-.076.02-.024.001-.05 0-.06-.003zm.1-.122c.034-.032.033-.08 0-.112-.015-.015-.023-.017-.05-.017s-.034.003-.05.017c-.036.034-.036.078-.002.11.02.02.03.022.06.02.018-.002.03-.007.043-.02zm-1.57-.14a.19.19 0 0 1-.116-.097C.844 1.305.84 1.29.84 1.247s.002-.056.017-.084c.02-.034.05-.065.085-.08s.11-.018.146-.002l.03.013.056-.038a1.26 1.26 0 0 1 .528-.201c.076-.01.25-.012.325-.003.24.03.474.13.656.283.063.053.094.085.1.103.01.033-.03.073-.064.064-.008-.002-.04-.026-.07-.054-.08-.073-.15-.12-.252-.172S2.21.993 2.103.97c-.09-.02-.27-.026-.363-.015a1.2 1.2 0 0 0-.498.181c-.05.033-.052.035-.047.052.003.01.005.04.005.065 0 .055-.015.09-.054.128-.044.042-.122.062-.175.044zm.085-.11c.07-.038.044-.142-.036-.142s-.104.108-.033.143c.025.012.044.012.07-.001z" fill="#f82d28"/><path d="M1.84 3.257c-.003-.003-.23-.115-.506-.25L.823 2.75C.812 2.74.812 2.73.81 2.396l-.003-.343-.154-.232-.16-.245c-.01-.018-.008-.038.003-.054a1.48 1.48 0 0 1 .262-.13c.02 0 .043.02.048.04.01.035-.004.047-.1.094l-.09.046c-.003.002.262.403.267.403s.06-.027.128-.06l.288-.14.157-.07c-.016.03-.003.064.032.08.012.005-.016.02-.245.132l-.258.13a24.17 24.17 0 0 0 .442.219l.442.216.374-.183.445-.218.07-.035-.255-.128c-.255-.128-.255-.128-.237-.135.03-.013.042-.044.027-.08-.003-.007.09.037.28.13l.29.134c.004-.004.06-.083.124-.175l.192-.277-.15-.073-.157-.085c-.02-.036.003-.078.042-.078a2.64 2.64 0 0 1 .387.188c.01.02.007.044-.015.077l-.19.274-.168.24-.003.343-.012.35c-.005.005-.24.12-.52.26l-.554.245zm-.02-.41v-.274l-.016-.01c-.037-.02-.88-.432-.885-.432s-.004.124-.004.277l.002.277.447.22.452.217c.002-.001.004-.125.004-.276zm.55.06l.448-.22a1.6 1.6 0 0 0 .007-.279l-.004-.273c-.002 0-.205.1-.45.22l-.447.22-.002.275.004.275c.003 0 .203-.097.446-.216z" fill="#101b28"/></svg> diff --git a/assets/artifact.svg b/assets/artifact.svg new file mode 100644 index 0000000..2efd25d --- /dev/null +++ b/assets/artifact.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.886 3.886" height="14.687" width="14.687"><circle cx="1.943" cy="1.943" r="1.943"/><g fill="#fff"><path d="M1.348.642h1.24V1.96h.457l-1.1.952L.83 1.96h.516z" paint-order="markers fill stroke"/><path d="M.96 2.903h1.933v.272H.96z" paint-order="markers fill stroke"/></g></svg> \ No newline at end of file diff --git a/assets/builder-instance.png b/assets/builder-instance.png new file mode 100644 index 0000000..8beb672 Binary files /dev/null and b/assets/builder-instance.png differ diff --git a/assets/builder.png b/assets/builder.png new file mode 100644 index 0000000..6585d31 Binary files /dev/null and b/assets/builder.png differ diff --git a/assets/builder.svg b/assets/builder.svg new file mode 100644 index 0000000..18b6949 --- /dev/null +++ b/assets/builder.svg @@ -0,0 +1,3918 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + sodipodi:docname="builder.svg" + inkscape:version="1.0beta2 (unknown)" + id="svg5724" + version="1.1" + viewBox="0 0 15.6447 9.72857" + height="9.72857mm" + width="15.6447mm"> +<defs + id="defs5718"> +<inkscape:path-effect + effect="perspective-envelope" + up_left_point="-577.413,-113.981" + up_right_point="-571.764,-113.974" + down_left_point="-577.376,-112.551" + down_right_point="-571.923,-112.554" + id="path-effect9673" + is_visible="true" + lpeversion="1" + deform_type="perspective" + horizontal_mirror="false" + vertical_mirror="false" + overflow_perspective="false" /> +<inkscape:path-effect + overflow_perspective="false" + vertical_mirror="false" + horizontal_mirror="false" + deform_type="perspective" + lpeversion="1" + is_visible="true" + id="path-effect9673-2" + down_right_point="-572.283,-112.605" + down_left_point="-577.379,-112.61" + up_right_point="-572.073,-113.934" + up_left_point="-577.4,-113.942" + effect="perspective-envelope" /> +<inkscape:path-effect + effect="perspective-envelope" + up_left_point="-576.97,-114.209" + up_right_point="-572.024,-114.225" + down_left_point="-576.899,-112.863" + down_right_point="-572.177,-112.855" + id="path-effect9673-2-8" + is_visible="true" + lpeversion="1" + deform_type="perspective" + horizontal_mirror="false" + vertical_mirror="false" + overflow_perspective="false" /> +</defs> +<sodipodi:namedview + inkscape:window-maximized="1" + inkscape:window-y="0" + inkscape:window-x="0" + inkscape:window-height="2089" + inkscape:window-width="3840" + showgrid="false" + inkscape:document-rotation="0" + inkscape:current-layer="layer1" + inkscape:document-units="mm" + inkscape:cy="85.8157" + inkscape:cx="43.0694" + inkscape:zoom="11.2739" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" /> +<metadata + id="metadata5721"> +<rdf:RDF> +<cc:Work + rdf:about=""> +<dc:format>image/svg+xml</dc:format> +<dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> +<dc:title></dc:title> +</cc:Work> +</rdf:RDF> +</metadata> +<g + transform="translate(204.37,173.442)" + id="layer1" + inkscape:groupmode="layer" + inkscape:label="Layer 1"> +<path + inkscape:connector-curvature="0" + id="path8375-2" + d="m -202.742,-165.815 1.822,-2.778 9.144,0.07 1.24,2.709 z" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.069044;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<path + inkscape:connector-curvature="0" + id="path8375-9" + d="m -203.145,-168.05 1.909,-2.912 9.582,0.08 1.3,2.839 z" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.072354;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<path + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + d="m -203.265,-170.386 1.979,-3.018 9.932,0.08 1.348,2.943 z" + id="path8375" + inkscape:connector-curvature="0" /> +<path + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + d="m -204.326,-170.463 0.323,2.157 h 14.784 l 0.448,-2.127 z" + id="path8373" + inkscape:connector-curvature="0" /> +<path + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + d="m -203.701,-169.945 c 0.171,-0.09 0.163,-0.06 0.319,0.03 -0.131,0.166 -0.158,0.288 -0.146,0.533 l 0.108,0.843 0.05,0.08 v -0.08 l 0.119,-0.177 c 0.193,-0.04 0.204,0.131 0.208,0.222 l -0.148,0.217 c -0.204,0.211 -0.326,0.147 -0.426,-0.03 -0.126,-0.365 -0.142,-0.76 -0.195,-1.141 -0.01,-0.192 0.03,-0.323 0.109,-0.495 z" + id="path8377" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccccc" /> +<path + sodipodi:nodetypes="cccccccccccc" + inkscape:connector-curvature="0" + id="path8377-5" + d="m -189.327,-169.97 c -0.171,-0.09 -0.163,-0.06 -0.319,0.03 0.131,0.166 0.158,0.288 0.146,0.533 l -0.108,0.843 -0.05,0.08 v -0.08 l -0.119,-0.177 c -0.193,-0.04 -0.204,0.131 -0.208,0.222 l 0.148,0.217 c 0.204,0.211 0.326,0.147 0.426,-0.03 0.126,-0.365 0.142,-0.76 0.195,-1.141 0.01,-0.192 -0.03,-0.323 -0.109,-0.495 z" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<path + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + d="m -202.841,-169.787 0.129,1.04 h 6.353 l -0.03,-1.047 z" + id="path8394" + inkscape:connector-curvature="0" /> +<path + inkscape:connector-curvature="0" + id="path8373-6" + d="m -203.942,-168.059 0.306,2.04 h 13.981 l 0.423,-2.012 z" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.070928;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<path + sodipodi:nodetypes="cccccccccccc" + inkscape:connector-curvature="0" + id="path8377-3" + d="m -203.351,-167.569 c 0.162,-0.08 0.154,-0.06 0.302,0.03 -0.124,0.157 -0.15,0.273 -0.138,0.504 l 0.102,0.798 0.05,0.08 v -0.08 l 0.113,-0.168 c 0.182,-0.04 0.193,0.124 0.196,0.21 l -0.14,0.205 c -0.192,0.2 -0.308,0.139 -0.402,-0.03 -0.12,-0.345 -0.135,-0.719 -0.185,-1.079 0,-0.182 0.03,-0.306 0.103,-0.468 z" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.070928;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<path + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.070928;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + d="m -189.757,-167.593 c -0.162,-0.08 -0.154,-0.06 -0.303,0.03 0.125,0.157 0.15,0.272 0.139,0.504 l -0.102,0.797 -0.05,0.08 v -0.08 l -0.113,-0.168 c -0.182,-0.04 -0.193,0.124 -0.197,0.21 l 0.14,0.206 c 0.193,0.199 0.309,0.139 0.403,-0.03 0.119,-0.345 0.135,-0.718 0.185,-1.079 0,-0.181 -0.03,-0.305 -0.103,-0.468 z" + id="path8377-5-9" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccccc" /> +<path + inkscape:connector-curvature="0" + id="path8394-4" + d="m -202.538,-167.42 0.122,0.984 h 6.008 l -0.03,-0.99 z" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.070928;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<path + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.067182;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + d="m -203.59,-165.827 0.289,1.933 h 13.244 l 0.4,-1.906 z" + id="path8373-6-2" + inkscape:connector-curvature="0" /> +<path + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.067182;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + d="m -203.03,-165.363 c 0.153,-0.08 0.146,-0.05 0.285,0.03 -0.117,0.149 -0.141,0.258 -0.13,0.478 l 0.1,0.755 0.05,0.07 v -0.07 l 0.107,-0.159 c 0.173,-0.04 0.183,0.118 0.186,0.199 l -0.132,0.195 c -0.183,0.189 -0.292,0.131 -0.382,-0.03 -0.113,-0.327 -0.127,-0.681 -0.175,-1.022 0,-0.172 0.03,-0.29 0.1,-0.444 z" + id="path8377-3-9" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccccc" /> +<path + sodipodi:nodetypes="cccccccccccc" + inkscape:connector-curvature="0" + id="path8377-5-9-3" + d="m -190.155,-165.385 c -0.154,-0.08 -0.146,-0.05 -0.286,0.03 0.117,0.149 0.142,0.258 0.131,0.477 l -0.101,0.755 -0.04,0.07 v -0.07 l -0.107,-0.158 c -0.172,-0.04 -0.182,0.117 -0.186,0.199 l 0.132,0.194 c 0.184,0.189 0.293,0.132 0.383,-0.03 0.113,-0.327 0.127,-0.68 0.174,-1.022 0,-0.172 -0.03,-0.289 -0.1,-0.443 z" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.067182;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<path + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.067182;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + d="m -202.26,-165.221 0.116,0.932 h 5.69 l -0.03,-0.938 z" + id="path8394-4-9" + inkscape:connector-curvature="0" /> +<g + transform="translate(381.608,-56.0747)" + style="stroke:#000000" + id="g9671" + inkscape:path-effect="#path-effect9673"> +<g + style="stroke:#000000" + transform="matrix(0.286023,0,0,0.286023,-409.326,-74.9126)" + id="g8702"> +<g + style="stroke:#000000" + id="g8491" + transform="matrix(0.582162,0,0,0.582162,-208.843,-64.0062)"> +<circle + d="m -649.237,-119.135 c 0,0.406 -0.285,0.734 -0.655,0.734 -0.369,0 -0.678,-0.328 -0.688,-0.734 -0.01,-0.408 0.282,-0.739 0.654,-0.739 0.372,0 0.68,0.332 0.689,0.739" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -649.321,-119.176 c 0,0.372 -0.26,0.673 -0.599,0.673 -0.34,0 -0.622,-0.301 -0.632,-0.673 -0,-0.374 0.259,-0.678 0.6,-0.678 0.341,0 0.624,0.304 0.631,0.678" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-68.5133)" + id="g8491-8"> +<circle + d="m -652.724,-116.166 c 0,0.421 -0.303,0.762 -0.679,0.762 -0.376,-0 -0.684,-0.342 -0.688,-0.764 -0,-0.423 0.301,-0.767 0.68,-0.766 0.378,0 0.686,0.345 0.687,0.768" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-5" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -652.808,-116.209 c 10e-4,0.387 -0.277,0.699 -0.623,0.699 -0.345,-10e-4 -0.627,-0.314 -0.63,-0.701 -0,-0.388 0.276,-0.703 0.623,-0.703 0.347,0 0.629,0.317 0.63,0.705" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-0" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-66.2597)" + id="g8491-9"> +<circle + d="m -651,-117.628 c 0,0.413 -0.294,0.747 -0.667,0.747 -0.373,0 -0.681,-0.334 -0.688,-0.748 -0,-0.415 0.292,-0.753 0.667,-0.752 0.375,0 0.683,0.338 0.688,0.753" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-6" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -651.084,-117.67 c 0,0.379 -0.269,0.686 -0.611,0.686 -0.342,-0 -0.625,-0.308 -0.631,-0.687 -0,-0.381 0.267,-0.69 0.611,-0.69 0.344,0 0.627,0.31 0.631,0.691" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-3" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-64.0061)" + id="g8491-85"> +<circle + d="m -654.451,-119.135 c -0,0.406 -0.303,0.733 -0.672,0.733 -0.369,0 -0.667,-0.327 -0.667,-0.733 0,-0.406 0.301,-0.737 0.672,-0.737 0.371,0 0.669,0.331 0.667,0.737" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-61" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -654.533,-119.176 c -0,0.372 -0.278,0.673 -0.616,0.673 -0.338,0 -0.612,-0.301 -0.612,-0.673 0,-0.373 0.276,-0.676 0.617,-0.676 0.34,0 0.613,0.303 0.611,0.676" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-1" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-66.2597)" + id="g8491-5"> +<circle + d="m -652.718,-117.627 c 10e-4,0.413 -0.3,0.747 -0.672,0.747 -0.373,0 -0.678,-0.334 -0.681,-0.748 -0,-0.415 0.298,-0.752 0.673,-0.752 0.375,0 0.679,0.338 0.68,0.753" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-9" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -652.801,-117.669 c 0,0.379 -0.275,0.686 -0.616,0.686 -0.342,-10e-4 -0.622,-0.307 -0.625,-0.687 -0,-0.38 0.273,-0.69 0.617,-0.689 0.344,0 0.623,0.309 0.624,0.69" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-8" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-61.7525)" + id="g8491-4"> +<circle + d="m -650.947,-120.687 c 0,0.398 -0.288,0.719 -0.654,0.72 -0.365,0 -0.668,-0.322 -0.674,-0.72 -0,-0.399 0.286,-0.724 0.654,-0.724 0.368,0 0.67,0.325 0.674,0.724" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-81" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -651.029,-120.728 c 0,0.365 -0.264,0.661 -0.599,0.661 -0.336,0 -0.613,-0.295 -0.619,-0.66 -0,-0.366 0.262,-0.664 0.6,-0.664 0.337,0 0.614,0.297 0.618,0.663" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-03" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-64.0061)" + id="g8491-0"> +<circle + d="m -650.973,-119.135 c 0,0.406 -0.291,0.733 -0.66,0.734 -0.37,0 -0.675,-0.328 -0.682,-0.734 -0,-0.407 0.289,-0.738 0.661,-0.738 0.371,0 0.676,0.331 0.681,0.738" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-4" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -651.056,-119.176 c 0,0.372 -0.267,0.673 -0.605,0.673 -0.339,0 -0.619,-0.301 -0.625,-0.673 -0,-0.374 0.265,-0.677 0.605,-0.677 0.341,0 0.621,0.304 0.625,0.677" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-4" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-68.5133)" + id="g8491-44"> +<circle + d="m -651.028,-116.169 c 0,0.422 -0.296,0.763 -0.673,0.763 -0.377,-10e-4 -0.688,-0.342 -0.695,-0.764 -0,-0.424 0.294,-0.768 0.673,-0.767 0.379,0 0.69,0.345 0.695,0.768" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-7" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -651.112,-116.212 c 0,0.388 -0.272,0.7 -0.617,0.7 -0.346,-0 -0.631,-0.314 -0.638,-0.701 -0,-0.388 0.27,-0.704 0.618,-0.703 0.347,0 0.632,0.316 0.637,0.704" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-6" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-208.842,-66.2597)" + id="g8491-3"> +<circle + d="m -649.286,-117.63 c 0,0.414 -0.287,0.749 -0.66,0.748 -0.373,0 -0.685,-0.334 -0.695,-0.748 -0.01,-0.416 0.285,-0.753 0.66,-0.753 0.376,0 0.687,0.338 0.695,0.753" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-1" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -649.37,-117.672 c 0,0.38 -0.263,0.687 -0.605,0.687 -0.343,-0 -0.628,-0.308 -0.638,-0.687 -0.01,-0.381 0.261,-0.691 0.606,-0.69 0.344,0 0.63,0.31 0.637,0.69" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-7" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-61.7525)" + id="g8491-59"> +<circle + d="m -652.706,-120.688 c 10e-4,0.398 -0.294,0.719 -0.659,0.719 -0.366,10e-4 -0.665,-0.321 -0.668,-0.719 -0,-0.399 0.292,-0.723 0.66,-0.723 0.367,0 0.666,0.324 0.667,0.723" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-62" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -652.787,-120.728 c 0,0.364 -0.27,0.66 -0.605,0.66 -0.335,0 -0.609,-0.295 -0.612,-0.66 -0,-0.366 0.268,-0.664 0.605,-0.664 0.337,0 0.611,0.298 0.612,0.664" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-17" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-61.7525)" + id="g8491-857"> +<circle + d="m -654.466,-120.689 c -0,0.398 -0.3,0.719 -0.665,0.719 -0.365,0 -0.661,-0.321 -0.661,-0.718 0,-0.399 0.299,-0.724 0.666,-0.724 0.367,0 0.663,0.324 0.66,0.723" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-41" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -654.547,-120.729 c -0,0.364 -0.275,0.659 -0.61,0.66 -0.335,0 -0.606,-0.295 -0.606,-0.66 0,-0.366 0.274,-0.663 0.611,-0.663 0.336,0 0.608,0.297 0.605,0.663" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-85" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-66.2597)" + id="g8491-97"> +<circle + d="m -654.436,-117.626 c -0,0.413 -0.307,0.747 -0.679,0.747 -0.372,0 -0.674,-0.334 -0.673,-0.748 0,-0.414 0.304,-0.752 0.678,-0.751 0.375,0 0.676,0.337 0.674,0.752" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-53" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -654.519,-117.668 c -0,0.379 -0.281,0.686 -0.622,0.685 -0.342,0 -0.618,-0.306 -0.618,-0.686 0,-0.38 0.279,-0.689 0.623,-0.689 0.343,0 0.619,0.31 0.617,0.69" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-88" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-68.5133)" + id="g8491-31"> +<circle + d="m -654.421,-116.164 c -0,0.422 -0.309,0.762 -0.685,0.762 -0.376,-0 -0.681,-0.342 -0.681,-0.763 0,-0.423 0.308,-0.767 0.686,-0.767 0.378,0 0.683,0.345 0.68,0.768" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-89" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -654.504,-116.207 c -0,0.387 -0.284,0.699 -0.629,0.699 -0.345,0 -0.624,-0.313 -0.624,-0.7 10e-4,-0.388 0.282,-0.703 0.629,-0.703 0.347,0 0.626,0.316 0.624,0.704" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-64" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-64.0061)" + id="g8491-33"> +<circle + d="m -652.712,-119.135 c 10e-4,0.406 -0.297,0.733 -0.666,0.733 -0.369,0 -0.671,-0.327 -0.674,-0.733 -0,-0.407 0.295,-0.738 0.666,-0.738 0.372,0 0.673,0.331 0.674,0.738" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-3" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -652.794,-119.176 c 10e-4,0.372 -0.272,0.673 -0.611,0.673 -0.338,0 -0.615,-0.301 -0.618,-0.673 -0,-0.373 0.271,-0.677 0.611,-0.676 0.34,0 0.617,0.303 0.618,0.676" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-86" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-208.842,-68.5133)" + id="g8491-04"> +<circle + d="m -649.335,-116.171 c 0,0.422 -0.29,0.763 -0.667,0.763 -0.376,-0 -0.691,-0.342 -0.702,-0.764 -0.01,-0.424 0.288,-0.768 0.667,-0.768 0.379,0 0.694,0.345 0.702,0.769" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-88" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -649.419,-116.214 c 0,0.387 -0.266,0.7 -0.612,0.7 -0.346,-0 -0.634,-0.314 -0.644,-0.701 -0.01,-0.389 0.264,-0.704 0.612,-0.704 0.347,10e-4 0.636,0.317 0.644,0.705" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-89" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-208.842,-61.7525)" + id="g8491-7"> +<circle + d="m -649.191,-120.686 c 0,0.398 -0.282,0.72 -0.648,0.72 -0.366,0 -0.671,-0.322 -0.681,-0.72 -0.01,-0.399 0.28,-0.724 0.648,-0.724 0.368,0 0.673,0.324 0.681,0.724" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-76" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -649.273,-120.727 c 0,0.365 -0.258,0.661 -0.594,0.661 -0.336,0 -0.616,-0.295 -0.625,-0.66 -0.01,-0.367 0.256,-0.664 0.594,-0.665 0.338,0 0.618,0.298 0.625,0.664" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-43" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +</g> +<g + style="stroke:#000000" + transform="matrix(0.286023,0,0,0.286023,-406.748,-74.9126)" + id="g8702-0"> +<g + style="stroke:#000000" + transform="matrix(0.582162,0,0,0.582162,-208.843,-64.0062)" + id="g8491-30"> +<circle + d="m -656.192,-119.134 c -0,0.405 -0.31,0.732 -0.678,0.732 -0.369,0 -0.664,-0.327 -0.66,-0.733 0,-0.406 0.307,-0.737 0.678,-0.737 0.37,0 0.666,0.331 0.66,0.738" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-92" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -656.274,-119.176 c -0,0.372 -0.284,0.672 -0.621,0.673 -0.338,0 -0.609,-0.301 -0.606,-0.673 0,-0.373 0.282,-0.676 0.622,-0.676 0.34,0 0.611,0.304 0.605,0.676" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-5" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + id="g8491-8-4" + transform="matrix(0.582163,0,0,0.582163,-204.335,-68.5133)"> +<circle + d="m -659.525,-116.157 c -0.01,0.421 -0.329,0.761 -0.704,0.761 -0.375,-0 -0.67,-0.341 -0.658,-0.762 0.01,-0.423 0.327,-0.766 0.704,-0.766 0.377,0 0.672,0.345 0.658,0.767" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-5-0" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -659.607,-116.2 c -0.01,0.386 -0.302,0.698 -0.646,0.698 -0.344,0 -0.614,-0.313 -0.603,-0.699 0.01,-0.388 0.299,-0.702 0.645,-0.702 0.346,0 0.616,0.316 0.604,0.703" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-0-5" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-9-9" + transform="matrix(0.582163,0,0,0.582163,-206.589,-66.2597)"> +<circle + d="m -657.879,-117.623 c -0.01,0.412 -0.319,0.746 -0.691,0.746 -0.371,0 -0.667,-0.334 -0.659,-0.747 0,-0.415 0.317,-0.751 0.691,-0.751 0.374,0 0.669,0.337 0.659,0.752" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-6-4" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -657.961,-117.665 c -0,0.378 -0.292,0.685 -0.633,0.684 -0.341,0 -0.612,-0.306 -0.605,-0.685 0,-0.38 0.291,-0.689 0.634,-0.689 0.343,0 0.613,0.31 0.604,0.69" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-3-6" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-85-9" + transform="matrix(0.582163,0,0,0.582163,-202.082,-64.0061)"> +<circle + d="m -661.429,-119.134 c -0.02,0.405 -0.328,0.732 -0.695,0.732 -0.368,0 -0.654,-0.327 -0.639,-0.732 0.01,-0.406 0.326,-0.736 0.696,-0.736 0.369,0 0.655,0.33 0.638,0.736" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-61-2" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -661.509,-119.175 c -0.01,0.371 -0.301,0.671 -0.638,0.671 -0.337,0 -0.599,-0.3 -0.585,-0.671 0.01,-0.372 0.299,-0.675 0.638,-0.675 0.338,0 0.601,0.303 0.585,0.675" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-1-2" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-5-4" + transform="matrix(0.582163,0,0,0.582163,-204.335,-66.2597)"> +<circle + d="m -659.604,-117.622 c -0.01,0.413 -0.326,0.746 -0.697,0.746 -0.371,0 -0.663,-0.334 -0.652,-0.747 0.01,-0.414 0.324,-0.751 0.697,-0.75 0.374,0 0.665,0.337 0.652,0.751" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-9-7" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -659.686,-117.664 c -0.01,0.379 -0.298,0.684 -0.638,0.684 -0.341,0 -0.609,-0.306 -0.598,-0.685 0.01,-0.38 0.296,-0.688 0.639,-0.688 0.342,0 0.61,0.309 0.597,0.689" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-8-7" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-4-5" + transform="matrix(0.582163,0,0,0.582163,-206.589,-61.7525)"> +<circle + d="m -657.991,-120.691 c -0.01,0.398 -0.313,0.719 -0.677,0.719 -0.365,0 -0.655,-0.321 -0.647,-0.718 0,-0.399 0.31,-0.723 0.677,-0.723 0.367,0 0.656,0.324 0.647,0.722" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-81-4" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -658.072,-120.731 c -0,0.365 -0.286,0.659 -0.62,0.659 -0.335,0 -0.6,-0.294 -0.594,-0.658 0,-0.366 0.285,-0.663 0.622,-0.663 0.336,0 0.601,0.297 0.592,0.662" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-03-8" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-0-1" + transform="matrix(0.582163,0,0,0.582163,-206.589,-64.0061)"> +<circle + d="m -657.936,-119.134 c -0.01,0.405 -0.316,0.732 -0.684,0.732 -0.368,0 -0.66,-0.327 -0.653,-0.732 0,-0.407 0.314,-0.737 0.685,-0.737 0.37,0 0.662,0.33 0.652,0.737" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-4-2" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -658.017,-119.175 c -0,0.371 -0.289,0.671 -0.627,0.671 -0.337,0 -0.606,-0.3 -0.599,-0.671 0,-0.373 0.288,-0.676 0.628,-0.676 0.339,0 0.607,0.303 0.598,0.676" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-4-8" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-44-9" + transform="matrix(0.582163,0,0,0.582163,-206.589,-68.5133)"> +<circle + d="m -657.821,-116.159 c -0.01,0.421 -0.323,0.761 -0.698,0.761 -0.375,-0 -0.673,-0.342 -0.666,-0.763 0,-0.422 0.321,-0.766 0.698,-0.765 0.378,0 0.676,0.344 0.666,0.767" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-7-3" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -657.904,-116.202 c -0,0.386 -0.296,0.698 -0.64,0.698 -0.344,0 -0.617,-0.313 -0.61,-0.699 0,-0.388 0.294,-0.703 0.64,-0.702 0.346,0 0.619,0.315 0.61,0.703" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-6-6" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-3-8" + transform="matrix(0.582163,0,0,0.582163,-208.842,-66.2597)"> +<circle + d="m -656.157,-117.625 c -0,0.413 -0.313,0.747 -0.684,0.747 -0.372,0 -0.671,-0.334 -0.667,-0.747 0,-0.415 0.311,-0.752 0.685,-0.752 0.374,10e-4 0.672,0.338 0.666,0.752" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-1-0" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -656.239,-117.667 c -0,0.379 -0.287,0.686 -0.628,0.685 -0.341,0 -0.615,-0.306 -0.611,-0.685 0,-0.381 0.285,-0.689 0.628,-0.689 0.343,0 0.617,0.309 0.611,0.689" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-7-2" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-59-1" + transform="matrix(0.582163,0,0,0.582163,-204.335,-61.7525)"> +<circle + d="m -659.758,-120.691 c -0.01,0.397 -0.318,0.718 -0.683,0.718 -0.364,0 -0.65,-0.321 -0.639,-0.718 0.01,-0.398 0.316,-0.722 0.683,-0.722 0.366,-0 0.652,0.323 0.639,0.722" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-62-0" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -659.837,-120.732 c -0.01,0.365 -0.293,0.659 -0.627,0.659 -0.334,0 -0.596,-0.294 -0.586,-0.658 0.01,-0.366 0.29,-0.663 0.626,-0.663 0.336,0 0.598,0.297 0.587,0.662" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-17-5" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-857-1" + transform="matrix(0.582163,0,0,0.582163,-202.082,-61.7525)"> +<circle + d="m -661.525,-120.692 c -0.02,0.397 -0.325,0.717 -0.689,0.718 -0.363,0 -0.647,-0.321 -0.633,-0.718 0.01,-0.398 0.323,-0.722 0.689,-0.722 0.366,0 0.649,0.324 0.633,0.722" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-41-1" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -661.604,-120.732 c -0.01,0.364 -0.298,0.658 -0.632,0.658 -0.334,0 -0.593,-0.294 -0.58,-0.658 0.01,-0.365 0.296,-0.662 0.631,-0.662 0.336,0 0.596,0.296 0.581,0.662" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-85-0" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-97-8" + transform="matrix(0.582163,0,0,0.582163,-202.082,-66.2597)"> +<circle + d="m -661.331,-117.621 c -0.02,0.413 -0.331,0.746 -0.702,0.746 -0.371,0 -0.66,-0.334 -0.645,-0.747 0.01,-0.414 0.33,-0.75 0.703,-0.75 0.373,0 0.662,0.337 0.644,0.751" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-53-5" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -661.411,-117.663 c -0.02,0.379 -0.304,0.684 -0.645,0.684 -0.34,0 -0.605,-0.306 -0.591,-0.685 0.01,-0.379 0.302,-0.688 0.645,-0.687 0.342,0 0.606,0.309 0.591,0.688" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-88-0" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-31-6" + transform="matrix(0.582163,0,0,0.582163,-202.082,-68.5133)"> +<circle + d="m -661.23,-116.155 c -0.02,0.421 -0.336,0.761 -0.71,0.76 -0.375,0 -0.666,-0.34 -0.651,-0.761 0.01,-0.422 0.333,-0.765 0.71,-0.765 0.377,0 0.668,0.344 0.651,0.766" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-89-4" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -661.312,-116.197 c -0.02,0.386 -0.307,0.697 -0.651,0.697 -0.344,0 -0.611,-0.313 -0.597,-0.699 0.01,-0.387 0.306,-0.701 0.651,-0.701 0.346,0 0.613,0.315 0.597,0.703" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-64-6" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-33-2" + transform="matrix(0.582163,0,0,0.582163,-204.335,-64.0061)"> +<circle + d="m -659.682,-119.134 c -0.01,0.405 -0.322,0.732 -0.689,0.732 -0.368,0 -0.657,-0.327 -0.646,-0.732 0.01,-0.406 0.32,-0.737 0.69,-0.737 0.37,10e-4 0.659,0.331 0.645,0.737" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-3-5" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -659.762,-119.175 c -0.01,0.371 -0.295,0.671 -0.633,0.671 -0.337,0 -0.602,-0.3 -0.592,-0.671 0.01,-0.373 0.294,-0.676 0.633,-0.676 0.339,0 0.604,0.303 0.592,0.676" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-86-8" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-04-6" + transform="matrix(0.582163,0,0,0.582163,-208.842,-68.5133)"> +<circle + d="m -656.12,-116.162 c -0,0.422 -0.316,0.762 -0.692,0.762 -0.375,-10e-4 -0.677,-0.342 -0.673,-0.763 0,-0.423 0.314,-0.766 0.692,-0.766 0.378,0 0.679,0.345 0.673,0.767" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-88-2" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -656.203,-116.204 c -0,0.386 -0.29,0.698 -0.634,0.698 -0.345,0 -0.621,-0.313 -0.618,-0.7 0,-0.388 0.288,-0.703 0.635,-0.702 0.346,0 0.622,0.316 0.617,0.704" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-89-8" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-7-4" + transform="matrix(0.582163,0,0,0.582163,-208.842,-61.7525)"> +<circle + d="m -656.228,-120.69 c -0,0.398 -0.306,0.719 -0.671,0.719 -0.365,0 -0.657,-0.321 -0.654,-0.718 0,-0.399 0.305,-0.723 0.672,-0.723 0.367,0 0.659,0.324 0.653,0.722" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-76-7" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -656.308,-120.73 c -0,0.365 -0.281,0.659 -0.616,0.66 -0.334,0 -0.603,-0.295 -0.599,-0.66 0,-0.365 0.279,-0.663 0.615,-0.663 0.337,0 0.605,0.297 0.6,0.663" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-43-2" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +</g> +<g + style="stroke:#000000" + transform="matrix(0.286023,0,0,0.286023,-404.17,-74.9126)" + id="g8702-4"> +<g + style="stroke:#000000" + transform="matrix(0.582162,0,0,0.582162,-208.843,-64.0062)" + id="g8491-06"> +<circle + d="m -663.178,-119.134 c -0.02,0.405 -0.334,0.731 -0.701,0.731 -0.367,0 -0.65,-0.326 -0.632,-0.731 0.02,-0.406 0.333,-0.736 0.702,-0.736 0.369,0 0.652,0.331 0.631,0.736" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-2" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -663.257,-119.175 c -0.02,0.371 -0.307,0.671 -0.643,0.671 -0.337,0 -0.597,-0.3 -0.58,-0.671 0.02,-0.372 0.305,-0.675 0.644,-0.675 0.338,10e-4 0.597,0.303 0.579,0.675" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-9" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + id="g8491-8-9" + transform="matrix(0.582163,0,0,0.582163,-204.335,-68.5133)"> +<circle + d="m -666.358,-116.148 c -0.03,0.421 -0.355,0.76 -0.728,0.759 -0.374,0 -0.655,-0.34 -0.629,-0.76 0.03,-0.422 0.352,-0.764 0.728,-0.764 0.376,10e-4 0.657,0.344 0.629,0.765" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-5-08" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -666.438,-116.19 c -0.03,0.385 -0.325,0.696 -0.668,0.696 -0.343,0 -0.601,-0.312 -0.577,-0.698 0.02,-0.386 0.323,-0.7 0.668,-0.7 0.345,0 0.603,0.315 0.577,0.702" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-0-1" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-9-3" + transform="matrix(0.582163,0,0,0.582163,-206.589,-66.2597)"> +<circle + d="m -664.789,-117.618 c -0.02,0.412 -0.344,0.745 -0.714,0.744 -0.371,0 -0.653,-0.333 -0.631,-0.745 0.02,-0.414 0.342,-0.75 0.715,-0.75 0.373,0 0.655,0.337 0.63,0.751" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-6-1" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -664.869,-117.66 c -0.02,0.378 -0.315,0.683 -0.655,0.683 -0.34,0 -0.599,-0.306 -0.578,-0.684 0.02,-0.379 0.314,-0.688 0.655,-0.687 0.342,0 0.6,0.308 0.578,0.688" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-3-1" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-85-0" + transform="matrix(0.582163,0,0,0.582163,-202.082,-64.0061)"> +<circle + d="m -668.437,-119.133 c -0.03,0.404 -0.352,0.73 -0.718,0.73 -0.366,0 -0.64,-0.326 -0.611,-0.73 0.03,-0.406 0.351,-0.735 0.719,-0.735 0.368,0 0.642,0.33 0.61,0.735" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-61-3" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -668.515,-119.174 c -0.03,0.37 -0.323,0.67 -0.659,0.67 -0.336,0 -0.587,-0.3 -0.56,-0.67 0.03,-0.372 0.322,-0.674 0.659,-0.674 0.338,0 0.589,0.302 0.56,0.674" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-1-4" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-5-0" + transform="matrix(0.582163,0,0,0.582163,-204.335,-66.2597)"> +<circle + d="m -666.522,-117.617 c -0.03,0.412 -0.35,0.745 -0.72,0.744 -0.37,0 -0.649,-0.333 -0.623,-0.745 0.02,-0.413 0.348,-0.749 0.72,-0.749 0.373,0 0.651,0.336 0.623,0.75" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-9-3" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -666.601,-117.659 c -0.03,0.378 -0.322,0.683 -0.661,0.683 -0.339,0 -0.595,-0.306 -0.572,-0.684 0.02,-0.379 0.32,-0.687 0.662,-0.687 0.341,0 0.596,0.309 0.571,0.688" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-8-9" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-4-1" + transform="matrix(0.582163,0,0,0.582163,-206.589,-61.7525)"> +<circle + d="m -665.066,-120.694 c -0.02,0.397 -0.336,0.717 -0.7,0.717 -0.363,0 -0.64,-0.32 -0.619,-0.716 0.02,-0.398 0.335,-0.722 0.701,-0.722 0.365,0 0.642,0.323 0.618,0.721" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-81-9" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -665.144,-120.734 c -0.02,0.364 -0.309,0.658 -0.642,0.658 -0.333,0 -0.587,-0.294 -0.568,-0.658 0.02,-0.365 0.307,-0.661 0.642,-0.661 0.335,0 0.589,0.296 0.568,0.661" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-03-6" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-0-9" + transform="matrix(0.582163,0,0,0.582163,-206.589,-64.0061)"> +<circle + d="m -664.929,-119.134 c -0.02,0.405 -0.34,0.731 -0.707,0.731 -0.367,0 -0.646,-0.326 -0.625,-0.731 0.02,-0.405 0.339,-0.735 0.708,-0.735 0.369,0 0.648,0.33 0.624,0.735" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-4-3" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -665.008,-119.175 c -0.02,0.371 -0.312,0.671 -0.648,0.671 -0.337,0 -0.593,-0.3 -0.573,-0.671 0.02,-0.372 0.31,-0.674 0.649,-0.674 0.338,0 0.594,0.302 0.572,0.674" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-4-3" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-44-8" + transform="matrix(0.582163,0,0,0.582163,-206.589,-68.5133)"> +<circle + d="m -664.647,-116.15 c -0.02,0.42 -0.348,0.76 -0.722,0.759 -0.373,0 -0.658,-0.34 -0.636,-0.76 0.02,-0.422 0.346,-0.765 0.723,-0.765 0.376,10e-4 0.66,0.344 0.635,0.766" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-7-0" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -664.727,-116.193 c -0.02,0.386 -0.319,0.697 -0.662,0.697 -0.343,0 -0.605,-0.312 -0.584,-0.698 0.02,-0.387 0.318,-0.701 0.663,-0.701 0.344,10e-4 0.606,0.316 0.583,0.702" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-6-5" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-3-6" + transform="matrix(0.582163,0,0,0.582163,-208.842,-66.2597)"> +<circle + d="m -663.059,-117.62 c -0.02,0.413 -0.338,0.746 -0.708,0.746 -0.371,-0 -0.657,-0.334 -0.638,-0.746 0.02,-0.414 0.336,-0.751 0.709,-0.75 0.373,0 0.658,0.337 0.637,0.75" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-1-6" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -663.139,-117.662 c -0.02,0.379 -0.31,0.684 -0.65,0.684 -0.34,0 -0.602,-0.306 -0.585,-0.684 0.02,-0.38 0.308,-0.688 0.65,-0.688 0.342,0 0.604,0.309 0.585,0.688" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-7-4" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-59-0" + transform="matrix(0.582163,0,0,0.582163,-204.335,-61.7525)"> +<circle + d="m -666.839,-120.695 c -0.03,0.397 -0.343,0.717 -0.706,0.717 -0.363,0 -0.637,-0.32 -0.612,-0.716 0.03,-0.398 0.341,-0.721 0.706,-0.722 0.365,0 0.639,0.324 0.612,0.721" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-62-04" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -666.917,-120.735 c -0.02,0.364 -0.315,0.658 -0.647,0.658 -0.333,0 -0.585,-0.294 -0.562,-0.657 0.02,-0.365 0.313,-0.662 0.647,-0.662 0.335,0 0.586,0.296 0.562,0.661" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-17-6" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-857-2" + transform="matrix(0.582163,0,0,0.582163,-202.082,-61.7525)"> +<circle + d="m -668.614,-120.696 c -0.03,0.397 -0.349,0.717 -0.711,0.717 -0.363,0 -0.634,-0.32 -0.606,-0.716 0.03,-0.398 0.347,-0.721 0.712,-0.721 0.365,0 0.635,0.323 0.605,0.72" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-41-6" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -668.692,-120.736 c -0.03,0.364 -0.32,0.658 -0.652,0.658 -0.333,0 -0.581,-0.294 -0.555,-0.657 0.03,-0.365 0.318,-0.661 0.652,-0.661 0.335,-0 0.583,0.296 0.555,0.66" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-85-7" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-97-5" + transform="matrix(0.582163,0,0,0.582163,-202.082,-66.2597)"> +<circle + d="m -668.256,-117.616 c -0.03,0.412 -0.356,0.744 -0.726,0.744 -0.37,0 -0.646,-0.333 -0.616,-0.745 0.03,-0.413 0.355,-0.749 0.727,-0.749 0.372,0 0.647,0.337 0.615,0.75" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-53-6" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -668.335,-117.658 c -0.03,0.378 -0.327,0.683 -0.666,0.683 -0.339,0 -0.592,-0.306 -0.565,-0.684 0.03,-0.379 0.326,-0.686 0.667,-0.686 0.341,0 0.593,0.308 0.564,0.687" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-88-9" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-31-8" + transform="matrix(0.582163,0,0,0.582163,-202.082,-68.5133)"> +<circle + d="m -668.071,-116.145 c -0.03,0.42 -0.361,0.759 -0.734,0.758 -0.373,0 -0.652,-0.34 -0.622,-0.76 0.03,-0.421 0.359,-0.764 0.735,-0.763 0.375,0 0.653,0.343 0.621,0.765" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-89-7" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -668.151,-116.188 c -0.03,0.385 -0.331,0.696 -0.673,0.696 -0.342,0 -0.598,-0.312 -0.57,-0.697 0.03,-0.387 0.329,-0.701 0.673,-0.7 0.345,0 0.6,0.315 0.57,0.701" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-64-2" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-33-8" + transform="matrix(0.582163,0,0,0.582163,-204.335,-64.0061)"> +<circle + d="m -666.682,-119.133 c -0.03,0.404 -0.347,0.73 -0.713,0.73 -0.366,0 -0.643,-0.326 -0.618,-0.731 0.03,-0.405 0.345,-0.735 0.714,-0.735 0.368,0 0.645,0.33 0.617,0.736" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-3-2" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -666.761,-119.174 c -0.02,0.37 -0.318,0.67 -0.654,0.67 -0.336,0 -0.589,-0.3 -0.566,-0.67 0.02,-0.372 0.316,-0.675 0.654,-0.675 0.338,0 0.591,0.303 0.566,0.675" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-86-9" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-04-9" + transform="matrix(0.582163,0,0,0.582163,-208.842,-68.5133)"> +<circle + d="m -662.938,-116.152 c -0.02,0.42 -0.341,0.76 -0.715,0.759 -0.375,0 -0.663,-0.34 -0.644,-0.761 0.02,-0.422 0.34,-0.765 0.716,-0.764 0.377,0 0.665,0.344 0.643,0.766" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-88-6" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -663.019,-116.195 c -0.02,0.386 -0.313,0.697 -0.656,0.697 -0.344,0 -0.608,-0.312 -0.59,-0.698 0.02,-0.387 0.311,-0.702 0.656,-0.701 0.346,0 0.61,0.315 0.59,0.702" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-89-0" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-7-2" + transform="matrix(0.582163,0,0,0.582163,-208.842,-61.7525)"> +<circle + d="m -663.295,-120.693 c -0.02,0.397 -0.33,0.717 -0.694,0.717 -0.363,0 -0.644,-0.32 -0.626,-0.717 0.02,-0.398 0.329,-0.721 0.695,-0.721 0.365,-0 0.646,0.323 0.625,0.721" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-76-76" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -663.373,-120.733 c -0.02,0.364 -0.304,0.658 -0.637,0.658 -0.334,0 -0.591,-0.294 -0.574,-0.658 0.02,-0.365 0.301,-0.662 0.637,-0.662 0.335,0 0.592,0.297 0.574,0.662" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-43-1" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +</g> +<g + style="stroke:#000000" + transform="matrix(0.286023,0,0,0.286023,-401.592,-74.9126)" + id="g8702-3"> +<g + style="stroke:#000000" + transform="matrix(0.582162,0,0,0.582162,-208.843,-64.0062)" + id="g8491-2"> +<circle + d="m -670.193,-119.133 c -0.03,0.404 -0.359,0.73 -0.725,0.73 -0.365,0 -0.636,-0.326 -0.603,-0.73 0.03,-0.405 0.357,-0.735 0.725,-0.735 0.368,10e-4 0.638,0.33 0.603,0.735" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-15" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -670.271,-119.174 c -0.03,0.37 -0.329,0.67 -0.664,0.67 -0.336,0 -0.584,-0.3 -0.554,-0.67 0.03,-0.372 0.327,-0.674 0.665,-0.674 0.337,0 0.585,0.303 0.553,0.674" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-99" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + id="g8491-8-1" + transform="matrix(0.582163,0,0,0.582163,-204.335,-68.5133)"> +<circle + d="m -673.222,-116.138 c -0.04,0.419 -0.38,0.758 -0.752,0.757 -0.373,0 -0.641,-0.339 -0.6,-0.759 0.04,-0.421 0.378,-0.763 0.752,-0.762 0.375,0 0.643,0.343 0.6,0.764" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-5-4" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -673.301,-116.181 c -0.04,0.385 -0.348,0.696 -0.69,0.695 -0.341,0 -0.587,-0.311 -0.55,-0.696 0.04,-0.386 0.347,-0.7 0.69,-0.699 0.344,0 0.59,0.314 0.55,0.7" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-0-9" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-9-1" + transform="matrix(0.582163,0,0,0.582163,-206.589,-66.2597)"> +<circle + d="m -671.729,-117.614 c -0.04,0.412 -0.37,0.744 -0.739,0.744 -0.369,0 -0.638,-0.333 -0.602,-0.744 0.04,-0.413 0.368,-0.749 0.739,-0.748 0.371,0 0.641,0.336 0.602,0.748" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-6-0" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -671.808,-117.655 c -0.03,0.377 -0.338,0.682 -0.676,0.682 -0.339,0 -0.586,-0.306 -0.553,-0.683 0.03,-0.379 0.338,-0.686 0.678,-0.686 0.34,0 0.587,0.308 0.551,0.687" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-3-7" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-85-5" + transform="matrix(0.582163,0,0,0.582163,-202.082,-64.0061)"> +<circle + d="m -675.474,-119.133 c -0.05,0.404 -0.378,0.729 -0.742,0.729 -0.365,0 -0.626,-0.325 -0.583,-0.729 0.04,-0.404 0.375,-0.733 0.742,-0.733 0.367,0 0.628,0.329 0.583,0.733" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-61-8" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -675.551,-119.173 c -0.04,0.369 -0.346,0.668 -0.68,0.668 -0.335,0 -0.574,-0.299 -0.535,-0.669 0.04,-0.371 0.344,-0.672 0.681,-0.672 0.336,0 0.575,0.302 0.534,0.673" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-1-7" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-5-04" + transform="matrix(0.582163,0,0,0.582163,-204.335,-66.2597)"> +<circle + d="m -673.47,-117.612 c -0.04,0.411 -0.375,0.743 -0.744,0.743 -0.368,0 -0.635,-0.333 -0.595,-0.744 0.04,-0.413 0.374,-0.748 0.745,-0.748 0.371,0 0.637,0.336 0.594,0.749" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-9-8" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -673.548,-117.654 c -0.04,0.377 -0.344,0.682 -0.682,0.682 -0.338,0 -0.582,-0.305 -0.545,-0.683 0.04,-0.378 0.342,-0.686 0.682,-0.686 0.34,10e-4 0.584,0.309 0.545,0.687" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-8-0" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-4-4" + transform="matrix(0.582163,0,0,0.582163,-206.589,-61.7525)"> +<circle + d="m -672.169,-120.697 c -0.04,0.395 -0.361,0.716 -0.723,0.716 -0.362,0 -0.627,-0.32 -0.592,-0.716 0.04,-0.397 0.359,-0.72 0.724,-0.72 0.364,0 0.628,0.323 0.591,0.72" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-81-2" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -672.246,-120.737 c -0.03,0.363 -0.331,0.656 -0.663,0.657 -0.332,0 -0.575,-0.294 -0.543,-0.657 0.03,-0.364 0.33,-0.66 0.664,-0.66 0.333,-10e-4 0.576,0.295 0.542,0.66" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-03-9" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-0-6" + transform="matrix(0.582163,0,0,0.582163,-206.589,-64.0061)"> +<circle + d="m -671.951,-119.133 c -0.04,0.404 -0.366,0.73 -0.731,0.73 -0.365,0 -0.633,-0.326 -0.597,-0.73 0.04,-0.405 0.364,-0.734 0.731,-0.734 0.368,0 0.635,0.329 0.597,0.734" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-4-1" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -672.029,-119.174 c -0.03,0.37 -0.335,0.67 -0.67,0.67 -0.335,0 -0.58,-0.3 -0.547,-0.67 0.03,-0.371 0.333,-0.673 0.67,-0.673 0.337,0 0.582,0.302 0.547,0.673" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-4-0" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-44-4" + transform="matrix(0.582163,0,0,0.582163,-206.589,-68.5133)"> +<circle + d="m -671.503,-116.141 c -0.04,0.42 -0.373,0.759 -0.746,0.758 -0.373,0 -0.645,-0.339 -0.607,-0.759 0.04,-0.421 0.372,-0.763 0.747,-0.763 0.374,0 0.646,0.343 0.606,0.764" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-7-2" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -671.582,-116.183 c -0.04,0.385 -0.342,0.696 -0.684,0.695 -0.342,0 -0.591,-0.312 -0.557,-0.697 0.03,-0.386 0.341,-0.699 0.685,-0.699 0.343,0 0.593,0.315 0.556,0.701" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-6-2" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-3-2" + transform="matrix(0.582163,0,0,0.582163,-208.842,-66.2597)"> +<circle + d="m -669.992,-117.615 c -0.04,0.412 -0.363,0.744 -0.732,0.744 -0.369,0 -0.642,-0.333 -0.609,-0.745 0.03,-0.413 0.361,-0.748 0.733,-0.748 0.371,0 0.644,0.336 0.608,0.749" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-1-05" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -670.07,-117.657 c -0.03,0.378 -0.333,0.683 -0.672,0.683 -0.339,0 -0.589,-0.306 -0.558,-0.683 0.03,-0.379 0.331,-0.687 0.672,-0.687 0.34,10e-4 0.59,0.309 0.558,0.687" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-7-5" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-59-2" + transform="matrix(0.582163,0,0,0.582163,-204.335,-61.7525)"> +<circle + d="m -673.95,-120.698 c -0.04,0.395 -0.367,0.715 -0.729,0.716 -0.361,0 -0.623,-0.32 -0.585,-0.716 0.04,-0.397 0.366,-0.72 0.729,-0.72 0.364,0 0.626,0.323 0.585,0.72" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-62-9" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -674.027,-120.738 c -0.04,0.363 -0.336,0.656 -0.668,0.656 -0.331,10e-4 -0.572,-0.293 -0.536,-0.656 0.03,-0.364 0.335,-0.66 0.668,-0.66 0.334,0 0.574,0.296 0.536,0.66" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-17-0" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-857-28" + transform="matrix(0.582163,0,0,0.582163,-202.082,-61.7525)"> +<circle + d="m -675.732,-120.699 c -0.05,0.395 -0.373,0.715 -0.735,0.715 -0.361,10e-4 -0.62,-0.319 -0.578,-0.714 0.04,-0.397 0.372,-0.72 0.735,-0.72 0.364,0 0.622,0.322 0.578,0.719" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-41-3" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -675.808,-120.739 c -0.04,0.363 -0.342,0.656 -0.674,0.656 -0.331,0 -0.568,-0.293 -0.53,-0.656 0.04,-0.364 0.341,-0.659 0.674,-0.66 0.333,0 0.57,0.296 0.53,0.66" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-85-8" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-97-0" + transform="matrix(0.582163,0,0,0.582163,-202.082,-66.2597)"> +<circle + d="m -675.211,-117.611 c -0.05,0.411 -0.382,0.743 -0.75,0.743 -0.369,0 -0.632,-0.333 -0.588,-0.744 0.04,-0.412 0.38,-0.748 0.751,-0.747 0.37,0 0.633,0.336 0.587,0.748" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-53-4" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -675.289,-117.653 c -0.04,0.377 -0.35,0.682 -0.687,0.682 -0.338,0 -0.58,-0.305 -0.539,-0.683 0.04,-0.378 0.348,-0.685 0.688,-0.685 0.34,0 0.581,0.308 0.538,0.686" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-88-09" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-31-1" + transform="matrix(0.582163,0,0,0.582163,-202.082,-68.5133)"> +<circle + d="m -674.943,-116.136 c -0.05,0.419 -0.386,0.758 -0.758,0.757 -0.372,0 -0.638,-0.339 -0.593,-0.758 0.04,-0.421 0.385,-0.763 0.759,-0.762 0.374,0 0.639,0.342 0.592,0.763" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-89-9" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -675.021,-116.179 c -0.04,0.385 -0.354,0.696 -0.695,0.695 -0.341,0 -0.585,-0.311 -0.544,-0.696 0.04,-0.386 0.353,-0.699 0.696,-0.699 0.343,0 0.586,0.315 0.543,0.7" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-64-62" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-33-5" + transform="matrix(0.582163,0,0,0.582163,-204.335,-64.0061)"> +<circle + d="m -673.712,-119.133 c -0.04,0.404 -0.372,0.73 -0.737,0.73 -0.365,0 -0.629,-0.326 -0.589,-0.73 0.04,-0.405 0.369,-0.734 0.736,-0.734 0.368,0 0.631,0.33 0.59,0.734" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-3-4" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -673.789,-119.174 c -0.04,0.37 -0.341,0.669 -0.676,0.67 -0.334,0 -0.577,-0.3 -0.541,-0.67 0.04,-0.371 0.339,-0.673 0.676,-0.673 0.337,10e-4 0.579,0.302 0.541,0.673" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-86-4" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-04-99" + transform="matrix(0.582163,0,0,0.582163,-208.842,-68.5133)"> +<circle + d="m -669.786,-116.143 c -0.04,0.42 -0.367,0.759 -0.74,0.758 -0.373,0 -0.648,-0.34 -0.615,-0.759 0.03,-0.422 0.366,-0.764 0.741,-0.763 0.375,0 0.65,0.343 0.614,0.764" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-88-3" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -669.865,-116.186 c -0.03,0.385 -0.337,0.696 -0.679,0.696 -0.342,0 -0.595,-0.312 -0.564,-0.697 0.03,-0.386 0.335,-0.7 0.679,-0.7 0.344,0 0.596,0.315 0.564,0.701" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-89-6" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-7-0" + transform="matrix(0.582163,0,0,0.582163,-208.842,-61.7525)"> +<circle + d="m -670.391,-120.696 c -0.03,0.395 -0.355,0.716 -0.717,0.716 -0.362,0 -0.63,-0.32 -0.599,-0.716 0.03,-0.397 0.354,-0.721 0.718,-0.721 0.364,0 0.632,0.323 0.598,0.721" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-76-5" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -670.468,-120.737 c -0.03,0.364 -0.325,0.657 -0.658,0.658 -0.332,0 -0.578,-0.294 -0.549,-0.657 0.03,-0.365 0.324,-0.661 0.658,-0.661 0.335,0 0.58,0.296 0.549,0.66" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-43-0" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +</g> +</g> +<g + style="stroke:#000000" + inkscape:path-effect="#path-effect9673-2" + id="g9671-9" + transform="translate(381.671,-53.7257)"> +<g + style="stroke:#000000" + id="g8702-43" + transform="matrix(0.286023,0,0,0.286023,-409.326,-74.9126)"> +<g + style="stroke:#000000" + transform="matrix(0.582162,0,0,0.582162,-208.843,-64.0062)" + id="g8491-51"> +<circle + d="m -649.311,-119.264 c 0,0.377 -0.273,0.681 -0.618,0.681 -0.345,0 -0.629,-0.305 -0.635,-0.682 -0,-0.379 0.271,-0.686 0.618,-0.686 0.348,10e-4 0.632,0.309 0.635,0.687" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-74" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -649.388,-119.302 c 0,0.346 -0.251,0.625 -0.567,0.625 -0.316,-10e-4 -0.577,-0.28 -0.582,-0.626 -0,-0.347 0.248,-0.63 0.567,-0.629 0.318,0 0.579,0.283 0.582,0.63" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-31" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + id="g8491-8-46" + transform="matrix(0.582163,0,0,0.582163,-204.335,-68.5133)"> +<circle + d="m -653.007,-115.977 c -0,0.396 -0.296,0.715 -0.649,0.714 -0.353,0 -0.637,-0.32 -0.634,-0.716 0,-0.398 0.293,-0.721 0.648,-0.72 0.356,0 0.64,0.324 0.635,0.722" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-5-9" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -653.085,-116.018 c -0,0.364 -0.271,0.657 -0.595,0.656 -0.324,0 -0.585,-0.294 -0.582,-0.657 0,-0.365 0.269,-0.661 0.595,-0.661 0.326,0 0.587,0.298 0.582,0.662" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-0-4" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-9-2" + transform="matrix(0.582163,0,0,0.582163,-206.589,-66.2597)"> +<circle + d="m -651.183,-117.593 c -10e-4,0.386 -0.285,0.697 -0.634,0.697 -0.348,0 -0.632,-0.313 -0.634,-0.699 -0,-0.388 0.281,-0.703 0.633,-0.703 0.351,0 0.636,0.317 0.635,0.705" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-6-2" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -651.261,-117.633 c -10e-4,0.355 -0.261,0.641 -0.581,0.64 -0.32,0 -0.58,-0.287 -0.582,-0.641 -10e-4,-0.356 0.259,-0.645 0.581,-0.644 0.322,0 0.583,0.29 0.582,0.645" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-3-64" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-85-1" + transform="matrix(0.582163,0,0,0.582163,-202.082,-64.0061)"> +<circle + d="m -654.946,-119.257 c -0,0.377 -0.296,0.681 -0.64,0.681 -0.344,-0 -0.618,-0.305 -0.612,-0.682 0,-0.378 0.294,-0.686 0.641,-0.686 0.347,0 0.621,0.309 0.611,0.687" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-61-28" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -655.022,-119.295 c -0,0.346 -0.271,0.625 -0.587,0.624 -0.316,0 -0.567,-0.28 -0.561,-0.625 0,-0.347 0.269,-0.629 0.588,-0.629 0.318,0 0.569,0.283 0.56,0.63" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-1-8" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-5-9" + transform="matrix(0.582163,0,0,0.582163,-204.335,-66.2597)"> +<circle + d="m -653.038,-117.591 c -0,0.387 -0.292,0.698 -0.64,0.698 -0.349,-0 -0.63,-0.313 -0.627,-0.699 0,-0.388 0.289,-0.703 0.641,-0.703 0.351,0 0.632,0.317 0.626,0.704" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-9-2" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -653.115,-117.63 c -0,0.354 -0.267,0.64 -0.587,0.64 -0.32,-0 -0.578,-0.287 -0.575,-0.641 0,-0.356 0.265,-0.645 0.587,-0.645 0.323,10e-4 0.58,0.29 0.575,0.646" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-8-8" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-4-8" + transform="matrix(0.582163,0,0,0.582163,-206.589,-61.7525)"> +<circle + d="m -651.194,-120.982 c -0,0.368 -0.277,0.665 -0.618,0.664 -0.34,0 -0.617,-0.297 -0.619,-0.665 -0,-0.37 0.275,-0.67 0.618,-0.67 0.343,0 0.62,0.302 0.619,0.671" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-81-8" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -651.269,-121.019 c -10e-4,0.337 -0.255,0.609 -0.567,0.609 -0.312,0 -0.567,-0.273 -0.568,-0.611 -0,-0.338 0.252,-0.614 0.566,-0.614 0.315,0 0.569,0.277 0.569,0.616" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-03-68" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-0-3" + transform="matrix(0.582163,0,0,0.582163,-206.589,-64.0061)"> +<circle + d="m -651.189,-119.261 c -0,0.377 -0.281,0.681 -0.625,0.68 -0.345,0 -0.625,-0.305 -0.627,-0.682 -0,-0.378 0.278,-0.686 0.625,-0.685 0.348,0 0.628,0.308 0.627,0.687" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-4-8" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -651.265,-119.3 c -10e-4,0.346 -0.258,0.625 -0.574,0.625 -0.316,0 -0.573,-0.28 -0.575,-0.626 -0,-0.347 0.255,-0.629 0.574,-0.629 0.318,0 0.575,0.283 0.575,0.63" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-4-33" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-44-3" + transform="matrix(0.582163,0,0,0.582163,-206.589,-68.5133)"> +<circle + d="m -651.178,-115.98 c -10e-4,0.396 -0.288,0.715 -0.641,0.714 -0.353,0 -0.641,-0.32 -0.643,-0.716 -0,-0.398 0.285,-0.721 0.641,-0.72 0.356,0 0.644,0.324 0.643,0.722" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-7-8" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -651.257,-116.021 c 0,0.364 -0.263,0.657 -0.587,0.656 -0.324,0 -0.588,-0.294 -0.59,-0.657 -0,-0.365 0.262,-0.661 0.588,-0.661 0.326,0 0.59,0.298 0.589,0.662" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-6-0" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-3-4" + transform="matrix(0.582163,0,0,0.582163,-208.842,-66.2597)"> +<circle + d="m -649.331,-117.596 c 0,0.386 -0.276,0.698 -0.625,0.697 -0.349,0 -0.637,-0.312 -0.643,-0.699 -0,-0.387 0.274,-0.703 0.626,-0.702 0.351,0 0.639,0.316 0.642,0.704" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-1-7" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -649.409,-117.635 c 0,0.354 -0.253,0.64 -0.573,0.64 -0.32,-10e-4 -0.584,-0.287 -0.59,-0.642 -0,-0.356 0.252,-0.645 0.574,-0.644 0.322,0 0.586,0.29 0.589,0.646" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-7-6" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-59-8" + transform="matrix(0.582163,0,0,0.582163,-204.335,-61.7525)"> +<circle + d="m -653.096,-120.98 c -0,0.368 -0.285,0.665 -0.626,0.664 -0.34,0 -0.614,-0.297 -0.612,-0.665 0,-0.37 0.283,-0.67 0.626,-0.67 0.343,10e-4 0.617,0.302 0.612,0.671" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-62-90" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -653.172,-121.017 c -0,0.337 -0.261,0.609 -0.573,0.609 -0.312,0 -0.564,-0.273 -0.561,-0.611 0,-0.338 0.259,-0.614 0.573,-0.613 0.314,0 0.566,0.276 0.561,0.615" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-17-68" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-857-7" + transform="matrix(0.582163,0,0,0.582163,-202.082,-61.7525)"> +<circle + d="m -654.999,-120.978 c -0,0.368 -0.292,0.664 -0.632,0.664 -0.34,0 -0.611,-0.298 -0.605,-0.665 0,-0.37 0.29,-0.67 0.633,-0.67 0.343,0 0.613,0.302 0.604,0.671" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-41-9" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -655.074,-121.015 c -0,0.337 -0.267,0.609 -0.58,0.609 -0.312,0 -0.56,-0.273 -0.554,-0.611 0,-0.338 0.266,-0.614 0.58,-0.613 0.314,0 0.563,0.276 0.554,0.615" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-85-03" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-97-3" + transform="matrix(0.582163,0,0,0.582163,-202.082,-66.2597)"> +<circle + d="m -654.892,-117.588 c -0,0.386 -0.299,0.698 -0.648,0.697 -0.348,0 -0.626,-0.313 -0.619,-0.699 0,-0.387 0.297,-0.703 0.649,-0.702 0.351,0 0.628,0.316 0.618,0.704" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-53-3" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -654.968,-117.627 c -0,0.354 -0.275,0.64 -0.595,0.639 -0.32,0 -0.574,-0.287 -0.567,-0.641 0,-0.355 0.272,-0.644 0.594,-0.644 0.322,0 0.576,0.29 0.568,0.646" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-88-7" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-31-3" + transform="matrix(0.582163,0,0,0.582163,-202.082,-68.5133)"> +<circle + d="m -654.836,-115.974 c -0.01,0.395 -0.304,0.715 -0.657,0.714 -0.352,0 -0.633,-0.32 -0.626,-0.716 0,-0.398 0.301,-0.721 0.657,-0.72 0.356,0 0.636,0.324 0.626,0.722" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-89-2" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -654.914,-116.015 c -0,0.364 -0.278,0.656 -0.602,0.656 -0.323,0 -0.581,-0.294 -0.574,-0.657 0,-0.365 0.276,-0.661 0.602,-0.661 0.326,0 0.583,0.298 0.574,0.662" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-64-65" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-33-26" + transform="matrix(0.582163,0,0,0.582163,-204.335,-64.0061)"> +<circle + d="m -653.067,-119.259 c -0,0.377 -0.289,0.681 -0.633,0.681 -0.345,-10e-4 -0.622,-0.305 -0.62,-0.682 0,-0.379 0.286,-0.686 0.633,-0.686 0.348,0 0.625,0.309 0.62,0.687" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-3-58" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -653.144,-119.297 c -0,0.346 -0.264,0.625 -0.58,0.624 -0.316,0 -0.57,-0.28 -0.568,-0.626 0,-0.347 0.262,-0.629 0.58,-0.628 0.319,0 0.573,0.283 0.568,0.63" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-86-7" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-04-96" + transform="matrix(0.582163,0,0,0.582163,-208.842,-68.5133)"> +<circle + d="m -649.35,-115.983 c 0,0.396 -0.28,0.715 -0.634,0.715 -0.353,-0 -0.644,-0.321 -0.65,-0.717 -0,-0.398 0.277,-0.721 0.633,-0.72 0.356,0 0.647,0.324 0.651,0.722" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-88-0" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -649.429,-116.023 c 0,0.363 -0.257,0.656 -0.581,0.655 -0.324,0 -0.591,-0.294 -0.597,-0.657 -0,-0.365 0.254,-0.661 0.581,-0.661 0.326,0 0.593,0.298 0.597,0.663" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-89-4" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-7-1" + transform="matrix(0.582163,0,0,0.582163,-208.842,-61.7525)"> +<circle + d="m -649.292,-120.984 c 0,0.368 -0.271,0.665 -0.611,0.664 -0.341,0 -0.621,-0.297 -0.627,-0.665 -0,-0.37 0.267,-0.67 0.611,-0.67 0.343,0 0.623,0.301 0.627,0.671" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-76-0" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -649.369,-121.022 c 0,0.338 -0.247,0.611 -0.56,0.61 -0.312,0 -0.569,-0.273 -0.575,-0.611 -0,-0.339 0.246,-0.614 0.56,-0.614 0.315,0 0.572,0.277 0.575,0.615" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-43-4" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +</g> +<g + style="stroke:#000000" + id="g8702-0-8" + transform="matrix(0.286023,0,0,0.286023,-406.748,-74.9126)"> +<g + style="stroke:#000000" + id="g8491-30-7" + transform="matrix(0.582162,0,0,0.582162,-208.843,-64.0062)"> +<circle + d="m -656.825,-119.254 c -0.01,0.376 -0.303,0.68 -0.647,0.68 -0.345,0 -0.615,-0.305 -0.604,-0.682 0.01,-0.378 0.301,-0.686 0.648,-0.685 0.347,0 0.617,0.308 0.603,0.687" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-92-0" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -656.9,-119.293 c -0.01,0.346 -0.278,0.625 -0.594,0.625 -0.316,-10e-4 -0.564,-0.28 -0.554,-0.626 0.01,-0.347 0.276,-0.629 0.594,-0.628 0.318,0 0.566,0.282 0.554,0.629" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-5-8" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-68.5133)" + id="g8491-8-4-6"> +<circle + d="m -660.327,-115.965 c -0.02,0.395 -0.327,0.714 -0.68,0.714 -0.352,-10e-4 -0.622,-0.321 -0.602,-0.716 0.02,-0.398 0.325,-0.721 0.681,-0.72 0.355,10e-4 0.624,0.324 0.601,0.722" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-5-0-2" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -660.403,-116.006 c -0.02,0.363 -0.3,0.656 -0.623,0.656 -0.324,-0 -0.571,-0.294 -0.553,-0.657 0.02,-0.365 0.298,-0.661 0.624,-0.66 0.326,0 0.573,0.297 0.552,0.661" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-0-5-4" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-66.2597)" + id="g8491-9-9-7"> +<circle + d="m -658.601,-117.583 c -0.02,0.386 -0.315,0.698 -0.664,0.697 -0.348,0 -0.618,-0.312 -0.603,-0.698 0.02,-0.388 0.313,-0.703 0.664,-0.703 0.351,0 0.621,0.317 0.603,0.704" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-6-4-9" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -658.677,-117.622 c -0.02,0.354 -0.289,0.64 -0.608,0.639 -0.32,0 -0.568,-0.286 -0.554,-0.64 0.02,-0.356 0.287,-0.645 0.609,-0.645 0.322,0 0.569,0.291 0.553,0.646" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-3-6-3" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-64.0061)" + id="g8491-85-9-9"> +<circle + d="m -662.466,-119.247 c -0.03,0.376 -0.326,0.68 -0.67,0.68 -0.344,-0 -0.604,-0.305 -0.58,-0.682 0.02,-0.378 0.323,-0.685 0.67,-0.685 0.347,10e-4 0.606,0.309 0.58,0.687" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-61-2-2" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -662.54,-119.286 c -0.02,0.346 -0.299,0.625 -0.614,0.624 -0.316,0 -0.554,-0.279 -0.533,-0.625 0.02,-0.347 0.297,-0.629 0.615,-0.628 0.318,0 0.556,0.283 0.532,0.629" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-1-2-8" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-66.2597)" + id="g8491-5-4-3"> +<circle + d="m -660.458,-117.58 c -0.02,0.386 -0.322,0.697 -0.671,0.697 -0.348,-0 -0.615,-0.313 -0.595,-0.699 0.02,-0.387 0.321,-0.702 0.672,-0.702 0.351,0 0.617,0.316 0.594,0.704" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-9-7-0" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -660.533,-117.619 c -0.02,0.354 -0.296,0.639 -0.615,0.639 -0.32,0 -0.564,-0.287 -0.546,-0.641 0.02,-0.355 0.294,-0.644 0.616,-0.644 0.321,0 0.566,0.29 0.545,0.646" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-8-7-1" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-61.7525)" + id="g8491-4-5-7"> +<circle + d="m -658.805,-120.974 c -0.02,0.368 -0.307,0.664 -0.647,0.664 -0.34,0 -0.604,-0.298 -0.589,-0.665 0.01,-0.37 0.304,-0.67 0.647,-0.669 0.342,0 0.606,0.301 0.589,0.67" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-81-4-8" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -658.879,-121.011 c -0.02,0.337 -0.282,0.609 -0.593,0.609 -0.312,0 -0.554,-0.273 -0.541,-0.611 0.01,-0.338 0.279,-0.613 0.593,-0.613 0.315,0 0.556,0.276 0.541,0.615" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-03-8-9" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-64.0061)" + id="g8491-0-1-1"> +<circle + d="m -658.704,-119.252 c -0.02,0.377 -0.311,0.681 -0.656,0.68 -0.344,0 -0.611,-0.305 -0.596,-0.681 0.01,-0.379 0.309,-0.686 0.656,-0.686 0.347,10e-4 0.613,0.309 0.596,0.687" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-4-2-5" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -658.779,-119.29 c -0.02,0.345 -0.285,0.624 -0.601,0.624 -0.316,0 -0.561,-0.28 -0.547,-0.626 0.01,-0.347 0.283,-0.628 0.601,-0.628 0.318,0 0.563,0.283 0.547,0.63" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-4-8-4" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-68.5133)" + id="g8491-44-9-9"> +<circle + d="m -658.495,-115.968 c -0.02,0.395 -0.32,0.714 -0.673,0.714 -0.352,-10e-4 -0.626,-0.321 -0.61,-0.716 0.02,-0.398 0.317,-0.721 0.673,-0.72 0.355,0 0.628,0.324 0.61,0.722" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-7-3-2" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -658.572,-116.009 c -0.02,0.363 -0.293,0.656 -0.617,0.656 -0.323,-0 -0.574,-0.294 -0.559,-0.657 0.01,-0.365 0.291,-0.661 0.616,-0.66 0.326,0 0.577,0.297 0.56,0.661" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-6-6-5" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-208.842,-66.2597)" + id="g8491-3-8-7"> +<circle + d="m -656.746,-117.585 c -0.01,0.386 -0.308,0.697 -0.656,0.697 -0.349,-10e-4 -0.622,-0.313 -0.611,-0.699 0.01,-0.388 0.305,-0.703 0.656,-0.702 0.351,0 0.625,0.316 0.611,0.704" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-1-0-4" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -656.822,-117.625 c -0.01,0.355 -0.282,0.64 -0.602,0.64 -0.32,0 -0.571,-0.287 -0.56,-0.641 0.01,-0.356 0.279,-0.645 0.601,-0.644 0.322,0 0.573,0.29 0.561,0.645" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-7-2-9" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-61.7525)" + id="g8491-59-1-9"> +<circle + d="m -660.71,-120.972 c -0.02,0.368 -0.314,0.664 -0.654,0.664 -0.34,0 -0.601,-0.298 -0.582,-0.665 0.02,-0.369 0.312,-0.67 0.655,-0.669 0.342,0 0.602,0.301 0.581,0.67" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-62-0-4" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -660.784,-121.009 c -0.02,0.337 -0.288,0.609 -0.599,0.609 -0.312,0 -0.551,-0.273 -0.534,-0.611 0.02,-0.338 0.286,-0.613 0.6,-0.613 0.314,0 0.553,0.276 0.533,0.615" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-17-5-5" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-61.7525)" + id="g8491-857-1-9"> +<circle + d="m -662.614,-120.97 c -0.03,0.368 -0.322,0.664 -0.662,0.664 -0.34,-0 -0.597,-0.298 -0.574,-0.665 0.02,-0.369 0.319,-0.669 0.662,-0.669 0.342,0 0.599,0.301 0.574,0.67" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-41-1-3" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -662.688,-121.007 c -0.02,0.337 -0.294,0.609 -0.606,0.609 -0.312,-0 -0.548,-0.273 -0.527,-0.611 0.02,-0.338 0.293,-0.613 0.607,-0.613 0.314,0 0.55,0.276 0.526,0.615" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-85-0-5" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-66.2597)" + id="g8491-97-8-7"> +<circle + d="m -662.314,-117.577 c -0.03,0.385 -0.33,0.697 -0.678,0.696 -0.349,0 -0.612,-0.312 -0.588,-0.698 0.03,-0.388 0.329,-0.703 0.68,-0.702 0.351,0 0.613,0.316 0.586,0.704" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-53-5-0" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -662.389,-117.617 c -0.02,0.354 -0.302,0.64 -0.622,0.64 -0.319,-10e-4 -0.561,-0.287 -0.539,-0.641 0.02,-0.356 0.301,-0.644 0.623,-0.644 0.322,0 0.563,0.29 0.538,0.645" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-88-0-8" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-68.5133)" + id="g8491-31-6-1"> +<circle + d="m -662.158,-115.962 c -0.03,0.395 -0.335,0.714 -0.687,0.714 -0.353,-10e-4 -0.619,-0.321 -0.594,-0.716 0.02,-0.398 0.333,-0.72 0.688,-0.72 0.355,10e-4 0.621,0.324 0.593,0.722" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-89-4-9" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -662.233,-116.003 c -0.03,0.363 -0.308,0.656 -0.631,0.656 -0.324,-0 -0.567,-0.294 -0.545,-0.657 0.02,-0.365 0.305,-0.661 0.631,-0.66 0.326,0 0.57,0.297 0.545,0.661" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-64-6-9" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-64.0061)" + id="g8491-33-2-7"> +<circle + d="m -660.585,-119.25 c -0.02,0.377 -0.319,0.681 -0.663,0.68 -0.344,0 -0.607,-0.304 -0.588,-0.681 0.02,-0.378 0.316,-0.686 0.663,-0.685 0.346,0 0.61,0.308 0.588,0.686" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-3-5-8" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -660.66,-119.288 c -0.02,0.346 -0.292,0.624 -0.607,0.624 -0.316,0 -0.558,-0.28 -0.54,-0.625 0.02,-0.347 0.29,-0.629 0.608,-0.629 0.318,0 0.559,0.283 0.539,0.63" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-86-8-2" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-208.842,-68.5133)" + id="g8491-04-6-5"> +<circle + d="m -656.666,-115.971 c -0.01,0.395 -0.311,0.714 -0.664,0.714 -0.353,-10e-4 -0.63,-0.32 -0.618,-0.716 0.01,-0.398 0.309,-0.721 0.664,-0.72 0.356,0 0.633,0.324 0.618,0.722" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-88-2-3" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -656.743,-116.012 c -0.01,0.363 -0.285,0.656 -0.609,0.656 -0.324,-0 -0.578,-0.294 -0.567,-0.657 0.01,-0.365 0.283,-0.661 0.609,-0.661 0.326,0 0.58,0.298 0.567,0.662" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-89-8-4" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-208.842,-61.7525)" + id="g8491-7-4-9"> +<circle + d="m -656.902,-120.976 c -0.01,0.368 -0.299,0.664 -0.64,0.664 -0.34,0 -0.607,-0.298 -0.596,-0.665 0.01,-0.37 0.297,-0.67 0.64,-0.669 0.342,0 0.609,0.301 0.596,0.67" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-76-7-0" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -656.976,-121.013 c -0.01,0.337 -0.275,0.609 -0.587,0.609 -0.312,0 -0.557,-0.273 -0.547,-0.611 0,-0.338 0.272,-0.614 0.586,-0.613 0.315,0 0.56,0.276 0.548,0.615" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-43-2-2" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +</g> +<g + style="stroke:#000000" + id="g8702-4-0" + transform="matrix(0.286023,0,0,0.286023,-404.17,-74.9126)"> +<g + style="stroke:#000000" + id="g8491-06-1" + transform="matrix(0.582162,0,0,0.582162,-208.843,-64.0062)"> +<circle + d="m -664.347,-119.245 c -0.03,0.377 -0.333,0.68 -0.677,0.68 -0.344,-0 -0.601,-0.305 -0.573,-0.681 0.03,-0.379 0.331,-0.686 0.678,-0.686 0.346,0 0.602,0.309 0.572,0.687" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-2-9" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -664.42,-119.283 c -0.03,0.345 -0.306,0.624 -0.622,0.624 -0.315,-10e-4 -0.55,-0.28 -0.525,-0.626 0.02,-0.346 0.304,-0.628 0.622,-0.628 0.317,10e-4 0.552,0.283 0.525,0.63" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-9-6" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-68.5133)" + id="g8491-8-9-2"> +<circle + d="m -667.655,-115.954 c -0.04,0.396 -0.359,0.715 -0.711,0.714 -0.352,0 -0.607,-0.32 -0.57,-0.715 0.04,-0.397 0.357,-0.72 0.712,-0.72 0.355,10e-4 0.61,0.324 0.569,0.721" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-5-08-1" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -667.729,-115.994 c -0.04,0.363 -0.329,0.656 -0.652,0.655 -0.324,0 -0.557,-0.294 -0.523,-0.656 0.03,-0.365 0.327,-0.661 0.653,-0.66 0.325,0 0.559,0.297 0.522,0.661" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-0-1-2" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-66.2597)" + id="g8491-9-3-0"> +<circle + d="m -666.028,-117.572 c -0.03,0.386 -0.345,0.697 -0.693,0.696 -0.348,0 -0.604,-0.312 -0.572,-0.698 0.03,-0.387 0.344,-0.702 0.695,-0.702 0.35,10e-4 0.606,0.316 0.57,0.704" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-6-1-7" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -666.102,-117.611 c -0.03,0.354 -0.317,0.639 -0.636,0.639 -0.319,-0 -0.554,-0.287 -0.524,-0.641 0.03,-0.355 0.315,-0.644 0.637,-0.644 0.321,0 0.556,0.29 0.523,0.646" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-3-1-3" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-64.0061)" + id="g8491-85-0-1"> +<circle + d="m -669.994,-119.238 c -0.04,0.376 -0.356,0.68 -0.7,0.68 -0.343,-10e-4 -0.589,-0.305 -0.549,-0.681 0.04,-0.378 0.354,-0.686 0.7,-0.685 0.346,0 0.592,0.308 0.549,0.686" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-61-3-1" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -670.066,-119.276 c -0.04,0.345 -0.327,0.624 -0.642,0.623 -0.315,0 -0.541,-0.279 -0.504,-0.625 0.04,-0.346 0.325,-0.628 0.642,-0.628 0.317,0 0.543,0.283 0.504,0.63" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-1-4-9" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-66.2597)" + id="g8491-5-0-0"> +<circle + d="m -667.886,-117.57 c -0.04,0.386 -0.354,0.697 -0.701,0.697 -0.348,-10e-4 -0.601,-0.312 -0.564,-0.698 0.04,-0.388 0.351,-0.702 0.702,-0.702 0.351,10e-4 0.603,0.316 0.563,0.703" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-9-3-5" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -667.96,-117.609 c -0.04,0.354 -0.324,0.64 -0.643,0.639 -0.319,0 -0.55,-0.286 -0.517,-0.64 0.03,-0.356 0.323,-0.644 0.644,-0.644 0.322,0 0.553,0.29 0.516,0.645" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-8-9-6" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-61.7525)" + id="g8491-4-1-7"> +<circle + d="m -666.425,-120.966 c -0.03,0.368 -0.336,0.664 -0.676,0.664 -0.339,-10e-4 -0.59,-0.298 -0.559,-0.665 0.03,-0.369 0.334,-0.669 0.677,-0.669 0.342,0 0.592,0.301 0.558,0.67" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-81-9-7" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -666.497,-121.003 c -0.03,0.337 -0.308,0.609 -0.62,0.609 -0.312,-10e-4 -0.541,-0.273 -0.513,-0.61 0.03,-0.339 0.307,-0.614 0.62,-0.614 0.314,10e-4 0.544,0.276 0.513,0.615" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-03-6-4" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-64.0061)" + id="g8491-0-9-0"> +<circle + d="m -666.229,-119.243 c -0.03,0.377 -0.34,0.68 -0.684,0.68 -0.344,0 -0.597,-0.305 -0.565,-0.681 0.03,-0.378 0.338,-0.685 0.685,-0.685 0.346,0 0.599,0.308 0.564,0.686" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-4-3-6" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -666.302,-119.281 c -0.03,0.345 -0.312,0.624 -0.628,0.624 -0.315,-0 -0.547,-0.28 -0.518,-0.625 0.03,-0.347 0.311,-0.629 0.628,-0.628 0.318,0 0.55,0.282 0.518,0.629" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-4-3-4" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-68.5133)" + id="g8491-44-8-7"> +<circle + d="m -665.821,-115.956 c -0.04,0.395 -0.351,0.714 -0.704,0.713 -0.352,0 -0.611,-0.32 -0.577,-0.715 0.03,-0.398 0.348,-0.72 0.703,-0.72 0.355,10e-4 0.614,0.324 0.578,0.722" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-7-0-4" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -665.896,-115.997 c -0.03,0.363 -0.322,0.656 -0.645,0.655 -0.324,0 -0.561,-0.294 -0.53,-0.656 0.03,-0.365 0.32,-0.661 0.645,-0.66 0.326,0 0.563,0.297 0.53,0.661" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-6-5-8" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-208.842,-66.2597)" + id="g8491-3-6-5"> +<circle + d="m -664.17,-117.575 c -0.03,0.386 -0.339,0.697 -0.687,0.697 -0.348,-0 -0.607,-0.313 -0.579,-0.698 0.03,-0.388 0.336,-0.703 0.687,-0.702 0.351,0 0.61,0.316 0.579,0.703" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-1-6-8" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -664.245,-117.614 c -0.03,0.354 -0.31,0.64 -0.629,0.639 -0.32,0 -0.558,-0.287 -0.532,-0.641 0.03,-0.355 0.309,-0.644 0.63,-0.643 0.322,0 0.56,0.29 0.531,0.645" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-7-4-2" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-61.7525)" + id="g8491-59-0-6"> +<circle + d="m -668.332,-120.964 c -0.04,0.367 -0.343,0.664 -0.683,0.664 -0.339,-0 -0.586,-0.298 -0.551,-0.665 0.04,-0.369 0.341,-0.669 0.683,-0.669 0.343,0 0.589,0.301 0.551,0.67" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-62-04-0" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -668.403,-121.001 c -0.03,0.337 -0.315,0.609 -0.627,0.609 -0.311,-0 -0.538,-0.273 -0.506,-0.61 0.03,-0.339 0.313,-0.614 0.627,-0.614 0.314,0 0.54,0.276 0.506,0.615" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-17-6-6" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-61.7525)" + id="g8491-857-2-6"> +<circle + d="m -670.238,-120.962 c -0.04,0.367 -0.351,0.664 -0.69,0.664 -0.34,-0 -0.583,-0.298 -0.544,-0.665 0.04,-0.369 0.348,-0.669 0.691,-0.669 0.342,0 0.585,0.301 0.543,0.67" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-41-6-4" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -670.309,-120.999 c -0.04,0.337 -0.322,0.609 -0.633,0.608 -0.312,0 -0.535,-0.272 -0.499,-0.609 0.04,-0.339 0.319,-0.614 0.633,-0.613 0.314,0 0.537,0.275 0.499,0.614" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-85-7-6" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-66.2597)" + id="g8491-97-5-2"> +<circle + d="m -669.744,-117.567 c -0.04,0.386 -0.361,0.697 -0.709,0.696 -0.348,0 -0.597,-0.312 -0.556,-0.698 0.04,-0.387 0.359,-0.702 0.71,-0.701 0.35,0 0.599,0.316 0.555,0.703" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-53-6-8" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -669.817,-117.606 c -0.04,0.354 -0.331,0.639 -0.65,0.639 -0.32,-0 -0.548,-0.287 -0.51,-0.641 0.04,-0.355 0.329,-0.644 0.651,-0.643 0.321,0 0.549,0.29 0.509,0.645" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-88-9-9" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-68.5133)" + id="g8491-31-8-6"> +<circle + d="m -669.488,-115.951 c -0.04,0.396 -0.367,0.715 -0.719,0.714 -0.352,0 -0.603,-0.32 -0.561,-0.715 0.04,-0.397 0.364,-0.72 0.719,-0.72 0.355,10e-4 0.606,0.324 0.561,0.721" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-89-7-0" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -669.562,-115.991 c -0.04,0.363 -0.336,0.655 -0.659,0.655 -0.323,0 -0.554,-0.294 -0.515,-0.656 0.04,-0.365 0.334,-0.661 0.66,-0.66 0.325,0 0.555,0.297 0.514,0.661" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-64-2-7" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-64.0061)" + id="g8491-33-8-0"> +<circle + d="m -668.112,-119.24 c -0.04,0.376 -0.348,0.68 -0.692,0.679 -0.343,0 -0.593,-0.304 -0.557,-0.681 0.04,-0.378 0.346,-0.685 0.693,-0.685 0.346,0 0.595,0.309 0.556,0.687" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-3-2-1" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -668.184,-119.279 c -0.04,0.346 -0.32,0.624 -0.635,0.624 -0.315,0 -0.544,-0.28 -0.511,-0.625 0.03,-0.347 0.317,-0.628 0.635,-0.628 0.318,0 0.546,0.283 0.511,0.629" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-86-9-0" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-208.842,-68.5133)" + id="g8491-04-9-1"> +<circle + d="m -663.989,-115.959 c -0.03,0.395 -0.343,0.714 -0.696,0.713 -0.352,0 -0.615,-0.32 -0.586,-0.715 0.03,-0.398 0.341,-0.72 0.696,-0.72 0.355,10e-4 0.617,0.324 0.586,0.722" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-88-6-3" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -664.065,-116 c -0.03,0.363 -0.314,0.656 -0.638,0.655 -0.323,0 -0.563,-0.294 -0.537,-0.656 0.03,-0.365 0.313,-0.661 0.638,-0.66 0.326,0 0.566,0.297 0.537,0.661" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-89-0-7" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-208.842,-61.7525)" + id="g8491-7-2-7"> +<circle + d="m -664.519,-120.968 c -0.03,0.368 -0.329,0.664 -0.669,0.664 -0.34,-0 -0.594,-0.298 -0.567,-0.665 0.03,-0.369 0.327,-0.669 0.669,-0.669 0.343,0 0.596,0.301 0.567,0.67" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-76-76-2" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -664.592,-121.005 c -0.03,0.337 -0.302,0.609 -0.613,0.609 -0.312,-0 -0.545,-0.273 -0.52,-0.611 0.02,-0.338 0.3,-0.613 0.613,-0.613 0.314,0 0.547,0.276 0.52,0.615" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-43-1-6" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +</g> +<g + style="stroke:#000000" + id="g8702-3-4" + transform="matrix(0.286023,0,0,0.286023,-401.592,-74.9126)"> +<g + style="stroke:#000000" + id="g8491-2-5" + transform="matrix(0.582162,0,0,0.582162,-208.843,-64.0062)"> +<circle + d="m -671.877,-119.236 c -0.05,0.377 -0.363,0.68 -0.707,0.68 -0.344,-0 -0.586,-0.305 -0.542,-0.681 0.04,-0.378 0.361,-0.685 0.707,-0.685 0.347,10e-4 0.589,0.308 0.542,0.686" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-15-2" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -671.949,-119.274 c -0.04,0.345 -0.333,0.624 -0.648,0.623 -0.315,0 -0.538,-0.279 -0.497,-0.624 0.04,-0.347 0.331,-0.629 0.648,-0.628 0.318,0 0.54,0.282 0.497,0.629" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-99-0" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-68.5133)" + id="g8491-8-1-2"> +<circle + d="m -674.992,-115.942 c -0.06,0.395 -0.39,0.714 -0.742,0.714 -0.352,-0 -0.592,-0.32 -0.537,-0.716 0.06,-0.396 0.388,-0.719 0.743,-0.718 0.355,0 0.595,0.324 0.536,0.72" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-5-4-9" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -675.064,-115.982 c -0.05,0.363 -0.358,0.655 -0.681,0.655 -0.323,-0 -0.543,-0.294 -0.493,-0.657 0.05,-0.364 0.357,-0.659 0.682,-0.659 0.325,0 0.545,0.297 0.492,0.661" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-0-9-0" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-66.2597)" + id="g8491-9-1-9"> +<circle + d="m -673.462,-117.562 c -0.05,0.386 -0.377,0.697 -0.724,0.696 -0.348,0 -0.59,-0.312 -0.54,-0.697 0.05,-0.388 0.374,-0.702 0.725,-0.702 0.35,0 0.591,0.316 0.539,0.703" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-6-0-9" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -673.534,-117.601 c -0.05,0.354 -0.346,0.639 -0.665,0.639 -0.319,-10e-4 -0.54,-0.287 -0.495,-0.64 0.05,-0.356 0.344,-0.644 0.665,-0.644 0.321,10e-4 0.543,0.29 0.495,0.645" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-3-7-4" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-64.0061)" + id="g8491-85-5-5"> +<circle + d="m -677.531,-119.229 c -0.06,0.376 -0.385,0.68 -0.729,0.679 -0.343,0 -0.575,-0.304 -0.519,-0.68 0.06,-0.378 0.384,-0.685 0.73,-0.685 0.346,0 0.578,0.309 0.518,0.686" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-61-8-1" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -677.601,-119.267 c -0.05,0.345 -0.354,0.624 -0.669,0.623 -0.315,0 -0.528,-0.279 -0.476,-0.624 0.05,-0.347 0.353,-0.628 0.67,-0.628 0.317,0 0.53,0.283 0.475,0.629" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-1-7-0" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-66.2597)" + id="g8491-5-04-3"> +<circle + d="m -675.323,-117.559 c -0.06,0.385 -0.384,0.696 -0.732,0.696 -0.347,-0 -0.585,-0.312 -0.531,-0.698 0.05,-0.387 0.382,-0.701 0.732,-0.701 0.35,0 0.588,0.316 0.531,0.703" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-9-8-7" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -675.395,-117.598 c -0.05,0.353 -0.352,0.639 -0.671,0.638 -0.318,0 -0.537,-0.286 -0.487,-0.64 0.05,-0.355 0.35,-0.643 0.671,-0.643 0.321,10e-4 0.539,0.29 0.487,0.645" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-8-0-8" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-61.7525)" + id="g8491-4-4-8"> +<circle + d="m -674.053,-120.958 c -0.05,0.367 -0.365,0.664 -0.704,0.663 -0.34,0 -0.576,-0.297 -0.529,-0.664 0.05,-0.369 0.363,-0.669 0.705,-0.668 0.342,0 0.579,0.3 0.528,0.669" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-81-2-6" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -674.123,-120.995 c -0.05,0.337 -0.335,0.609 -0.646,0.608 -0.312,0 -0.529,-0.272 -0.485,-0.609 0.04,-0.339 0.333,-0.614 0.646,-0.613 0.314,0 0.531,0.275 0.485,0.614" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-03-9-0" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-64.0061)" + id="g8491-0-6-4"> +<circle + d="m -673.761,-119.233 c -0.05,0.376 -0.371,0.679 -0.714,0.679 -0.344,0 -0.583,-0.305 -0.535,-0.681 0.05,-0.378 0.369,-0.685 0.715,-0.684 0.347,0 0.585,0.308 0.534,0.686" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-4-1-6" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -673.832,-119.272 c -0.05,0.346 -0.34,0.624 -0.655,0.624 -0.315,-0 -0.535,-0.28 -0.49,-0.625 0.04,-0.347 0.338,-0.628 0.655,-0.628 0.318,10e-4 0.537,0.283 0.49,0.629" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-4-0-7" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-68.5133)" + id="g8491-44-4-6"> +<circle + d="m -673.156,-115.945 c -0.05,0.396 -0.382,0.714 -0.734,0.714 -0.352,-0 -0.597,-0.32 -0.546,-0.716 0.05,-0.396 0.381,-0.719 0.735,-0.718 0.355,0 0.599,0.323 0.545,0.72" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-7-2-0" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -673.229,-115.985 c -0.05,0.363 -0.351,0.655 -0.674,0.655 -0.322,-0 -0.546,-0.294 -0.5,-0.657 0.05,-0.364 0.349,-0.659 0.674,-0.659 0.326,0 0.549,0.297 0.5,0.661" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-6-2-9" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-208.842,-66.2597)" + id="g8491-3-2-7"> +<circle + d="m -671.603,-117.564 c -0.05,0.385 -0.369,0.696 -0.717,0.696 -0.347,-0 -0.593,-0.312 -0.547,-0.698 0.04,-0.387 0.367,-0.702 0.717,-0.701 0.35,0 0.595,0.315 0.547,0.703" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-1-05-5" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -671.676,-117.604 c -0.04,0.354 -0.338,0.64 -0.657,0.639 -0.319,0 -0.544,-0.286 -0.502,-0.64 0.04,-0.355 0.336,-0.644 0.658,-0.643 0.321,0 0.545,0.289 0.501,0.644" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-7-5-9" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-61.7525)" + id="g8491-59-2-7"> +<circle + d="m -675.961,-120.956 c -0.05,0.367 -0.373,0.664 -0.712,0.663 -0.339,0 -0.573,-0.297 -0.522,-0.664 0.05,-0.369 0.371,-0.669 0.713,-0.668 0.342,0 0.575,0.3 0.521,0.669" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-62-9-8" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -676.031,-120.993 c -0.05,0.337 -0.342,0.609 -0.653,0.608 -0.311,0 -0.526,-0.272 -0.478,-0.609 0.05,-0.339 0.34,-0.613 0.653,-0.613 0.313,0 0.527,0.276 0.478,0.614" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-17-0-5" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-61.7525)" + id="g8491-857-28-3"> +<circle + d="m -677.87,-120.954 c -0.06,0.367 -0.379,0.664 -0.719,0.663 -0.339,0 -0.569,-0.297 -0.514,-0.664 0.06,-0.369 0.378,-0.669 0.72,-0.668 0.342,0 0.572,0.3 0.513,0.669" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-41-3-3" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -677.939,-120.991 c -0.05,0.337 -0.349,0.609 -0.66,0.608 -0.311,0 -0.522,-0.272 -0.471,-0.609 0.05,-0.339 0.347,-0.613 0.66,-0.613 0.313,0 0.524,0.276 0.471,0.614" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-85-8-8" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-66.2597)" + id="g8491-97-0-3"> +<circle + d="m -677.183,-117.556 c -0.06,0.385 -0.392,0.696 -0.739,0.695 -0.348,0 -0.583,-0.312 -0.524,-0.697 0.06,-0.387 0.39,-0.702 0.74,-0.701 0.35,0 0.584,0.316 0.523,0.703" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-53-4-7" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -677.254,-117.596 c -0.06,0.354 -0.36,0.639 -0.678,0.639 -0.319,0 -0.534,-0.286 -0.481,-0.64 0.05,-0.355 0.358,-0.644 0.679,-0.643 0.321,0 0.536,0.289 0.48,0.644" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-88-09-9" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-68.5133)" + id="g8491-31-1-3"> +<circle + d="m -676.827,-115.939 c -0.06,0.395 -0.398,0.714 -0.75,0.713 -0.352,0 -0.589,-0.319 -0.529,-0.715 0.06,-0.396 0.396,-0.719 0.751,-0.718 0.354,0 0.591,0.324 0.528,0.72" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-89-9-7" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -676.899,-115.979 c -0.06,0.363 -0.365,0.655 -0.688,0.654 -0.322,0 -0.54,-0.293 -0.485,-0.656 0.05,-0.364 0.363,-0.659 0.688,-0.659 0.325,0 0.542,0.297 0.485,0.661" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-64-62-8" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-64.0061)" + id="g8491-33-5-7"> +<circle + d="m -675.646,-119.231 c -0.06,0.376 -0.378,0.68 -0.722,0.679 -0.343,0 -0.579,-0.304 -0.526,-0.681 0.05,-0.377 0.376,-0.684 0.722,-0.684 0.346,0 0.582,0.308 0.526,0.686" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-3-4-4" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -675.717,-119.269 c -0.05,0.345 -0.347,0.623 -0.662,0.623 -0.315,0 -0.531,-0.28 -0.483,-0.625 0.05,-0.346 0.346,-0.628 0.663,-0.627 0.317,0 0.533,0.282 0.482,0.629" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-86-4-1" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-208.842,-68.5133)" + id="g8491-04-99-9"> +<circle + d="m -671.322,-115.948 c -0.05,0.396 -0.374,0.714 -0.726,0.714 -0.353,-0 -0.6,-0.32 -0.554,-0.715 0.05,-0.397 0.373,-0.72 0.727,-0.72 0.355,10e-4 0.603,0.324 0.553,0.721" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-88-3-0" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -671.395,-115.988 c -0.05,0.363 -0.344,0.655 -0.667,0.655 -0.323,-0 -0.55,-0.294 -0.507,-0.657 0.04,-0.364 0.341,-0.659 0.667,-0.659 0.325,0 0.552,0.297 0.507,0.661" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-89-6-9" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-208.842,-61.7525)" + id="g8491-7-0-8"> +<circle + d="m -672.145,-120.96 c -0.05,0.367 -0.358,0.664 -0.698,0.663 -0.339,0 -0.579,-0.297 -0.536,-0.664 0.04,-0.369 0.356,-0.669 0.698,-0.669 0.342,10e-4 0.582,0.301 0.536,0.67" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-76-5-8" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -672.216,-120.997 c -0.04,0.337 -0.328,0.609 -0.64,0.608 -0.311,0 -0.531,-0.272 -0.492,-0.609 0.04,-0.339 0.327,-0.614 0.64,-0.613 0.314,0 0.534,0.275 0.492,0.614" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-43-0-5" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +</g> +</g> +<g + style="stroke:#000000" + transform="translate(381.283,-51.2827)" + id="g9671-9-4" + inkscape:path-effect="#path-effect9673-2-8"> +<g + style="stroke:#000000" + transform="matrix(0.286023,0,0,0.286023,-409.326,-74.9126)" + id="g8702-43-3"> +<g + style="stroke:#000000" + id="g8491-51-7" + transform="matrix(0.582162,0,0,0.582162,-208.843,-64.0062)"> +<circle + d="m -646.646,-120.812 c 0.02,0.381 -0.223,0.689 -0.537,0.689 -0.313,0 -0.584,-0.308 -0.604,-0.688 -0.02,-0.383 0.22,-0.694 0.536,-0.694 0.316,0 0.587,0.311 0.605,0.693" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-74-1" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -646.718,-120.85 c 0.02,0.349 -0.204,0.632 -0.492,0.632 -0.287,-10e-4 -0.535,-0.283 -0.554,-0.632 -0.02,-0.351 0.202,-0.636 0.491,-0.636 0.29,-10e-4 0.538,0.285 0.555,0.636" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-31-3" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-68.5133)" + id="g8491-8-46-8"> +<circle + d="m -650.865,-117.594 c 0.01,0.402 -0.243,0.727 -0.566,0.728 -0.323,0 -0.596,-0.323 -0.608,-0.724 -0.01,-0.404 0.24,-0.733 0.566,-0.734 0.325,-0 0.598,0.327 0.608,0.73" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-5-9-0" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -650.938,-117.634 c 0.01,0.368 -0.223,0.667 -0.519,0.667 -0.297,0 -0.546,-0.296 -0.558,-0.664 -0.01,-0.37 0.22,-0.672 0.519,-0.673 0.299,-0 0.549,0.3 0.558,0.67" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-0-4-9" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-66.2597)" + id="g8491-9-2-7"> +<circle + d="m -648.78,-119.171 c 0.01,0.391 -0.232,0.707 -0.551,0.707 -0.318,0 -0.589,-0.315 -0.606,-0.706 -0.02,-0.392 0.23,-0.712 0.551,-0.713 0.321,-0 0.592,0.319 0.606,0.712" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-6-2-9" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -648.852,-119.211 c 0.01,0.359 -0.213,0.649 -0.505,0.649 -0.292,10e-4 -0.541,-0.289 -0.556,-0.647 -0.02,-0.36 0.21,-0.654 0.504,-0.654 0.295,-0 0.544,0.292 0.557,0.652" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-3-64-9" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-64.0061)" + id="g8491-85-1-3"> +<circle + d="m -652.794,-120.812 c 0,0.383 -0.246,0.691 -0.562,0.691 -0.315,0 -0.578,-0.309 -0.586,-0.691 -0,-0.383 0.243,-0.696 0.561,-0.696 0.318,0 0.581,0.312 0.587,0.696" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-61-28-2" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -652.865,-120.85 c 0,0.35 -0.225,0.633 -0.515,0.633 -0.29,0 -0.53,-0.283 -0.538,-0.633 -0,-0.352 0.223,-0.639 0.515,-0.639 0.291,0 0.532,0.286 0.538,0.639" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-1-8-4" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-66.2597)" + id="g8491-5-9-3"> +<circle + d="m -650.806,-119.174 c 0.01,0.392 -0.241,0.708 -0.56,0.709 -0.319,0 -0.588,-0.316 -0.6,-0.707 -0.01,-0.393 0.238,-0.714 0.559,-0.714 0.322,-0 0.591,0.319 0.601,0.712" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-9-2-7" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -650.878,-119.214 c 0,0.36 -0.221,0.65 -0.514,0.651 -0.292,0 -0.539,-0.29 -0.55,-0.649 -0.01,-0.361 0.218,-0.654 0.513,-0.655 0.295,0 0.541,0.293 0.551,0.653" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-8-8-1" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-61.7525)" + id="g8491-4-8-2"> +<circle + d="m -648.622,-122.507 c 0.01,0.372 -0.227,0.672 -0.538,0.671 -0.31,0 -0.574,-0.301 -0.59,-0.673 -0.02,-0.373 0.225,-0.677 0.537,-0.676 0.313,0 0.578,0.304 0.591,0.678" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-81-8-2" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -648.692,-122.545 c 0.01,0.341 -0.209,0.617 -0.494,0.616 -0.284,0 -0.527,-0.276 -0.541,-0.617 -0.01,-0.343 0.206,-0.621 0.493,-0.621 0.287,0 0.529,0.279 0.542,0.622" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-03-68-0" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-64.0061)" + id="g8491-0-3-2"> +<circle + d="m -648.7,-120.812 c 0.01,0.382 -0.23,0.69 -0.544,0.69 -0.314,0 -0.582,-0.308 -0.598,-0.69 -0.02,-0.382 0.227,-0.694 0.544,-0.694 0.317,0 0.585,0.311 0.598,0.694" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-4-8-1" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -648.771,-120.85 c 0.01,0.35 -0.211,0.632 -0.499,0.632 -0.289,0 -0.534,-0.282 -0.549,-0.632 -0.01,-0.351 0.208,-0.637 0.499,-0.637 0.29,0 0.536,0.285 0.549,0.637" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-4-33-7" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-206.589,-68.5133)" + id="g8491-44-3-5"> +<circle + d="m -648.862,-117.589 c 0.01,0.402 -0.235,0.726 -0.558,0.727 -0.322,10e-4 -0.597,-0.322 -0.614,-0.723 -0.02,-0.403 0.233,-0.732 0.557,-0.733 0.325,-0 0.601,0.326 0.615,0.729" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-7-8-1" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -648.935,-117.629 c 0.01,0.368 -0.216,0.666 -0.512,0.667 -0.295,0 -0.547,-0.296 -0.563,-0.664 -0.02,-0.369 0.213,-0.671 0.511,-0.672 0.298,-0 0.55,0.299 0.564,0.669" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-6-0-7" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-208.842,-66.2597)" + id="g8491-3-4-4"> +<circle + d="m -646.75,-119.169 c 0.02,0.391 -0.225,0.707 -0.542,0.707 -0.318,0 -0.592,-0.315 -0.612,-0.705 -0.02,-0.392 0.221,-0.712 0.541,-0.713 0.321,0 0.595,0.319 0.613,0.711" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-1-7-1" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -646.822,-119.208 c 0.02,0.358 -0.207,0.648 -0.498,0.648 -0.291,0 -0.542,-0.289 -0.561,-0.647 -0.02,-0.359 0.203,-0.653 0.497,-0.653 0.293,-0 0.545,0.292 0.562,0.652" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-7-6-7" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-61.7525)" + id="g8491-59-8-1"> +<circle + d="m -650.694,-122.505 c 0.01,0.373 -0.235,0.673 -0.546,0.672 -0.311,0 -0.573,-0.301 -0.585,-0.673 -0.01,-0.374 0.232,-0.678 0.546,-0.678 0.314,0 0.576,0.305 0.585,0.679" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-62-90-1" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -650.764,-122.543 c 0,0.342 -0.216,0.618 -0.501,0.617 -0.286,0 -0.526,-0.277 -0.537,-0.618 -0.01,-0.343 0.214,-0.622 0.501,-0.621 0.288,0 0.528,0.279 0.537,0.622" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-17-68-1" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-61.7525)" + id="g8491-857-7-7"> +<circle + d="m -652.761,-122.503 c 0,0.373 -0.243,0.674 -0.555,0.673 -0.312,0 -0.571,-0.301 -0.579,-0.674 -0,-0.374 0.24,-0.678 0.554,-0.678 0.315,0 0.574,0.305 0.58,0.679" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-41-9-0" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -652.831,-122.54 c 0,0.342 -0.223,0.618 -0.509,0.617 -0.286,0 -0.524,-0.277 -0.531,-0.619 -0,-0.343 0.22,-0.622 0.508,-0.622 0.288,0 0.526,0.28 0.532,0.624" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-85-03-4" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-66.2597)" + id="g8491-97-3-0"> +<circle + d="m -652.828,-119.176 c 0,0.392 -0.249,0.708 -0.568,0.709 -0.32,0 -0.586,-0.316 -0.594,-0.708 -0,-0.393 0.245,-0.714 0.568,-0.715 0.322,0 0.588,0.32 0.594,0.714" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-53-3-8" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -652.9,-119.216 c 0,0.36 -0.228,0.65 -0.521,0.651 -0.293,0 -0.537,-0.29 -0.545,-0.65 -0,-0.361 0.226,-0.655 0.521,-0.655 0.295,-10e-4 0.54,0.293 0.545,0.654" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-88-7-5" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-202.082,-68.5133)" + id="g8491-31-3-1"> +<circle + d="m -652.862,-117.599 c 0,0.402 -0.252,0.728 -0.576,0.728 -0.324,10e-4 -0.593,-0.323 -0.602,-0.725 -0,-0.403 0.249,-0.733 0.575,-0.734 0.327,-0 0.596,0.327 0.603,0.731" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-89-2-6" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -652.935,-117.64 c 0,0.369 -0.231,0.668 -0.528,0.669 -0.297,0 -0.544,-0.297 -0.552,-0.666 -0,-0.37 0.228,-0.672 0.527,-0.673 0.3,-0 0.547,0.3 0.553,0.67" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-64-65-6" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-204.335,-64.0061)" + id="g8491-33-26-2"> +<circle + d="m -650.749,-120.812 c 0,0.382 -0.238,0.69 -0.553,0.69 -0.315,0 -0.58,-0.308 -0.593,-0.69 -0.01,-0.383 0.235,-0.695 0.553,-0.695 0.317,0 0.583,0.312 0.593,0.695" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-3-58-1" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -650.821,-120.85 c 0,0.35 -0.218,0.633 -0.507,0.633 -0.289,0 -0.532,-0.283 -0.543,-0.633 -0.01,-0.352 0.216,-0.638 0.507,-0.638 0.291,0 0.534,0.286 0.543,0.638" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-86-7-9" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-208.842,-68.5133)" + id="g8491-04-96-6"> +<circle + d="m -646.855,-117.583 c 0.02,0.401 -0.228,0.725 -0.55,0.726 -0.321,0 -0.599,-0.323 -0.62,-0.723 -0.02,-0.402 0.224,-0.731 0.548,-0.732 0.325,-0 0.603,0.326 0.622,0.729" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-88-0-4" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -646.929,-117.624 c 0.02,0.368 -0.208,0.666 -0.504,0.666 -0.295,0 -0.549,-0.295 -0.569,-0.663 -0.02,-0.369 0.206,-0.67 0.503,-0.671 0.298,-0 0.553,0.299 0.57,0.668" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-89-4-8" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + transform="matrix(0.582163,0,0,0.582163,-208.842,-61.7525)" + id="g8491-7-1-0"> +<circle + d="m -646.546,-122.509 c 0.02,0.371 -0.22,0.671 -0.53,0.67 -0.31,0 -0.577,-0.301 -0.596,-0.672 -0.02,-0.373 0.217,-0.676 0.529,-0.676 0.312,0 0.579,0.304 0.597,0.678" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-76-0-8" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -646.617,-122.547 c 0.02,0.341 -0.202,0.616 -0.486,0.615 -0.284,0 -0.529,-0.276 -0.547,-0.617 -0.02,-0.342 0.199,-0.62 0.486,-0.62 0.286,10e-4 0.531,0.279 0.547,0.622" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-43-4-1" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +</g> +<g + style="stroke:#000000" + transform="matrix(0.286023,0,0,0.286023,-406.748,-74.9126)" + id="g8702-0-8-0"> +<g + style="stroke:#000000" + transform="matrix(0.582162,0,0,0.582162,-208.843,-64.0062)" + id="g8491-30-7-2"> +<circle + d="m -654.835,-120.812 c 0,0.383 -0.253,0.692 -0.57,0.692 -0.316,0 -0.576,-0.309 -0.58,-0.692 -0,-0.384 0.25,-0.697 0.569,-0.697 0.319,0 0.579,0.313 0.581,0.697" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-92-0-2" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -654.906,-120.851 c 0,0.351 -0.232,0.635 -0.522,0.635 -0.29,0 -0.529,-0.284 -0.533,-0.635 -0,-0.352 0.23,-0.639 0.523,-0.639 0.292,0 0.531,0.287 0.532,0.639" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-5-8-9" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + id="g8491-8-4-6-7" + transform="matrix(0.582163,0,0,0.582163,-204.335,-68.5133)"> +<circle + d="m -658.831,-117.615 c -0,0.404 -0.277,0.73 -0.603,0.731 -0.326,0 -0.587,-0.324 -0.583,-0.727 0,-0.406 0.274,-0.736 0.602,-0.737 0.329,-0 0.59,0.328 0.584,0.733" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-5-0-2-5" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -658.903,-117.655 c -0,0.37 -0.254,0.67 -0.553,0.67 -0.299,0 -0.538,-0.297 -0.535,-0.667 0,-0.372 0.251,-0.675 0.553,-0.676 0.301,-10e-4 0.541,0.301 0.535,0.673" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-0-5-4-6" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-9-9-7-4" + transform="matrix(0.582163,0,0,0.582163,-206.589,-66.2597)"> +<circle + d="m -656.859,-119.182 c -0,0.393 -0.264,0.711 -0.586,0.711 -0.321,0 -0.581,-0.316 -0.581,-0.709 -0,-0.394 0.261,-0.716 0.585,-0.717 0.324,0 0.584,0.321 0.582,0.715" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-6-4-9-6" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -656.93,-119.221 c -0,0.36 -0.243,0.652 -0.537,0.652 -0.295,0 -0.534,-0.291 -0.534,-0.651 0,-0.362 0.24,-0.656 0.537,-0.657 0.297,-0 0.536,0.294 0.534,0.656" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-3-6-3-3" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-85-9-9-7" + transform="matrix(0.582163,0,0,0.582163,-202.082,-64.0061)"> +<circle + d="m -660.934,-120.812 c -0.01,0.384 -0.277,0.694 -0.595,0.694 -0.319,0 -0.571,-0.31 -0.563,-0.694 0,-0.385 0.274,-0.699 0.595,-0.699 0.321,0 0.573,0.313 0.563,0.699" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-61-2-2-9" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -661.004,-120.851 c -0,0.352 -0.254,0.637 -0.546,0.637 -0.292,0 -0.523,-0.285 -0.516,-0.637 0,-0.353 0.252,-0.641 0.546,-0.641 0.294,0 0.526,0.287 0.516,0.641" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-1-2-8-7" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-5-4-3-4" + transform="matrix(0.582163,0,0,0.582163,-204.335,-66.2597)"> +<circle + d="m -658.869,-119.184 c -0,0.393 -0.272,0.711 -0.594,0.711 -0.322,0 -0.58,-0.316 -0.576,-0.71 0,-0.394 0.27,-0.716 0.594,-0.717 0.325,-10e-4 0.583,0.321 0.576,0.716" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-9-7-0-9" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -658.94,-119.224 c -0,0.361 -0.25,0.653 -0.545,0.653 -0.295,0 -0.532,-0.291 -0.528,-0.652 0,-0.362 0.247,-0.657 0.545,-0.657 0.297,-0 0.534,0.294 0.528,0.656" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-8-7-1-1" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-4-5-7-7" + transform="matrix(0.582163,0,0,0.582163,-206.589,-61.7525)"> +<circle + d="m -656.885,-122.498 c -0,0.374 -0.257,0.675 -0.571,0.675 -0.313,-0 -0.567,-0.303 -0.567,-0.676 0,-0.375 0.255,-0.68 0.571,-0.68 0.315,0 0.57,0.306 0.567,0.681" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-81-4-8-0" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -656.954,-122.536 c -0,0.343 -0.237,0.619 -0.524,0.619 -0.287,-0 -0.52,-0.278 -0.52,-0.62 -10e-4,-0.344 0.234,-0.624 0.523,-0.624 0.29,0 0.523,0.281 0.521,0.625" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-03-8-9-6" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-0-1-1-0" + transform="matrix(0.582163,0,0,0.582163,-206.589,-64.0061)"> +<circle + d="m -656.872,-120.812 c -0,0.383 -0.261,0.693 -0.578,0.692 -0.317,0 -0.574,-0.309 -0.575,-0.692 0,-0.384 0.259,-0.697 0.578,-0.698 0.32,0 0.577,0.313 0.575,0.698" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-4-2-5-8" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -656.942,-120.851 c -0,0.352 -0.24,0.636 -0.531,0.636 -0.291,-10e-4 -0.526,-0.284 -0.527,-0.636 0,-0.352 0.237,-0.639 0.53,-0.64 0.293,0 0.53,0.287 0.528,0.64" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-4-8-4-5" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-44-9-9-3" + transform="matrix(0.582163,0,0,0.582163,-206.589,-68.5133)"> +<circle + d="m -656.845,-117.609 c -0,0.403 -0.269,0.729 -0.594,0.73 -0.325,0 -0.589,-0.324 -0.589,-0.727 -10e-4,-0.405 0.265,-0.735 0.593,-0.736 0.328,-0 0.592,0.328 0.59,0.733" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-7-3-2-9" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -656.918,-117.65 c -0,0.37 -0.245,0.669 -0.544,0.67 -0.298,0 -0.54,-0.297 -0.54,-0.667 -0,-0.371 0.243,-0.674 0.543,-0.675 0.301,-10e-4 0.543,0.3 0.541,0.672" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-6-6-5-4" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-3-8-7-1" + transform="matrix(0.582163,0,0,0.582163,-208.842,-66.2597)"> +<circle + d="m -654.846,-119.179 c 0,0.392 -0.256,0.71 -0.577,0.71 -0.32,0 -0.583,-0.316 -0.588,-0.708 -0,-0.394 0.254,-0.716 0.577,-0.716 0.323,-0 0.587,0.32 0.588,0.714" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-1-0-4-5" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -654.917,-119.219 c 0,0.36 -0.235,0.651 -0.529,0.652 -0.294,0 -0.536,-0.29 -0.54,-0.65 -0,-0.362 0.233,-0.656 0.529,-0.657 0.296,0 0.538,0.294 0.54,0.655" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-7-2-9-4" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-59-1-9-1" + transform="matrix(0.582163,0,0,0.582163,-204.335,-61.7525)"> +<circle + d="m -658.941,-122.496 c -0,0.374 -0.266,0.676 -0.579,0.676 -0.314,-0 -0.566,-0.303 -0.562,-0.677 0,-0.375 0.263,-0.681 0.579,-0.68 0.317,0 0.568,0.306 0.562,0.681" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-62-0-4-5" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -659.01,-122.534 c -0,0.344 -0.244,0.62 -0.532,0.62 -0.288,-0 -0.518,-0.278 -0.515,-0.621 0,-0.344 0.241,-0.624 0.531,-0.624 0.29,0 0.521,0.281 0.516,0.625" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-17-5-5-5" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-857-1-9-4" + transform="matrix(0.582163,0,0,0.582163,-202.082,-61.7525)"> +<circle + d="m -660.993,-122.493 c -0.01,0.374 -0.273,0.676 -0.587,0.676 -0.315,-0 -0.564,-0.303 -0.556,-0.678 0,-0.376 0.271,-0.681 0.588,-0.681 0.317,0 0.566,0.307 0.555,0.683" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-41-1-3-9" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -661.062,-122.531 c -0,0.343 -0.25,0.62 -0.539,0.62 -0.288,-0 -0.516,-0.278 -0.509,-0.622 0,-0.344 0.248,-0.625 0.538,-0.624 0.291,0 0.519,0.281 0.51,0.626" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-85-0-5-8" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-97-8-7-3" + transform="matrix(0.582163,0,0,0.582163,-202.082,-66.2597)"> +<circle + d="m -660.874,-119.187 c -0.01,0.394 -0.28,0.712 -0.603,0.713 -0.323,0 -0.578,-0.318 -0.57,-0.711 0,-0.396 0.278,-0.718 0.604,-0.718 0.325,-10e-4 0.58,0.321 0.569,0.716" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-53-5-0-8" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -660.945,-119.227 c -0,0.362 -0.257,0.654 -0.553,0.654 -0.296,0 -0.53,-0.291 -0.522,-0.652 0,-0.363 0.255,-0.658 0.553,-0.659 0.298,0 0.532,0.294 0.522,0.657" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-88-0-8-5" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-31-6-1-2" + transform="matrix(0.582163,0,0,0.582163,-202.082,-68.5133)"> +<circle + d="m -660.812,-117.62 c -0.01,0.404 -0.285,0.731 -0.611,0.732 -0.327,0 -0.585,-0.325 -0.577,-0.729 0,-0.405 0.282,-0.736 0.611,-0.737 0.33,-0 0.588,0.328 0.577,0.734" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-89-4-9-2" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -660.884,-117.661 c -0,0.371 -0.261,0.671 -0.56,0.672 -0.3,0 -0.537,-0.298 -0.529,-0.669 0,-0.372 0.258,-0.675 0.56,-0.676 0.302,-10e-4 0.539,0.301 0.529,0.673" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-64-6-9-2" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-33-2-7-7" + transform="matrix(0.582163,0,0,0.582163,-204.335,-64.0061)"> +<circle + d="m -658.905,-120.812 c -0,0.384 -0.269,0.693 -0.587,0.693 -0.318,0 -0.573,-0.31 -0.569,-0.693 0,-0.385 0.267,-0.698 0.587,-0.698 0.32,-10e-4 0.575,0.313 0.569,0.698" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-3-5-8-0" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -658.976,-120.851 c -0,0.352 -0.246,0.636 -0.538,0.636 -0.291,0 -0.525,-0.284 -0.521,-0.636 0,-0.353 0.244,-0.64 0.538,-0.64 0.293,0 0.527,0.287 0.521,0.64" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-86-8-2-3" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-04-6-5-4" + transform="matrix(0.582163,0,0,0.582163,-208.842,-68.5133)"> +<circle + d="m -654.856,-117.604 c 0,0.403 -0.26,0.728 -0.585,0.729 -0.324,0 -0.591,-0.323 -0.595,-0.726 -0,-0.404 0.257,-0.734 0.584,-0.735 0.327,-0 0.594,0.327 0.596,0.732" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-88-2-3-6" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -654.929,-117.645 c 0,0.37 -0.238,0.669 -0.536,0.669 -0.297,0 -0.542,-0.297 -0.546,-0.666 -0,-0.371 0.236,-0.673 0.535,-0.674 0.301,-10e-4 0.545,0.3 0.547,0.671" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-89-8-4-3" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-7-4-9-6" + transform="matrix(0.582163,0,0,0.582163,-208.842,-61.7525)"> +<circle + d="m -654.825,-122.5 c 0,0.373 -0.25,0.674 -0.563,0.673 -0.312,0 -0.569,-0.302 -0.573,-0.675 -0,-0.374 0.247,-0.679 0.562,-0.679 0.315,10e-4 0.572,0.306 0.574,0.681" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-76-7-0-3" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -654.895,-122.538 c 0,0.342 -0.229,0.618 -0.516,0.618 -0.287,0 -0.522,-0.277 -0.526,-0.619 -0,-0.344 0.227,-0.623 0.516,-0.623 0.289,0 0.524,0.28 0.526,0.624" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-43-2-2-3" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +</g> +<g + style="stroke:#000000" + transform="matrix(0.286023,0,0,0.286023,-404.17,-74.9126)" + id="g8702-4-0-4"> +<g + style="stroke:#000000" + transform="matrix(0.582162,0,0,0.582162,-208.843,-64.0062)" + id="g8491-06-1-4"> +<circle + d="m -662.958,-120.812 c -0.01,0.384 -0.285,0.695 -0.604,0.694 -0.32,0 -0.569,-0.31 -0.557,-0.694 0.01,-0.386 0.282,-0.7 0.604,-0.7 0.322,0 0.571,0.314 0.557,0.7" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-2-9-3" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -663.028,-120.851 c -0.01,0.353 -0.261,0.637 -0.554,0.637 -0.293,0 -0.521,-0.285 -0.51,-0.637 0.01,-0.354 0.258,-0.642 0.553,-0.642 0.295,0 0.524,0.288 0.511,0.642" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-9-6-9" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + id="g8491-8-9-2-7" + transform="matrix(0.582163,0,0,0.582163,-204.335,-68.5133)"> +<circle + d="m -666.729,-117.636 c -0.02,0.406 -0.31,0.734 -0.639,0.735 -0.329,0 -0.578,-0.326 -0.557,-0.732 0.02,-0.407 0.307,-0.739 0.638,-0.74 0.332,-10e-4 0.582,0.33 0.558,0.737" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-5-08-1-2" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -666.8,-117.677 c -0.02,0.372 -0.284,0.673 -0.586,0.674 -0.301,0 -0.53,-0.299 -0.511,-0.671 0.02,-0.373 0.282,-0.678 0.586,-0.679 0.304,-0 0.533,0.303 0.511,0.676" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-0-1-2-5" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-9-3-0-8" + transform="matrix(0.582163,0,0,0.582163,-206.589,-66.2597)"> +<circle + d="m -664.871,-119.192 c -0.02,0.395 -0.297,0.714 -0.621,0.714 -0.324,0 -0.573,-0.318 -0.557,-0.712 0.02,-0.397 0.294,-0.72 0.621,-0.72 0.326,-0 0.576,0.321 0.557,0.718" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-6-1-7-9" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -664.941,-119.232 c -0.02,0.362 -0.273,0.655 -0.57,0.655 -0.297,0 -0.526,-0.292 -0.511,-0.653 0.02,-0.364 0.27,-0.66 0.57,-0.661 0.299,0 0.528,0.295 0.511,0.659" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-3-1-3-0" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-85-0-1-2" + transform="matrix(0.582163,0,0,0.582163,-202.082,-64.0061)"> +<circle + d="m -669.008,-120.812 c -0.03,0.385 -0.309,0.697 -0.63,0.697 -0.321,-0 -0.562,-0.312 -0.538,-0.697 0.02,-0.387 0.306,-0.703 0.63,-0.703 0.324,0 0.565,0.315 0.538,0.703" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-61-3-1-4" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -669.077,-120.851 c -0.02,0.353 -0.283,0.639 -0.578,0.639 -0.294,0 -0.515,-0.286 -0.493,-0.639 0.02,-0.355 0.281,-0.644 0.578,-0.645 0.297,0 0.518,0.289 0.493,0.645" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-1-4-9-7" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-5-0-0-6" + transform="matrix(0.582163,0,0,0.582163,-204.335,-66.2597)"> +<circle + d="m -666.864,-119.195 c -0.02,0.396 -0.305,0.715 -0.63,0.715 -0.325,0 -0.571,-0.318 -0.551,-0.713 0.02,-0.397 0.303,-0.72 0.63,-0.721 0.327,0 0.574,0.322 0.551,0.719" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-9-3-5-5" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -666.934,-119.235 c -0.02,0.363 -0.28,0.656 -0.578,0.656 -0.298,10e-4 -0.524,-0.292 -0.505,-0.654 0.02,-0.364 0.277,-0.66 0.577,-0.661 0.301,-10e-4 0.527,0.295 0.506,0.659" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-8-9-6-7" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-4-1-7-1" + transform="matrix(0.582163,0,0,0.582163,-206.589,-61.7525)"> +<circle + d="m -665.084,-122.489 c -0.02,0.376 -0.289,0.678 -0.605,0.678 -0.315,-10e-4 -0.559,-0.304 -0.543,-0.679 0.01,-0.377 0.286,-0.683 0.604,-0.683 0.319,0 0.562,0.308 0.544,0.684" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-81-9-7-3" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -665.152,-122.527 c -0.02,0.345 -0.265,0.622 -0.555,0.622 -0.29,-0 -0.513,-0.279 -0.499,-0.623 0.01,-0.345 0.263,-0.626 0.555,-0.626 0.292,0 0.515,0.282 0.499,0.627" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-03-6-4-3" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-0-9-0-3" + transform="matrix(0.582163,0,0,0.582163,-206.589,-64.0061)"> +<circle + d="m -664.979,-120.812 c -0.02,0.385 -0.293,0.695 -0.613,0.695 -0.319,0 -0.566,-0.311 -0.55,-0.695 0.02,-0.386 0.29,-0.701 0.613,-0.701 0.322,0 0.569,0.314 0.55,0.701" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-4-3-6-8" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -665.048,-120.851 c -0.02,0.353 -0.269,0.638 -0.562,0.638 -0.294,0 -0.52,-0.285 -0.505,-0.638 0.01,-0.354 0.266,-0.643 0.562,-0.643 0.295,0 0.522,0.288 0.505,0.643" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-4-3-4-5" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-44-8-7-1" + transform="matrix(0.582163,0,0,0.582163,-206.589,-68.5133)"> +<circle + d="m -664.76,-117.631 c -0.02,0.406 -0.302,0.733 -0.63,0.734 -0.328,0 -0.581,-0.325 -0.564,-0.73 0.02,-0.407 0.299,-0.739 0.629,-0.74 0.331,-10e-4 0.584,0.33 0.565,0.736" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-7-0-4-0" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -664.832,-117.671 c -0.02,0.371 -0.276,0.672 -0.577,0.673 -0.301,0 -0.533,-0.299 -0.517,-0.67 0.02,-0.373 0.274,-0.678 0.577,-0.679 0.303,-0 0.535,0.302 0.517,0.676" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-6-5-8-8" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-3-6-5-7" + transform="matrix(0.582163,0,0,0.582163,-208.842,-66.2597)"> +<circle + d="m -662.875,-119.189 c -0.01,0.394 -0.288,0.712 -0.612,0.713 -0.323,0 -0.575,-0.318 -0.563,-0.712 0.01,-0.396 0.286,-0.718 0.612,-0.719 0.326,0 0.578,0.321 0.563,0.718" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-1-6-8-6" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -662.945,-119.229 c -0.01,0.361 -0.265,0.654 -0.562,0.654 -0.296,0 -0.527,-0.291 -0.516,-0.653 0.01,-0.363 0.262,-0.659 0.561,-0.659 0.299,-0 0.53,0.294 0.517,0.658" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-7-4-2-3" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-59-0-6-5" + transform="matrix(0.582163,0,0,0.582163,-204.335,-61.7525)"> +<circle + d="m -667.124,-122.486 c -0.02,0.375 -0.297,0.678 -0.613,0.678 -0.317,-10e-4 -0.558,-0.304 -0.538,-0.68 0.02,-0.377 0.294,-0.683 0.613,-0.683 0.319,0 0.56,0.308 0.538,0.685" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-62-04-0-0" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -667.193,-122.524 c -0.02,0.344 -0.271,0.622 -0.562,0.622 -0.29,-0 -0.511,-0.279 -0.493,-0.624 0.02,-0.346 0.269,-0.627 0.562,-0.626 0.293,0 0.514,0.282 0.493,0.628" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-17-6-6-8" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-857-2-6-0" + transform="matrix(0.582163,0,0,0.582163,-202.082,-61.7525)"> +<circle + d="m -669.16,-122.484 c -0.03,0.376 -0.304,0.68 -0.621,0.679 -0.318,-10e-4 -0.556,-0.304 -0.532,-0.68 0.02,-0.378 0.302,-0.685 0.621,-0.684 0.32,0 0.558,0.307 0.532,0.685" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-41-6-4-4" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -669.228,-122.522 c -0.02,0.345 -0.279,0.623 -0.57,0.623 -0.291,-0 -0.509,-0.28 -0.488,-0.624 0.02,-0.347 0.277,-0.628 0.57,-0.628 0.294,0 0.512,0.283 0.488,0.629" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-85-7-6-1" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-97-5-2-1" + transform="matrix(0.582163,0,0,0.582163,-202.082,-66.2597)"> +<circle + d="m -668.852,-119.197 c -0.03,0.395 -0.313,0.715 -0.639,0.715 -0.325,0 -0.569,-0.318 -0.545,-0.714 0.02,-0.397 0.311,-0.721 0.639,-0.721 0.329,-0 0.572,0.322 0.545,0.72" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-53-6-8-3" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -668.922,-119.237 c -0.02,0.363 -0.287,0.656 -0.586,0.656 -0.298,0 -0.522,-0.292 -0.499,-0.655 0.02,-0.364 0.285,-0.661 0.586,-0.661 0.301,-10e-4 0.524,0.295 0.499,0.66" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-88-9-9-5" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-31-8-6-9" + transform="matrix(0.582163,0,0,0.582163,-202.082,-68.5133)"> +<circle + d="m -668.692,-117.641 c -0.03,0.406 -0.318,0.734 -0.648,0.735 -0.33,0 -0.577,-0.326 -0.551,-0.732 0.02,-0.407 0.315,-0.74 0.648,-0.741 0.332,-10e-4 0.579,0.33 0.551,0.738" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-89-7-0-3" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -668.763,-117.682 c -0.02,0.372 -0.291,0.674 -0.594,0.675 -0.302,0 -0.529,-0.3 -0.506,-0.672 0.02,-0.374 0.29,-0.679 0.595,-0.68 0.305,-0 0.531,0.303 0.505,0.677" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-64-2-7-4" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-33-8-0-1" + transform="matrix(0.582163,0,0,0.582163,-204.335,-64.0061)"> +<circle + d="m -666.996,-120.812 c -0.02,0.385 -0.301,0.696 -0.621,0.696 -0.321,0 -0.565,-0.311 -0.545,-0.696 0.02,-0.387 0.299,-0.702 0.622,-0.702 0.323,0 0.567,0.315 0.544,0.702" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-3-2-1-5" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -667.065,-120.851 c -0.02,0.353 -0.276,0.639 -0.57,0.638 -0.294,0 -0.518,-0.285 -0.499,-0.638 0.02,-0.355 0.273,-0.644 0.57,-0.644 0.296,0 0.52,0.289 0.499,0.644" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-86-9-0-0" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-04-9-1-8" + transform="matrix(0.582163,0,0,0.582163,-208.842,-68.5133)"> +<circle + d="m -662.788,-117.625 c -0.02,0.404 -0.293,0.732 -0.621,0.732 -0.327,0 -0.583,-0.325 -0.57,-0.729 0.01,-0.406 0.29,-0.738 0.62,-0.739 0.33,-10e-4 0.586,0.329 0.571,0.736" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-88-6-3-3" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -662.86,-117.666 c -0.01,0.371 -0.268,0.671 -0.569,0.672 -0.3,0 -0.535,-0.298 -0.523,-0.669 0.01,-0.373 0.266,-0.676 0.569,-0.677 0.302,-10e-4 0.537,0.301 0.523,0.674" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-89-0-7-5" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-7-2-7-6" + transform="matrix(0.582163,0,0,0.582163,-208.842,-61.7525)"> +<circle + d="m -663.04,-122.491 c -0.01,0.375 -0.281,0.677 -0.597,0.677 -0.315,-0 -0.561,-0.304 -0.549,-0.678 0.01,-0.377 0.278,-0.683 0.596,-0.682 0.317,0 0.564,0.307 0.55,0.683" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-76-76-2-5" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -663.109,-122.529 c -0.01,0.344 -0.258,0.621 -0.547,0.621 -0.289,-0 -0.515,-0.279 -0.504,-0.622 0.01,-0.345 0.255,-0.626 0.546,-0.626 0.292,0 0.518,0.282 0.505,0.627" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-43-1-6-9" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +</g> +<g + style="stroke:#000000" + transform="matrix(0.286023,0,0,0.286023,-401.592,-74.9126)" + id="g8702-3-4-9"> +<g + style="stroke:#000000" + transform="matrix(0.582162,0,0,0.582162,-208.843,-64.0062)" + id="g8491-2-5-0"> +<circle + d="m -671.016,-120.812 c -0.03,0.386 -0.316,0.697 -0.639,0.697 -0.322,0 -0.56,-0.311 -0.532,-0.697 0.03,-0.388 0.315,-0.703 0.639,-0.704 0.325,0 0.563,0.316 0.532,0.704" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-15-2-7" + cx="-595.891" + cy="-112.846" + r="1.21632" /> +<circle + d="m -671.084,-120.852 c -0.03,0.355 -0.291,0.641 -0.586,0.641 -0.296,0 -0.514,-0.286 -0.488,-0.64 0.03,-0.356 0.288,-0.645 0.586,-0.645 0.297,-0 0.516,0.289 0.488,0.644" + r="1.11528" + cy="-112.914" + cx="-595.939" + id="path8472-8-99-0-6" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +</g> +<g + style="stroke:#000000" + id="g8491-8-1-2-3" + transform="matrix(0.582163,0,0,0.582163,-204.335,-68.5133)"> +<circle + d="m -674.557,-117.657 c -0.04,0.407 -0.343,0.737 -0.675,0.738 -0.332,0 -0.57,-0.328 -0.532,-0.735 0.04,-0.409 0.341,-0.743 0.676,-0.744 0.335,-0 0.573,0.331 0.531,0.741" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-5-4-9-7" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -674.626,-117.698 c -0.04,0.373 -0.315,0.676 -0.62,0.677 -0.305,0 -0.523,-0.301 -0.488,-0.674 0.04,-0.375 0.313,-0.681 0.62,-0.682 0.307,-0 0.525,0.303 0.488,0.679" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-0-9-0-6" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-9-1-9-1" + transform="matrix(0.582163,0,0,0.582163,-206.589,-66.2597)"> +<circle + d="m -672.815,-119.203 c -0.04,0.397 -0.33,0.717 -0.657,0.718 -0.327,0 -0.565,-0.32 -0.532,-0.716 0.03,-0.398 0.327,-0.722 0.657,-0.723 0.33,-10e-4 0.568,0.323 0.532,0.721" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-6-0-9-5" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -672.884,-119.243 c -0.03,0.364 -0.303,0.658 -0.603,0.659 -0.3,0 -0.518,-0.294 -0.488,-0.657 0.03,-0.365 0.3,-0.663 0.603,-0.663 0.302,-0 0.52,0.296 0.488,0.661" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-3-7-4-0" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-85-5-5-6" + transform="matrix(0.582163,0,0,0.582163,-202.082,-64.0061)"> +<circle + d="m -677.015,-120.813 c -0.04,0.388 -0.341,0.701 -0.665,0.7 -0.325,0 -0.555,-0.312 -0.514,-0.7 0.04,-0.388 0.339,-0.705 0.666,-0.705 0.327,0 0.556,0.316 0.513,0.705" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-61-8-1-5" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -677.083,-120.852 c -0.04,0.356 -0.313,0.643 -0.61,0.642 -0.298,0 -0.508,-0.287 -0.471,-0.642 0.04,-0.356 0.311,-0.647 0.61,-0.647 0.3,0 0.511,0.29 0.471,0.647" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-1-7-0-0" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-5-04-3-8" + transform="matrix(0.582163,0,0,0.582163,-204.335,-66.2597)"> +<circle + d="m -674.791,-119.205 c -0.04,0.397 -0.338,0.718 -0.666,0.718 -0.328,0 -0.563,-0.32 -0.526,-0.717 0.04,-0.398 0.336,-0.723 0.666,-0.724 0.331,0 0.566,0.324 0.526,0.723" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-9-8-7-1" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -674.86,-119.245 c -0.04,0.364 -0.31,0.658 -0.611,0.659 -0.301,0 -0.516,-0.294 -0.482,-0.658 0.03,-0.365 0.308,-0.663 0.611,-0.664 0.303,0 0.519,0.297 0.482,0.663" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-8-0-8-2" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-4-4-8-2" + transform="matrix(0.582163,0,0,0.582163,-206.589,-61.7525)"> +<circle + d="m -673.218,-122.479 c -0.04,0.377 -0.321,0.681 -0.639,0.68 -0.319,0 -0.552,-0.305 -0.52,-0.682 0.03,-0.378 0.317,-0.686 0.639,-0.685 0.321,0 0.554,0.308 0.52,0.687" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-81-2-6-6" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -673.286,-122.517 c -0.03,0.345 -0.293,0.624 -0.586,0.624 -0.292,-0 -0.506,-0.28 -0.477,-0.626 0.03,-0.347 0.292,-0.629 0.586,-0.629 0.295,10e-4 0.508,0.283 0.477,0.631" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-03-9-0-9" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-0-6-4-1" + transform="matrix(0.582163,0,0,0.582163,-206.589,-64.0061)"> +<circle + d="m -673.019,-120.812 c -0.03,0.386 -0.325,0.698 -0.648,0.698 -0.323,0 -0.558,-0.312 -0.526,-0.698 0.03,-0.388 0.322,-0.704 0.648,-0.704 0.325,-10e-4 0.561,0.315 0.526,0.704" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-4-1-6-0" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -673.088,-120.852 c -0.03,0.355 -0.298,0.641 -0.594,0.641 -0.296,0 -0.512,-0.286 -0.482,-0.641 0.03,-0.355 0.296,-0.645 0.594,-0.645 0.298,0 0.514,0.289 0.482,0.645" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-4-0-7-4" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-44-4-6-2" + transform="matrix(0.582163,0,0,0.582163,-206.589,-68.5133)"> +<circle + d="m -672.606,-117.652 c -0.04,0.407 -0.335,0.736 -0.666,0.737 -0.331,0 -0.572,-0.327 -0.538,-0.734 0.03,-0.408 0.332,-0.741 0.666,-0.743 0.334,-0 0.575,0.331 0.538,0.74" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-7-2-0-7" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -672.676,-117.693 c -0.03,0.373 -0.307,0.676 -0.611,0.676 -0.304,10e-4 -0.525,-0.3 -0.494,-0.673 0.03,-0.375 0.305,-0.68 0.612,-0.681 0.306,-0 0.527,0.303 0.493,0.678" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-6-2-9-4" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-3-2-7-0" + transform="matrix(0.582163,0,0,0.582163,-208.842,-66.2597)"> +<circle + d="m -670.836,-119.2 c -0.03,0.396 -0.321,0.716 -0.648,0.717 -0.326,0 -0.567,-0.319 -0.538,-0.715 0.03,-0.398 0.319,-0.722 0.648,-0.723 0.329,0 0.57,0.323 0.538,0.721" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-1-05-5-3" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -670.905,-119.24 c -0.03,0.363 -0.295,0.657 -0.595,0.657 -0.299,0 -0.52,-0.292 -0.493,-0.656 0.03,-0.364 0.292,-0.661 0.594,-0.662 0.301,0 0.523,0.296 0.494,0.661" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-7-5-9-2" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-59-2-7-8" + transform="matrix(0.582163,0,0,0.582163,-204.335,-61.7525)"> +<circle + d="m -675.243,-122.477 c -0.04,0.378 -0.328,0.682 -0.647,0.681 -0.319,0 -0.55,-0.305 -0.514,-0.682 0.04,-0.379 0.326,-0.687 0.648,-0.687 0.322,0 0.552,0.309 0.513,0.688" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-62-9-8-0" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -675.31,-122.515 c -0.04,0.346 -0.301,0.625 -0.594,0.625 -0.293,-0 -0.504,-0.28 -0.471,-0.626 0.03,-0.348 0.299,-0.63 0.594,-0.63 0.295,0 0.506,0.283 0.471,0.631" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-17-0-5-3" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-857-28-3-7" + transform="matrix(0.582163,0,0,0.582163,-202.082,-61.7525)"> +<circle + d="m -677.262,-122.474 c -0.04,0.377 -0.336,0.682 -0.656,0.681 -0.32,0 -0.547,-0.305 -0.508,-0.683 0.04,-0.379 0.334,-0.687 0.657,-0.687 0.322,0 0.55,0.309 0.507,0.689" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-41-3-3-0" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -677.329,-122.513 c -0.04,0.347 -0.308,0.626 -0.601,0.626 -0.294,-0 -0.503,-0.281 -0.466,-0.627 0.04,-0.348 0.306,-0.631 0.602,-0.63 0.295,0 0.504,0.283 0.465,0.631" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-85-8-8-0" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-97-0-3-3" + transform="matrix(0.582163,0,0,0.582163,-202.082,-66.2597)"> +<circle + d="m -676.762,-119.208 c -0.04,0.398 -0.347,0.719 -0.675,0.719 -0.328,0 -0.561,-0.32 -0.519,-0.717 0.04,-0.399 0.344,-0.724 0.675,-0.725 0.331,-0 0.564,0.324 0.519,0.723" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-53-4-7-4" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -676.831,-119.248 c -0.04,0.365 -0.317,0.659 -0.619,0.66 -0.301,0 -0.514,-0.294 -0.476,-0.659 0.04,-0.366 0.316,-0.664 0.619,-0.664 0.304,-0 0.517,0.297 0.476,0.663" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-88-09-9-9" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-31-1-3-2" + transform="matrix(0.582163,0,0,0.582163,-202.082,-68.5133)"> +<circle + d="m -676.502,-117.663 c -0.05,0.408 -0.352,0.738 -0.685,0.739 -0.333,0 -0.568,-0.328 -0.525,-0.735 0.04,-0.41 0.35,-0.744 0.685,-0.745 0.336,-0 0.571,0.332 0.525,0.741" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-89-9-7-7" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -676.572,-117.704 c -0.04,0.375 -0.323,0.677 -0.628,0.678 -0.305,10e-4 -0.521,-0.301 -0.482,-0.675 0.04,-0.375 0.321,-0.682 0.629,-0.683 0.308,-0 0.523,0.304 0.481,0.68" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-64-62-8-4" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-33-5-7-2" + transform="matrix(0.582163,0,0,0.582163,-204.335,-64.0061)"> +<circle + d="m -675.02,-120.812 c -0.04,0.386 -0.333,0.699 -0.656,0.699 -0.324,0 -0.557,-0.313 -0.52,-0.699 0.04,-0.389 0.331,-0.705 0.657,-0.705 0.326,0 0.559,0.316 0.519,0.705" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-3-4-4-5" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -675.088,-120.852 c -0.04,0.355 -0.305,0.642 -0.602,0.642 -0.297,0 -0.51,-0.287 -0.477,-0.642 0.03,-0.356 0.304,-0.646 0.603,-0.646 0.299,0 0.512,0.29 0.476,0.646" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-86-4-1-2" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-04-99-9-4" + transform="matrix(0.582163,0,0,0.582163,-208.842,-68.5133)"> +<circle + d="m -670.651,-117.647 c -0.03,0.407 -0.327,0.736 -0.657,0.737 -0.331,0 -0.575,-0.327 -0.545,-0.733 0.03,-0.408 0.324,-0.741 0.657,-0.742 0.334,-10e-4 0.577,0.33 0.545,0.738" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-88-3-0-4" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -670.722,-117.688 c -0.03,0.373 -0.299,0.675 -0.602,0.676 -0.303,0 -0.527,-0.3 -0.5,-0.672 0.03,-0.375 0.297,-0.68 0.603,-0.681 0.305,-0 0.529,0.303 0.499,0.677" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-89-6-9-3" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +<g + style="stroke:#000000" + id="g8491-7-0-8-8" + transform="matrix(0.582163,0,0,0.582163,-208.842,-61.7525)"> +<circle + d="m -671.191,-122.481 c -0.03,0.376 -0.312,0.68 -0.63,0.679 -0.318,0 -0.554,-0.305 -0.526,-0.681 0.03,-0.378 0.309,-0.685 0.63,-0.685 0.32,0 0.556,0.308 0.526,0.687" + r="1.21632" + cy="-112.846" + cx="-595.891" + id="path8472-76-5-8-6" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /> +<circle + d="m -671.259,-122.52 c -0.03,0.346 -0.286,0.624 -0.578,0.624 -0.292,-0 -0.508,-0.28 -0.482,-0.625 0.02,-0.347 0.284,-0.628 0.577,-0.628 0.294,0 0.51,0.282 0.483,0.629" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.06877;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + id="path8472-8-43-0-5-0" + cx="-595.939" + cy="-112.914" + r="1.11528" /> +</g> +</g> +</g> +<path + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + d="m -199.721,-169.779 0.106,1.029" + id="path11140" + inkscape:connector-curvature="0" /> +<path + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + d="m -199.501,-167.402 0.109,1.003" + id="path11142" + inkscape:connector-curvature="0" /> +<path + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.075;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" + d="m -199.328,-165.183 0.09,0.911" + id="path11144" + inkscape:connector-curvature="0" /> +</g> +</svg> diff --git a/assets/decal-2.svg b/assets/decal-2.svg new file mode 100644 index 0000000..dff950e --- /dev/null +++ b/assets/decal-2.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.438 6.473" height="24.465" width="65.907"><style><![CDATA[.B{stroke:#000}.C{stroke-width:.035}]]></style><defs><filter id="A" x="-.033" width="1.066" y="-.103" height="1.207" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation=".215"/></filter></defs><rect width="15.733" height="4.999" x="1.008" y=".958" ry="1.438" paint-order="markers fill stroke" filter="url(#A)"/><rect ry="1.438" y=".78" x=".843" height="4.999" width="15.733" fill="#f2f2f2" stroke="#4d4d4d" stroke-width=".156" stroke-linejoin="round" paint-order="markers fill stroke"/><g transform="matrix(.650317 0 0 .650317 376.721 65.8344)"><path d="M-574.03-95.98l.115.117q-.057.057-.1.096-.043.038-.083.068.032.03.066.057.036.026.074.055l-.174.15q-.034-.032-.066-.064-.032-.032-.062-.062l.055.18q-.166.057-.317.102-.15.045-.315.094l-.068-.22q.045-.01.085-.02.042-.008.085-.02v-.08q0-.02-.002-.043 0-.02.002-.042-.03.026-.062.05-.032.026-.066.05l-.128-.196q.117-.074.187-.138.072-.064.125-.136h-.234v-.22h.35v-.117h-.353v-.223h.368v-.134h-.306v-.215h.306v-.208h.236v.208h.287v.215h-.287v.134h.39q-.005-.153-.006-.293 0-.142-.002-.264h.213q0 .142.002.283.002.14.008.274h.502v.223h-.487q.008.102.02.196.013.093.03.183.05-.085.09-.174.04-.09.077-.19l.168.117q-.055.143-.12.27-.064.128-.142.242.008.023.014.045.008.02.02.038.008.02.02.045.013.023.026.042.014.02.028.032.014.013.028.013.01 0 .02-.02.008-.02.013-.05.006-.034.01-.077.006-.045.013-.098l.193.15q-.006.064-.026.128-.017.066-.045.117-.028.053-.066.087-.038.034-.087.034-.08 0-.142-.045-.062-.045-.113-.117-.038-.05-.072-.12-.17.18-.398.304l-.108-.185q.132-.074.236-.157.106-.085.19-.185-.038-.14-.06-.3-.02-.16-.032-.325h-.42v.117h.393v.22zm.557-.578q-.023-.053-.043-.096-.02-.043-.043-.083-.02-.043-.05-.085-.026-.043-.064-.098l.18-.1q.042.055.07.098.03.043.05.083.023.04.04.08.02.04.04.09zm-.8.98q-.13-.136-.232-.27-.013.017-.02.023v.323q.117-.03.25-.077zm.04-.255q.034-.028.066-.062.032-.034.074-.085h-.257q.03.04.057.077.03.034.06.07z" opacity=".1" fill="#fff"/><path d="M-572.356-95.87l.02-1.443" fill="none" class="B C"/><path d="M-572.673-96.87h.818l.152.146-.046.245-.86.496-.253-.463z" fill="#fff"/><path d="M-572.237-97.212c-.15-.01-.292-.002-.43.064-.1.043-.172.103-.238.177-.326.365-.218.958.222 1.18a.77.77 0 0 0 .295.079c.106.007.212-.01.31-.047.073-.026.14-.067.202-.114.343-.27.375-.787.083-1.106-.06-.064-.132-.12-.21-.157-.072-.035-.154-.068-.234-.074m-.265.87l.276-.173.15-.094c.05-.028.107-.085.165-.095.07-.013.118.057.093.12-.022.054-.11.085-.157.115l-.385.24c-.05.032-.118.09-.18.084s-.098-.096-.13-.143l-.087-.135c-.02-.03-.04-.06-.03-.097.017-.057.084-.082.132-.047.027.02.044.054.062.08.03.046.068.092.09.142z" fill="#47e91b"/><path d="M-575.8-95.87l.02-1.444" fill="none" class="B C"/><g transform="matrix(.172489 -.018433 .018433 .172489 -487.262 -69.3142)"><path d="M-493.596-213l-.576 2.198 3.534 4.24 2.17-.12 1.067-4.722z" fill="#fff"/><path d="M-464.42-215.27v1.78c0 .28-.04.6 0 .875.02.118.154.182.227.268.133.158.22.377.182.584-.136.776-1.235.904-1.492.133-.1-.26-.02-.536.162-.74.106-.122.27-.17.28-.348.03-.574 0-1.154 0-1.727 0-.255.06-.6 0-.846-.06-.212-.367-.312-.442-.563-.05-.17.05-.38 0-.53-.114-.302-.6-.622-.818-.85-.07-.07-.183-.234-.292-.234-.1 0-.18.12-.24.18l-.558.557-1.727 1.728c-.24.238-.612.5-.68.85-.1.534.535.946.865 1.275l3.03 3.03c.36.36.814 1.066 1.408.826.263-.107.468-.4.664-.586l3.455-3.455c.262-.262.694-.562.66-.983-.04-.513-.72-.963-1.058-1.302l-2.577-2.577c-.33-.33-.73-.915-1.17-1.086-.53-.208-.893.304-1.223.634-.102.102-.327.255-.362.4-.04.138.25.34.336.425.216.216.468.582.744.716.178.1.4-.02.61.08.238.12.415.372.443.638.02.145-.06.27.04.398.192.24.442.448.66.665.104.103.214.255.346.324s.327-.02.478.03c.45.14.725.653.49 1.083-.25.46-.862.57-1.232.185-.14-.144-.215-.33-.215-.53 0-.1.05-.207.02-.292-.04-.124-.194-.23-.283-.32-.23-.23-.452-.504-.718-.69" fill="#ef5034" transform="rotate(328.992 -469.827 -165.36)"/></g><circle cx="-574.052" cy="-97.756" r=".186"/><path d="M-574.238-97.755h.37v3.222h-.37z"/><path d="M-573.023-94.584v.37h-2.116v-.37z"/><path d="M-574.168-97.488c-.488-.492-1.073-.487-1.54-.05-.1.054-.23.023-.265-.084m1.993.137c.487-.492 1.073-.487 1.54-.05.1.054.23.023.266-.084" fill="none" stroke-width=".131" stroke-linecap="round" class="B"/><path d="M-576.285-95.873l.47-1.48" class="B C"/><g fill="none" class="B"><g stroke-width=".039"><ellipse cx="-575.777" cy="-97.456" rx=".068" ry=".137"/><ellipse ry=".137" rx=".068" cy="-97.455" cx="-572.334"/></g><path d="M-575.267-95.86l-.468-1.48m2.89 1.466l.468-1.48m.554 1.48l-.468-1.48" class="C"/></g><path d="M-576.382-95.85a.65.65 0 0 0 .04.12c.01.023.07.058.07.058h1.013s.05-.033.07-.058c.02-.034.03-.117.03-.117l.05-.015v-.013h-1.328v.013zm3.436-.02s.02.084.04.12c.02.023.07.058.07.058h1.012s.05-.033.07-.058c.02-.034.03-.117.03-.117l.05-.015v-.013h-1.33v.013z"/><circle r=".186" cy="-94.4" cx="-575.125"/><circle cx="-573.023" cy="-94.398" r=".186"/><path d="M-567.934-96.85c0-.558-.118-1.102-1.393-1.102-.366 0-1.3 0-1.3 1.013 0 .516.276.85.848.9l1.032.113c.535.056.66.314.66.708 0 .446-.164.75-1.1.75-1.04 0-1.154-.394-1.154-.914h-.323c0 .713.3 1.2 1.467 1.2 1.05 0 1.435-.333 1.435-1.05 0-.867-.675-.942-.975-.975l-.994-.108c-.413-.047-.568-.244-.568-.614 0-.544.3-.746.995-.746.95 0 1.05.366 1.05.825zm3.066.718c0-.394 0-.886-1.087-.886-1 0-1.08.534-1.084.844h.3c0-.563.45-.572.774-.572.774 0 .816.258.816.54 0 .36-.15.366-.32.37l-.863.023c-.38.01-.8.206-.8.825 0 .774.63.807 1.036.807.75 0 .868-.216.962-.398a1.05 1.05 0 0 0 .06.36h.3c-.08-.16-.08-.44-.08-.516zm-.286.9c0 .45-.16.792-.928.792-.507 0-.764-.1-.764-.516 0-.202.02-.586.6-.6l.834-.02c.104 0 .207-.038.258-.103zm1.134-1.738v2.762h.3v-2.762zm.166-.682l.025.026q-.013.013-.022.02-.01.008-.018.015.007.006.014.013.008.006.016.012l-.038.033q-.007-.007-.014-.014-.007-.007-.014-.014l.012.04q-.036.013-.07.022-.033.01-.07.02l-.015-.048q.01-.002.02-.004.01-.002.02-.004v-.017q0-.005 0-.01 0-.005 0-.01-.007.006-.014.01-.007.006-.014.01l-.028-.043q.026-.016.04-.03.016-.014.027-.03h-.05v-.048h.076v-.026h-.077v-.05h.08v-.03h-.067v-.047h.067v-.046h.052v.046h.063v.047h-.063v.03h.086q-.001-.034-.001-.064 0-.03 0-.058h.047q0 .03 0 .062 0 .03.002.06h.1v.05h-.107q.002.022.004.043.003.02.007.04.01-.02.02-.038.01-.02.017-.042l.037.026q-.012.03-.027.06-.014.028-.03.053.002.005.003.01.002.004.004.008.002.005.005.01.003.005.006.01.003.004.006.007.003.003.006.003.002 0 .004-.004.002-.004.003-.01.001-.007.002-.017.001-.01.003-.02l.042.033q-.001.014-.006.028-.004.014-.01.026-.006.012-.014.02-.008.007-.02.007-.017 0-.03-.01-.014-.01-.025-.026-.008-.01-.016-.027-.037.04-.087.067l-.024-.04q.03-.016.052-.034.023-.02.042-.04-.008-.03-.013-.066-.005-.035-.007-.07h-.092v.026h.086v.048zm.122-.127q-.005-.012-.01-.02-.004-.01-.01-.018-.005-.01-.01-.02-.006-.01-.014-.02l.04-.022q.01.012.015.02.006.01.01.018.005.01.01.018.004.01.01.02zm-.175.215q-.028-.03-.05-.06-.003.004-.004.005v.07q.026-.007.055-.017zm.01-.056q.007-.006.014-.014.007-.007.016-.02h-.056q.007.01.013.017.007.007.013.015z"/></g><g transform="matrix(-.132562 0 0 -.132562 -65.5303 -33.5984)"><circle cx="-606.196" cy="-277.984" r="3.403" paint-order="markers fill stroke"/><circle r="3.403" cy="-277.926" cx="-596.374" paint-order="markers fill stroke"/><circle r="3.403" cy="-278.088" cx="-586.527" paint-order="markers fill stroke"/></g></svg> \ No newline at end of file diff --git a/assets/decal-3.svg b/assets/decal-3.svg new file mode 100644 index 0000000..494e2ad --- /dev/null +++ b/assets/decal-3.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.438 6.449" height="24.374" width="65.907"><style><![CDATA[.B{stroke:#000}.C{stroke-width:.035}]]></style><defs><filter id="A" x="-.033" width="1.066" y="-.103" height="1.207" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation=".215"/></filter></defs><rect width="15.733" height="4.999" x="1.022" y=".919" ry="1.438" paint-order="markers fill stroke" filter="url(#A)"/><rect ry="1.438" y=".741" x=".858" height="4.999" width="15.733" fill="#afc" stroke="#4d4d4d" stroke-width=".156" stroke-linejoin="round" paint-order="markers fill stroke"/><g transform="matrix(.650317 0 0 .650317 376.735 65.7952)"><path d="M-574.03-95.98l.115.117q-.057.057-.1.096-.043.038-.083.068.032.03.066.057.036.026.074.055l-.174.15q-.034-.032-.066-.064-.032-.032-.062-.062l.055.18q-.166.057-.317.102-.15.045-.315.094l-.068-.22q.045-.01.085-.02.042-.008.085-.02v-.08q0-.02-.002-.043 0-.02.002-.042-.03.026-.062.05-.032.026-.066.05l-.128-.196q.117-.074.187-.138.072-.064.125-.136h-.234v-.22h.35v-.117h-.353v-.223h.368v-.134h-.306v-.215h.306v-.208h.236v.208h.287v.215h-.287v.134h.39q-.005-.153-.006-.293 0-.142-.002-.264h.213q0 .142.002.283.002.14.008.274h.502v.223h-.487q.008.102.02.196.013.093.03.183.05-.085.09-.174.04-.09.077-.19l.168.117q-.055.143-.12.27-.064.128-.142.242.008.023.014.045.008.02.02.038.008.02.02.045.013.023.026.042.014.02.028.032.014.013.028.013.01 0 .02-.02.008-.02.013-.05.006-.034.01-.077.006-.045.013-.098l.193.15q-.006.064-.026.128-.017.066-.045.117-.028.053-.066.087-.038.034-.087.034-.08 0-.142-.045-.062-.045-.113-.117-.038-.05-.072-.12-.17.18-.398.304l-.108-.185q.132-.074.236-.157.106-.085.19-.185-.038-.14-.06-.3-.02-.16-.032-.325h-.42v.117h.393v.22zm.557-.578q-.023-.053-.043-.096-.02-.043-.043-.083-.02-.043-.05-.085-.026-.043-.064-.098l.18-.1q.042.055.07.098.03.043.05.083.023.04.04.08.02.04.04.09zm-.8.98q-.13-.136-.232-.27-.013.017-.02.023v.323q.117-.03.25-.077zm.04-.255q.034-.028.066-.062.032-.034.074-.085h-.257q.03.04.057.077.03.034.06.07z" opacity=".1" fill="#fff"/><path d="M-572.356-95.87l.02-1.443" fill="none" class="B C"/><path d="M-572.673-96.87h.818l.152.146-.046.245-.86.496-.253-.463z" fill="#fff"/><path d="M-572.237-97.212c-.15-.01-.292-.002-.43.064-.1.043-.172.103-.238.177-.326.365-.218.958.222 1.18a.77.77 0 0 0 .295.079c.106.007.212-.01.31-.047.073-.026.14-.067.202-.114.343-.27.375-.787.083-1.106-.06-.064-.132-.12-.21-.157-.072-.035-.154-.068-.234-.074m-.265.87l.276-.173.15-.094c.05-.028.107-.085.165-.095.07-.013.118.057.093.12-.022.054-.11.085-.157.115l-.385.24c-.05.032-.118.09-.18.084s-.098-.096-.13-.143l-.087-.135c-.02-.03-.04-.06-.03-.097.017-.057.084-.082.132-.047.027.02.044.054.062.08.03.046.068.092.09.142z" fill="#47e91b"/><path d="M-575.8-95.87l.02-1.444" fill="none" class="B C"/><g transform="matrix(.172489 -.018433 .018433 .172489 -487.262 -69.3142)"><path d="M-493.596-213l-.576 2.198 3.534 4.24 2.17-.12 1.067-4.722z" fill="#fff"/><path d="M-464.42-215.27v1.78c0 .28-.04.6 0 .875.02.118.154.182.227.268.133.158.22.377.182.584-.136.776-1.235.904-1.492.133-.1-.26-.02-.536.162-.74.106-.122.27-.17.28-.348.03-.574 0-1.154 0-1.727 0-.255.06-.6 0-.846-.06-.212-.367-.312-.442-.563-.05-.17.05-.38 0-.53-.114-.302-.6-.622-.818-.85-.07-.07-.183-.234-.292-.234-.1 0-.18.12-.24.18l-.558.557-1.727 1.728c-.24.238-.612.5-.68.85-.1.534.535.946.865 1.275l3.03 3.03c.36.36.814 1.066 1.408.826.263-.107.468-.4.664-.586l3.455-3.455c.262-.262.694-.562.66-.983-.04-.513-.72-.963-1.058-1.302l-2.577-2.577c-.33-.33-.73-.915-1.17-1.086-.53-.208-.893.304-1.223.634-.102.102-.327.255-.362.4-.04.138.25.34.336.425.216.216.468.582.744.716.178.1.4-.02.61.08.238.12.415.372.443.638.02.145-.06.27.04.398.192.24.442.448.66.665.104.103.214.255.346.324s.327-.02.478.03c.45.14.725.653.49 1.083-.25.46-.862.57-1.232.185-.14-.144-.215-.33-.215-.53 0-.1.05-.207.02-.292-.04-.124-.194-.23-.283-.32-.23-.23-.452-.504-.718-.69" fill="#ef5034" transform="rotate(328.992 -469.827 -165.36)"/></g><circle cx="-574.052" cy="-97.756" r=".186"/><path d="M-574.238-97.755h.37v3.222h-.37z"/><path d="M-573.023-94.584v.37h-2.116v-.37z"/><path d="M-574.168-97.488c-.488-.492-1.073-.487-1.54-.05-.1.054-.23.023-.265-.084m1.993.137c.487-.492 1.073-.487 1.54-.05.1.054.23.023.266-.084" fill="none" stroke-width=".131" stroke-linecap="round" class="B"/><path d="M-576.285-95.873l.47-1.48" class="B C"/><g fill="none" class="B"><g stroke-width=".039"><ellipse cx="-575.777" cy="-97.456" rx=".068" ry=".137"/><ellipse ry=".137" rx=".068" cy="-97.455" cx="-572.334"/></g><path d="M-575.267-95.86l-.468-1.48m2.89 1.466l.468-1.48m.554 1.48l-.468-1.48" class="C"/></g><path d="M-576.382-95.85a.65.65 0 0 0 .04.12c.01.023.07.058.07.058h1.013s.05-.033.07-.058c.02-.034.03-.117.03-.117l.05-.015v-.013h-1.328v.013zm3.436-.02s.02.084.04.12c.02.023.07.058.07.058h1.012s.05-.033.07-.058c.02-.034.03-.117.03-.117l.05-.015v-.013h-1.33v.013z"/><circle r=".186" cy="-94.4" cx="-575.125"/><circle cx="-573.023" cy="-94.398" r=".186"/><path d="M-567.934-96.85c0-.558-.118-1.102-1.393-1.102-.366 0-1.3 0-1.3 1.013 0 .516.276.85.848.9l1.032.113c.535.056.66.314.66.708 0 .446-.164.75-1.1.75-1.04 0-1.154-.394-1.154-.914h-.323c0 .713.3 1.2 1.467 1.2 1.05 0 1.435-.333 1.435-1.05 0-.867-.675-.942-.975-.975l-.994-.108c-.413-.047-.568-.244-.568-.614 0-.544.3-.746.995-.746.95 0 1.05.366 1.05.825zm3.066.718c0-.394 0-.886-1.087-.886-1 0-1.08.534-1.084.844h.3c0-.563.45-.572.774-.572.774 0 .816.258.816.54 0 .36-.15.366-.32.37l-.863.023c-.38.01-.8.206-.8.825 0 .774.63.807 1.036.807.75 0 .868-.216.962-.398a1.05 1.05 0 0 0 .06.36h.3c-.08-.16-.08-.44-.08-.516zm-.286.9c0 .45-.16.792-.928.792-.507 0-.764-.1-.764-.516 0-.202.02-.586.6-.6l.834-.02c.104 0 .207-.038.258-.103zm1.134-1.738v2.762h.3v-2.762zm.166-.682l.025.026q-.013.013-.022.02-.01.008-.018.015.007.006.014.013.008.006.016.012l-.038.033q-.007-.007-.014-.014-.007-.007-.014-.014l.012.04q-.036.013-.07.022-.033.01-.07.02l-.015-.048q.01-.002.02-.004.01-.002.02-.004v-.017q0-.005 0-.01 0-.005 0-.01-.007.006-.014.01-.007.006-.014.01l-.028-.043q.026-.016.04-.03.016-.014.027-.03h-.05v-.048h.076v-.026h-.077v-.05h.08v-.03h-.067v-.047h.067v-.046h.052v.046h.063v.047h-.063v.03h.086q-.001-.034-.001-.064 0-.03 0-.058h.047q0 .03 0 .062 0 .03.002.06h.1v.05h-.107q.002.022.004.043.003.02.007.04.01-.02.02-.038.01-.02.017-.042l.037.026q-.012.03-.027.06-.014.028-.03.053.002.005.003.01.002.004.004.008.002.005.005.01.003.005.006.01.003.004.006.007.003.003.006.003.002 0 .004-.004.002-.004.003-.01.001-.007.002-.017.001-.01.003-.02l.042.033q-.001.014-.006.028-.004.014-.01.026-.006.012-.014.02-.008.007-.02.007-.017 0-.03-.01-.014-.01-.025-.026-.008-.01-.016-.027-.037.04-.087.067l-.024-.04q.03-.016.052-.034.023-.02.042-.04-.008-.03-.013-.066-.005-.035-.007-.07h-.092v.026h.086v.048zm.122-.127q-.005-.012-.01-.02-.004-.01-.01-.018-.005-.01-.01-.02-.006-.01-.014-.02l.04-.022q.01.012.015.02.006.01.01.018.005.01.01.018.004.01.01.02zm-.175.215q-.028-.03-.05-.06-.003.004-.004.005v.07q.026-.007.055-.017zm.01-.056q.007-.006.014-.014.007-.007.016-.02h-.056q.007.01.013.017.007.007.013.015z"/></g><circle r="1.943" cy="3.203" cx="13.515" fill="#0a0" stroke-width=".07" class="B"/><path d="M12.53 2.84l1.9-.624.38.626-1.55 1.704-1.043-1.43z" fill="#fff"/><path d="M13.356 1.727c-.29.05-.554.143-.784.34-.148.127-.273.282-.36.456-.432.857.07 1.925 1.018 2.12.198.04.402.04.6 0a1.49 1.49 0 0 0 .565-.247c.125-.1.234-.198.325-.32.513-.686.31-1.68-.405-2.137a1.41 1.41 0 0 0-.476-.191c-.154-.03-.326-.05-.482-.02m-.06 1.784l.435-.47.238-.253c.08-.08.16-.215.265-.264.126-.06.253.05.237.18-.01.113-.168.218-.24.298l-.607.652c-.08.1-.178.233-.3.25s-.235-.13-.317-.205l-.234-.21c-.05-.05-.104-.1-.104-.168 0-.117.117-.198.226-.157.06.02.112.08.16.122.08.07.176.14.242.225z" fill="#47e91b"/></svg> \ No newline at end of file diff --git a/assets/decal-4.svg b/assets/decal-4.svg new file mode 100644 index 0000000..cda47df --- /dev/null +++ b/assets/decal-4.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.438 6.449" height="24.374" width="65.907"><style><![CDATA[.B{stroke:#000}.C{stroke-width:.035}]]></style><defs><filter height="1.207" y="-.103" width="1.066" x="-.033" id="A" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation=".215"/></filter></defs><rect ry="1.438" y=".818" x=".964" height="4.999" width="15.733" paint-order="markers fill stroke" filter="url(#A)"/><rect width="15.733" height="4.999" x=".799" y=".64" ry="1.438" fill="#f4d7d7" stroke="#4d4d4d" stroke-width=".156" stroke-linejoin="round" paint-order="markers fill stroke"/><g transform="matrix(.650317 0 0 .650317 376.676 65.6943)"><path d="M-574.03-95.98l.115.117q-.057.057-.1.096-.043.038-.083.068.032.03.066.057.036.026.074.055l-.174.15q-.034-.032-.066-.064-.032-.032-.062-.062l.055.18q-.166.057-.317.102-.15.045-.315.094l-.068-.22q.045-.01.085-.02.042-.008.085-.02v-.08q0-.02-.002-.043 0-.02.002-.042-.03.026-.062.05-.032.026-.066.05l-.128-.196q.117-.074.187-.138.072-.064.125-.136h-.234v-.22h.35v-.117h-.353v-.223h.368v-.134h-.306v-.215h.306v-.208h.236v.208h.287v.215h-.287v.134h.39q-.005-.153-.006-.293 0-.142-.002-.264h.213q0 .142.002.283.002.14.008.274h.502v.223h-.487q.008.102.02.196.013.093.03.183.05-.085.09-.174.04-.09.077-.19l.168.117q-.055.143-.12.27-.064.128-.142.242.008.023.014.045.008.02.02.038.008.02.02.045.013.023.026.042.014.02.028.032.014.013.028.013.01 0 .02-.02.008-.02.013-.05.006-.034.01-.077.006-.045.013-.098l.193.15q-.006.064-.026.128-.017.066-.045.117-.028.053-.066.087-.038.034-.087.034-.08 0-.142-.045-.062-.045-.113-.117-.038-.05-.072-.12-.17.18-.398.304l-.108-.185q.132-.074.236-.157.106-.085.19-.185-.038-.14-.06-.3-.02-.16-.032-.325h-.42v.117h.393v.22zm.557-.578q-.023-.053-.043-.096-.02-.043-.043-.083-.02-.043-.05-.085-.026-.043-.064-.098l.18-.1q.042.055.07.098.03.043.05.083.023.04.04.08.02.04.04.09zm-.8.98q-.13-.136-.232-.27-.013.017-.02.023v.323q.117-.03.25-.077zm.04-.255q.034-.028.066-.062.032-.034.074-.085h-.257q.03.04.057.077.03.034.06.07z" opacity=".1" fill="#fff"/><path d="M-572.356-95.87l.02-1.443" fill="none" class="B C"/><path d="M-572.673-96.87h.818l.152.146-.046.245-.86.496-.253-.463z" fill="#fff"/><path d="M-572.237-97.212c-.15-.01-.292-.002-.43.064-.1.043-.172.103-.238.177-.326.365-.218.958.222 1.18a.77.77 0 0 0 .295.079c.106.007.212-.01.31-.047.073-.026.14-.067.202-.114.343-.27.375-.787.083-1.106-.06-.064-.132-.12-.21-.157-.072-.035-.154-.068-.234-.074m-.265.87l.276-.173.15-.094c.05-.028.107-.085.165-.095.07-.013.118.057.093.12-.022.054-.11.085-.157.115l-.385.24c-.05.032-.118.09-.18.084s-.098-.096-.13-.143l-.087-.135c-.02-.03-.04-.06-.03-.097.017-.057.084-.082.132-.047.027.02.044.054.062.08.03.046.068.092.09.142z" fill="#47e91b"/><path d="M-575.8-95.87l.02-1.444" fill="none" class="B C"/><g transform="matrix(.172489 -.018433 .018433 .172489 -487.262 -69.3142)"><path d="M-493.596-213l-.576 2.198 3.534 4.24 2.17-.12 1.067-4.722z" fill="#fff"/><path d="M-464.42-215.27v1.78c0 .28-.04.6 0 .875.02.118.154.182.227.268.133.158.22.377.182.584-.136.776-1.235.904-1.492.133-.1-.26-.02-.536.162-.74.106-.122.27-.17.28-.348.03-.574 0-1.154 0-1.727 0-.255.06-.6 0-.846-.06-.212-.367-.312-.442-.563-.05-.17.05-.38 0-.53-.114-.302-.6-.622-.818-.85-.07-.07-.183-.234-.292-.234-.1 0-.18.12-.24.18l-.558.557-1.727 1.728c-.24.238-.612.5-.68.85-.1.534.535.946.865 1.275l3.03 3.03c.36.36.814 1.066 1.408.826.263-.107.468-.4.664-.586l3.455-3.455c.262-.262.694-.562.66-.983-.04-.513-.72-.963-1.058-1.302l-2.577-2.577c-.33-.33-.73-.915-1.17-1.086-.53-.208-.893.304-1.223.634-.102.102-.327.255-.362.4-.04.138.25.34.336.425.216.216.468.582.744.716.178.1.4-.02.61.08.238.12.415.372.443.638.02.145-.06.27.04.398.192.24.442.448.66.665.104.103.214.255.346.324s.327-.02.478.03c.45.14.725.653.49 1.083-.25.46-.862.57-1.232.185-.14-.144-.215-.33-.215-.53 0-.1.05-.207.02-.292-.04-.124-.194-.23-.283-.32-.23-.23-.452-.504-.718-.69" fill="#ef5034" transform="rotate(328.992 -469.827 -165.36)"/></g><circle r=".186" cy="-97.756" cx="-574.052"/><path d="M-574.238-97.755h.37v3.222h-.37z"/><path d="M-573.023-94.584v.37h-2.116v-.37z"/><path d="M-574.168-97.488c-.488-.492-1.073-.487-1.54-.05-.1.054-.23.023-.265-.084m1.993.137c.487-.492 1.073-.487 1.54-.05.1.054.23.023.266-.084" fill="none" stroke-width=".131" stroke-linecap="round" class="B"/><path d="M-576.285-95.873l.47-1.48" class="B C"/><g fill="none" class="B"><g stroke-width=".039"><ellipse ry=".137" rx=".068" cy="-97.456" cx="-575.777"/><ellipse cx="-572.334" cy="-97.455" rx=".068" ry=".137"/></g><path d="M-575.267-95.86l-.468-1.48m2.89 1.466l.468-1.48m.554 1.48l-.468-1.48" class="C"/></g><path d="M-576.382-95.85a.65.65 0 0 0 .04.12c.01.023.07.058.07.058h1.013s.05-.033.07-.058c.02-.034.03-.117.03-.117l.05-.015v-.013h-1.328v.013zm3.436-.02s.02.084.04.12c.02.023.07.058.07.058h1.012s.05-.033.07-.058c.02-.034.03-.117.03-.117l.05-.015v-.013h-1.33v.013z"/><circle cx="-575.125" cy="-94.4" r=".186"/><circle r=".186" cy="-94.398" cx="-573.023"/><path d="M-567.934-96.85c0-.558-.118-1.102-1.393-1.102-.366 0-1.3 0-1.3 1.013 0 .516.276.85.848.9l1.032.113c.535.056.66.314.66.708 0 .446-.164.75-1.1.75-1.04 0-1.154-.394-1.154-.914h-.323c0 .713.3 1.2 1.467 1.2 1.05 0 1.435-.333 1.435-1.05 0-.867-.675-.942-.975-.975l-.994-.108c-.413-.047-.568-.244-.568-.614 0-.544.3-.746.995-.746.95 0 1.05.366 1.05.825zm3.066.718c0-.394 0-.886-1.087-.886-1 0-1.08.534-1.084.844h.3c0-.563.45-.572.774-.572.774 0 .816.258.816.54 0 .36-.15.366-.32.37l-.863.023c-.38.01-.8.206-.8.825 0 .774.63.807 1.036.807.75 0 .868-.216.962-.398a1.05 1.05 0 0 0 .06.36h.3c-.08-.16-.08-.44-.08-.516zm-.286.9c0 .45-.16.792-.928.792-.507 0-.764-.1-.764-.516 0-.202.02-.586.6-.6l.834-.02c.104 0 .207-.038.258-.103zm1.134-1.738v2.762h.3v-2.762zm.166-.682l.025.026q-.013.013-.022.02-.01.008-.018.015.007.006.014.013.008.006.016.012l-.038.033q-.007-.007-.014-.014-.007-.007-.014-.014l.012.04q-.036.013-.07.022-.033.01-.07.02l-.015-.048q.01-.002.02-.004.01-.002.02-.004v-.017q0-.005 0-.01 0-.005 0-.01-.007.006-.014.01-.007.006-.014.01l-.028-.043q.026-.016.04-.03.016-.014.027-.03h-.05v-.048h.076v-.026h-.077v-.05h.08v-.03h-.067v-.047h.067v-.046h.052v.046h.063v.047h-.063v.03h.086q-.001-.034-.001-.064 0-.03 0-.058h.047q0 .03 0 .062 0 .03.002.06h.1v.05h-.107q.002.022.004.043.003.02.007.04.01-.02.02-.038.01-.02.017-.042l.037.026q-.012.03-.027.06-.014.028-.03.053.002.005.003.01.002.004.004.008.002.005.005.01.003.005.006.01.003.004.006.007.003.003.006.003.002 0 .004-.004.002-.004.003-.01.001-.007.002-.017.001-.01.003-.02l.042.033q-.001.014-.006.028-.004.014-.01.026-.006.012-.014.02-.008.007-.02.007-.017 0-.03-.01-.014-.01-.025-.026-.008-.01-.016-.027-.037.04-.087.067l-.024-.04q.03-.016.052-.034.023-.02.042-.04-.008-.03-.013-.066-.005-.035-.007-.07h-.092v.026h.086v.048zm.122-.127q-.005-.012-.01-.02-.004-.01-.01-.018-.005-.01-.01-.02-.006-.01-.014-.02l.04-.022q.01.012.015.02.006.01.01.018.005.01.01.018.004.01.01.02zm-.175.215q-.028-.03-.05-.06-.003.004-.004.005v.07q.026-.007.055-.017zm.01-.056q.007-.006.014-.014.007-.007.016-.02h-.056q.007.01.013.017.007.007.013.015z"/></g><circle cx="13.466" cy="3.169" r="1.943" fill="maroon" stroke-width=".206" class="B"/><g fill="red"><path d="M13.975 1.85l.784.784-1.846 1.846-.784-.784z"/><path d="M12.892 1.873l-.784.784 1.846 1.846.784-.784z"/></g></svg> \ No newline at end of file diff --git a/assets/decal-5.svg b/assets/decal-5.svg new file mode 100644 index 0000000..fbb1892 --- /dev/null +++ b/assets/decal-5.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.438 6.471" height="24.457" width="65.907"><style><![CDATA[.B{stroke:#000}.C{stroke-width:.035}]]></style><defs><filter height="1.207" y="-.103" width="1.066" x="-.033" id="A" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation=".215"/></filter></defs><rect ry="1.438" y=".955" x=".924" height="4.999" width="15.733" paint-order="markers fill stroke" filter="url(#A)"/><rect width="15.733" height="4.999" x=".863" y=".771" ry="1.438" fill="#ccc" stroke="#4d4d4d" stroke-width=".156" stroke-linejoin="round" paint-order="markers fill stroke"/><g transform="matrix(.650317 0 0 .650317 376.74 65.8258)"><path d="M-574.03-95.98l.115.117q-.057.057-.1.096-.043.038-.083.068.032.03.066.057.036.026.074.055l-.174.15q-.034-.032-.066-.064-.032-.032-.062-.062l.055.18q-.166.057-.317.102-.15.045-.315.094l-.068-.22q.045-.01.085-.02.042-.008.085-.02v-.08q0-.02-.002-.043 0-.02.002-.042-.03.026-.062.05-.032.026-.066.05l-.128-.196q.117-.074.187-.138.072-.064.125-.136h-.234v-.22h.35v-.117h-.353v-.223h.368v-.134h-.306v-.215h.306v-.208h.236v.208h.287v.215h-.287v.134h.39q-.005-.153-.006-.293 0-.142-.002-.264h.213q0 .142.002.283.002.14.008.274h.502v.223h-.487q.008.102.02.196.013.093.03.183.05-.085.09-.174.04-.09.077-.19l.168.117q-.055.143-.12.27-.064.128-.142.242.008.023.014.045.008.02.02.038.008.02.02.045.013.023.026.042.014.02.028.032.014.013.028.013.01 0 .02-.02.008-.02.013-.05.006-.034.01-.077.006-.045.013-.098l.193.15q-.006.064-.026.128-.017.066-.045.117-.028.053-.066.087-.038.034-.087.034-.08 0-.142-.045-.062-.045-.113-.117-.038-.05-.072-.12-.17.18-.398.304l-.108-.185q.132-.074.236-.157.106-.085.19-.185-.038-.14-.06-.3-.02-.16-.032-.325h-.42v.117h.393v.22zm.557-.578q-.023-.053-.043-.096-.02-.043-.043-.083-.02-.043-.05-.085-.026-.043-.064-.098l.18-.1q.042.055.07.098.03.043.05.083.023.04.04.08.02.04.04.09zm-.8.98q-.13-.136-.232-.27-.013.017-.02.023v.323q.117-.03.25-.077zm.04-.255q.034-.028.066-.062.032-.034.074-.085h-.257q.03.04.057.077.03.034.06.07z" opacity=".1" fill="#fff"/><path d="M-572.356-95.87l.02-1.443" fill="none" class="B C"/><path d="M-572.673-96.87h.818l.152.146-.046.245-.86.496-.253-.463z" fill="#fff"/><path d="M-572.237-97.212c-.15-.01-.292-.002-.43.064-.1.043-.172.103-.238.177-.326.365-.218.958.222 1.18a.77.77 0 0 0 .295.079c.106.007.212-.01.31-.047.073-.026.14-.067.202-.114.343-.27.375-.787.083-1.106-.06-.064-.132-.12-.21-.157-.072-.035-.154-.068-.234-.074m-.265.87l.276-.173.15-.094c.05-.028.107-.085.165-.095.07-.013.118.057.093.12-.022.054-.11.085-.157.115l-.385.24c-.05.032-.118.09-.18.084s-.098-.096-.13-.143l-.087-.135c-.02-.03-.04-.06-.03-.097.017-.057.084-.082.132-.047.027.02.044.054.062.08.03.046.068.092.09.142z" fill="#47e91b"/><path d="M-575.8-95.87l.02-1.444" fill="none" class="B C"/><g transform="matrix(.172489 -.018433 .018433 .172489 -487.262 -69.3142)"><path d="M-493.596-213l-.576 2.198 3.534 4.24 2.17-.12 1.067-4.722z" fill="#fff"/><path d="M-464.42-215.27v1.78c0 .28-.04.6 0 .875.02.118.154.182.227.268.133.158.22.377.182.584-.136.776-1.235.904-1.492.133-.1-.26-.02-.536.162-.74.106-.122.27-.17.28-.348.03-.574 0-1.154 0-1.727 0-.255.06-.6 0-.846-.06-.212-.367-.312-.442-.563-.05-.17.05-.38 0-.53-.114-.302-.6-.622-.818-.85-.07-.07-.183-.234-.292-.234-.1 0-.18.12-.24.18l-.558.557-1.727 1.728c-.24.238-.612.5-.68.85-.1.534.535.946.865 1.275l3.03 3.03c.36.36.814 1.066 1.408.826.263-.107.468-.4.664-.586l3.455-3.455c.262-.262.694-.562.66-.983-.04-.513-.72-.963-1.058-1.302l-2.577-2.577c-.33-.33-.73-.915-1.17-1.086-.53-.208-.893.304-1.223.634-.102.102-.327.255-.362.4-.04.138.25.34.336.425.216.216.468.582.744.716.178.1.4-.02.61.08.238.12.415.372.443.638.02.145-.06.27.04.398.192.24.442.448.66.665.104.103.214.255.346.324s.327-.02.478.03c.45.14.725.653.49 1.083-.25.46-.862.57-1.232.185-.14-.144-.215-.33-.215-.53 0-.1.05-.207.02-.292-.04-.124-.194-.23-.283-.32-.23-.23-.452-.504-.718-.69" fill="#ef5034" transform="rotate(328.992 -469.827 -165.36)"/></g><circle r=".186" cy="-97.756" cx="-574.052"/><path d="M-574.238-97.755h.37v3.222h-.37z"/><path d="M-573.023-94.584v.37h-2.116v-.37z"/><path d="M-574.168-97.488c-.488-.492-1.073-.487-1.54-.05-.1.054-.23.023-.265-.084m1.993.137c.487-.492 1.073-.487 1.54-.05.1.054.23.023.266-.084" fill="none" stroke-width=".131" stroke-linecap="round" class="B"/><path d="M-576.285-95.873l.47-1.48" class="B C"/><g fill="none" class="B"><g stroke-width=".039"><ellipse ry=".137" rx=".068" cy="-97.456" cx="-575.777"/><ellipse cx="-572.334" cy="-97.455" rx=".068" ry=".137"/></g><path d="M-575.267-95.86l-.468-1.48m2.89 1.466l.468-1.48m.554 1.48l-.468-1.48" class="C"/></g><path d="M-576.382-95.85a.65.65 0 0 0 .04.12c.01.023.07.058.07.058h1.013s.05-.033.07-.058c.02-.034.03-.117.03-.117l.05-.015v-.013h-1.328v.013zm3.436-.02s.02.084.04.12c.02.023.07.058.07.058h1.012s.05-.033.07-.058c.02-.034.03-.117.03-.117l.05-.015v-.013h-1.33v.013z"/><circle cx="-575.125" cy="-94.4" r=".186"/><circle r=".186" cy="-94.398" cx="-573.023"/><path d="M-567.934-96.85c0-.558-.118-1.102-1.393-1.102-.366 0-1.3 0-1.3 1.013 0 .516.276.85.848.9l1.032.113c.535.056.66.314.66.708 0 .446-.164.75-1.1.75-1.04 0-1.154-.394-1.154-.914h-.323c0 .713.3 1.2 1.467 1.2 1.05 0 1.435-.333 1.435-1.05 0-.867-.675-.942-.975-.975l-.994-.108c-.413-.047-.568-.244-.568-.614 0-.544.3-.746.995-.746.95 0 1.05.366 1.05.825zm3.066.718c0-.394 0-.886-1.087-.886-1 0-1.08.534-1.084.844h.3c0-.563.45-.572.774-.572.774 0 .816.258.816.54 0 .36-.15.366-.32.37l-.863.023c-.38.01-.8.206-.8.825 0 .774.63.807 1.036.807.75 0 .868-.216.962-.398a1.05 1.05 0 0 0 .06.36h.3c-.08-.16-.08-.44-.08-.516zm-.286.9c0 .45-.16.792-.928.792-.507 0-.764-.1-.764-.516 0-.202.02-.586.6-.6l.834-.02c.104 0 .207-.038.258-.103zm1.134-1.738v2.762h.3v-2.762zm.166-.682l.025.026q-.013.013-.022.02-.01.008-.018.015.007.006.014.013.008.006.016.012l-.038.033q-.007-.007-.014-.014-.007-.007-.014-.014l.012.04q-.036.013-.07.022-.033.01-.07.02l-.015-.048q.01-.002.02-.004.01-.002.02-.004v-.017q0-.005 0-.01 0-.005 0-.01-.007.006-.014.01-.007.006-.014.01l-.028-.043q.026-.016.04-.03.016-.014.027-.03h-.05v-.048h.076v-.026h-.077v-.05h.08v-.03h-.067v-.047h.067v-.046h.052v.046h.063v.047h-.063v.03h.086q-.001-.034-.001-.064 0-.03 0-.058h.047q0 .03 0 .062 0 .03.002.06h.1v.05h-.107q.002.022.004.043.003.02.007.04.01-.02.02-.038.01-.02.017-.042l.037.026q-.012.03-.027.06-.014.028-.03.053.002.005.003.01.002.004.004.008.002.005.005.01.003.005.006.01.003.004.006.007.003.003.006.003.002 0 .004-.004.002-.004.003-.01.001-.007.002-.017.001-.01.003-.02l.042.033q-.001.014-.006.028-.004.014-.01.026-.006.012-.014.02-.008.007-.02.007-.017 0-.03-.01-.014-.01-.025-.026-.008-.01-.016-.027-.037.04-.087.067l-.024-.04q.03-.016.052-.034.023-.02.042-.04-.008-.03-.013-.066-.005-.035-.007-.07h-.092v.026h.086v.048zm.122-.127q-.005-.012-.01-.02-.004-.01-.01-.018-.005-.01-.01-.02-.006-.01-.014-.02l.04-.022q.01.012.015.02.006.01.01.018.005.01.01.018.004.01.01.02zm-.175.215q-.028-.03-.05-.06-.003.004-.004.005v.07q.026-.007.055-.017zm.01-.056q.007-.006.014-.014.007-.007.016-.02h-.056q.007.01.013.017.007.007.013.015z"/></g></svg> \ No newline at end of file diff --git a/assets/decal-6.svg b/assets/decal-6.svg new file mode 100644 index 0000000..76b8282 --- /dev/null +++ b/assets/decal-6.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.438 6.449" height="24.374" width="65.907"><style><![CDATA[.B{stroke:#000}.C{stroke-width:.035}]]></style><defs><filter id="A" x="-.033" width="1.066" y="-.103" height="1.207" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation=".215"/></filter></defs><rect width="15.733" height="4.999" x="1.076" y=".857" ry="1.438" paint-order="markers fill stroke" filter="url(#A)"/><rect ry="1.438" y=".679" x=".913" height="4.999" width="15.733" fill="#f4d7d7" stroke="#4d4d4d" stroke-width=".156" stroke-linejoin="round" paint-order="markers fill stroke"/><g transform="matrix(.650317 0 0 .650317 376.79 65.7333)"><path d="M-574.03-95.98l.115.117q-.057.057-.1.096-.043.038-.083.068.032.03.066.057.036.026.074.055l-.174.15q-.034-.032-.066-.064-.032-.032-.062-.062l.055.18q-.166.057-.317.102-.15.045-.315.094l-.068-.22q.045-.01.085-.02.042-.008.085-.02v-.08q0-.02-.002-.043 0-.02.002-.042-.03.026-.062.05-.032.026-.066.05l-.128-.196q.117-.074.187-.138.072-.064.125-.136h-.234v-.22h.35v-.117h-.353v-.223h.368v-.134h-.306v-.215h.306v-.208h.236v.208h.287v.215h-.287v.134h.39q-.005-.153-.006-.293 0-.142-.002-.264h.213q0 .142.002.283.002.14.008.274h.502v.223h-.487q.008.102.02.196.013.093.03.183.05-.085.09-.174.04-.09.077-.19l.168.117q-.055.143-.12.27-.064.128-.142.242.008.023.014.045.008.02.02.038.008.02.02.045.013.023.026.042.014.02.028.032.014.013.028.013.01 0 .02-.02.008-.02.013-.05.006-.034.01-.077.006-.045.013-.098l.193.15q-.006.064-.026.128-.017.066-.045.117-.028.053-.066.087-.038.034-.087.034-.08 0-.142-.045-.062-.045-.113-.117-.038-.05-.072-.12-.17.18-.398.304l-.108-.185q.132-.074.236-.157.106-.085.19-.185-.038-.14-.06-.3-.02-.16-.032-.325h-.42v.117h.393v.22zm.557-.578q-.023-.053-.043-.096-.02-.043-.043-.083-.02-.043-.05-.085-.026-.043-.064-.098l.18-.1q.042.055.07.098.03.043.05.083.023.04.04.08.02.04.04.09zm-.8.98q-.13-.136-.232-.27-.013.017-.02.023v.323q.117-.03.25-.077zm.04-.255q.034-.028.066-.062.032-.034.074-.085h-.257q.03.04.057.077.03.034.06.07z" opacity=".1" fill="#fff"/><path d="M-572.356-95.87l.02-1.443" fill="none" class="B C"/><path d="M-572.673-96.87h.818l.152.146-.046.245-.86.496-.253-.463z" fill="#fff"/><path d="M-572.237-97.212c-.15-.01-.292-.002-.43.064-.1.043-.172.103-.238.177-.326.365-.218.958.222 1.18a.77.77 0 0 0 .295.079c.106.007.212-.01.31-.047.073-.026.14-.067.202-.114.343-.27.375-.787.083-1.106-.06-.064-.132-.12-.21-.157-.072-.035-.154-.068-.234-.074m-.265.87l.276-.173.15-.094c.05-.028.107-.085.165-.095.07-.013.118.057.093.12-.022.054-.11.085-.157.115l-.385.24c-.05.032-.118.09-.18.084s-.098-.096-.13-.143l-.087-.135c-.02-.03-.04-.06-.03-.097.017-.057.084-.082.132-.047.027.02.044.054.062.08.03.046.068.092.09.142z" fill="#47e91b"/><path d="M-575.8-95.87l.02-1.444" fill="none" class="B C"/><g transform="matrix(.172489 -.018433 .018433 .172489 -487.262 -69.3142)"><path d="M-493.596-213l-.576 2.198 3.534 4.24 2.17-.12 1.067-4.722z" fill="#fff"/><path d="M-464.42-215.27v1.78c0 .28-.04.6 0 .875.02.118.154.182.227.268.133.158.22.377.182.584-.136.776-1.235.904-1.492.133-.1-.26-.02-.536.162-.74.106-.122.27-.17.28-.348.03-.574 0-1.154 0-1.727 0-.255.06-.6 0-.846-.06-.212-.367-.312-.442-.563-.05-.17.05-.38 0-.53-.114-.302-.6-.622-.818-.85-.07-.07-.183-.234-.292-.234-.1 0-.18.12-.24.18l-.558.557-1.727 1.728c-.24.238-.612.5-.68.85-.1.534.535.946.865 1.275l3.03 3.03c.36.36.814 1.066 1.408.826.263-.107.468-.4.664-.586l3.455-3.455c.262-.262.694-.562.66-.983-.04-.513-.72-.963-1.058-1.302l-2.577-2.577c-.33-.33-.73-.915-1.17-1.086-.53-.208-.893.304-1.223.634-.102.102-.327.255-.362.4-.04.138.25.34.336.425.216.216.468.582.744.716.178.1.4-.02.61.08.238.12.415.372.443.638.02.145-.06.27.04.398.192.24.442.448.66.665.104.103.214.255.346.324s.327-.02.478.03c.45.14.725.653.49 1.083-.25.46-.862.57-1.232.185-.14-.144-.215-.33-.215-.53 0-.1.05-.207.02-.292-.04-.124-.194-.23-.283-.32-.23-.23-.452-.504-.718-.69" fill="#ef5034" transform="rotate(328.992 -469.827 -165.36)"/></g><circle cx="-574.052" cy="-97.756" r=".186"/><path d="M-574.238-97.755h.37v3.222h-.37z"/><path d="M-573.023-94.584v.37h-2.116v-.37z"/><path d="M-574.168-97.488c-.488-.492-1.073-.487-1.54-.05-.1.054-.23.023-.265-.084m1.993.137c.487-.492 1.073-.487 1.54-.05.1.054.23.023.266-.084" fill="none" stroke-width=".131" stroke-linecap="round" class="B"/><path d="M-576.285-95.873l.47-1.48" class="B C"/><g fill="none" class="B"><g stroke-width=".039"><ellipse cx="-575.777" cy="-97.456" rx=".068" ry=".137"/><ellipse ry=".137" rx=".068" cy="-97.455" cx="-572.334"/></g><path d="M-575.267-95.86l-.468-1.48m2.89 1.466l.468-1.48m.554 1.48l-.468-1.48" class="C"/></g><path d="M-576.382-95.85a.65.65 0 0 0 .04.12c.01.023.07.058.07.058h1.013s.05-.033.07-.058c.02-.034.03-.117.03-.117l.05-.015v-.013h-1.328v.013zm3.436-.02s.02.084.04.12c.02.023.07.058.07.058h1.012s.05-.033.07-.058c.02-.034.03-.117.03-.117l.05-.015v-.013h-1.33v.013z"/><circle r=".186" cy="-94.4" cx="-575.125"/><circle cx="-573.023" cy="-94.398" r=".186"/><path d="M-567.934-96.85c0-.558-.118-1.102-1.393-1.102-.366 0-1.3 0-1.3 1.013 0 .516.276.85.848.9l1.032.113c.535.056.66.314.66.708 0 .446-.164.75-1.1.75-1.04 0-1.154-.394-1.154-.914h-.323c0 .713.3 1.2 1.467 1.2 1.05 0 1.435-.333 1.435-1.05 0-.867-.675-.942-.975-.975l-.994-.108c-.413-.047-.568-.244-.568-.614 0-.544.3-.746.995-.746.95 0 1.05.366 1.05.825zm3.066.718c0-.394 0-.886-1.087-.886-1 0-1.08.534-1.084.844h.3c0-.563.45-.572.774-.572.774 0 .816.258.816.54 0 .36-.15.366-.32.37l-.863.023c-.38.01-.8.206-.8.825 0 .774.63.807 1.036.807.75 0 .868-.216.962-.398a1.05 1.05 0 0 0 .06.36h.3c-.08-.16-.08-.44-.08-.516zm-.286.9c0 .45-.16.792-.928.792-.507 0-.764-.1-.764-.516 0-.202.02-.586.6-.6l.834-.02c.104 0 .207-.038.258-.103zm1.134-1.738v2.762h.3v-2.762zm.166-.682l.025.026q-.013.013-.022.02-.01.008-.018.015.007.006.014.013.008.006.016.012l-.038.033q-.007-.007-.014-.014-.007-.007-.014-.014l.012.04q-.036.013-.07.022-.033.01-.07.02l-.015-.048q.01-.002.02-.004.01-.002.02-.004v-.017q0-.005 0-.01 0-.005 0-.01-.007.006-.014.01-.007.006-.014.01l-.028-.043q.026-.016.04-.03.016-.014.027-.03h-.05v-.048h.076v-.026h-.077v-.05h.08v-.03h-.067v-.047h.067v-.046h.052v.046h.063v.047h-.063v.03h.086q-.001-.034-.001-.064 0-.03 0-.058h.047q0 .03 0 .062 0 .03.002.06h.1v.05h-.107q.002.022.004.043.003.02.007.04.01-.02.02-.038.01-.02.017-.042l.037.026q-.012.03-.027.06-.014.028-.03.053.002.005.003.01.002.004.004.008.002.005.005.01.003.005.006.01.003.004.006.007.003.003.006.003.002 0 .004-.004.002-.004.003-.01.001-.007.002-.017.001-.01.003-.02l.042.033q-.001.014-.006.028-.004.014-.01.026-.006.012-.014.02-.008.007-.02.007-.017 0-.03-.01-.014-.01-.025-.026-.008-.01-.016-.027-.037.04-.087.067l-.024-.04q.03-.016.052-.034.023-.02.042-.04-.008-.03-.013-.066-.005-.035-.007-.07h-.092v.026h.086v.048zm.122-.127q-.005-.012-.01-.02-.004-.01-.01-.018-.005-.01-.01-.02-.006-.01-.014-.02l.04-.022q.01.012.015.02.006.01.01.018.005.01.01.018.004.01.01.02zm-.175.215q-.028-.03-.05-.06-.003.004-.004.005v.07q.026-.007.055-.017zm.01-.056q.007-.006.014-.014.007-.007.016-.02h-.056q.007.01.013.017.007.007.013.015z"/></g><g opacity=".574"><circle r="1.943" cy="3.208" cx="13.58" fill="maroon" stroke-width=".206" class="B"/><g fill="red"><path d="M14.09 1.89l.784.784-1.846 1.846-.784-.784z"/><path d="M13.006 1.912l-.784.784 1.846 1.846.784-.784z"/></g></g><g transform="matrix(-.132562 0 0 -.132562 -65.4427 -33.6362)"><circle r="3.403" cy="-277.984" cx="-606.196" paint-order="markers fill stroke"/><circle cx="-596.374" cy="-277.926" r="3.403" paint-order="markers fill stroke"/><circle cx="-586.527" cy="-278.088" r="3.403" paint-order="markers fill stroke"/></g></svg> \ No newline at end of file diff --git a/assets/delete.png b/assets/delete.png new file mode 100644 index 0000000..262eab9 Binary files /dev/null and b/assets/delete.png differ diff --git a/assets/failed-login.html b/assets/failed-login.html new file mode 100644 index 0000000..9ab065b --- /dev/null +++ b/assets/failed-login.html @@ -0,0 +1,3 @@ +<html> +This is an example destination that will appear after a failed login +</html> diff --git a/assets/failed.svg b/assets/failed.svg new file mode 100644 index 0000000..9c04372 --- /dev/null +++ b/assets/failed.svg @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="5.3046mm" height="5.3046mm" version="1.1" viewBox="0 0 5.3046 5.3046" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> +<metadata> +<rdf:RDF> +<cc:Work rdf:about=""> +<dc:format>image/svg+xml</dc:format> +<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> +<dc:title/> +</cc:Work> +</rdf:RDF> +</metadata> +<g transform="translate(-93.577 -79.452)" stroke-linecap="round"> +<circle cx="96.229" cy="82.104" r="2.5187" fill="#800000" stroke="#000" stroke-width=".26727"/> +<rect transform="rotate(45)" x="125.36" y="-11.664" width="1.437" height="3.3842" fill="#f00" stroke-width=".24936"/> +<rect transform="matrix(-.70711 .70711 .70711 .70711 0 0)" x="-10.65" y="124.39" width="1.437" height="3.3842" fill="#f00" stroke-width=".24936"/> +</g> +</svg> + diff --git a/assets/freertos-espidf.svg b/assets/freertos-espidf.svg new file mode 100644 index 0000000..78ef16a --- /dev/null +++ b/assets/freertos-espidf.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle cx="1.908" cy="1.908" r="1.908" opacity=".12" fill="#f9f9f9"/><path d="M1.663 3.382A1.5 1.5 0 0 1 .5 2.277c-.02-.11-.04-.21-.04-.374a1.37 1.37 0 0 1 .24-.821C.814.91.856.865.89.88c.02 0 0 .04-.08.16a1.4 1.4 0 0 0-.292.864A1.45 1.45 0 0 0 1.329 3.2c.212.106.376.143.63.143a1.36 1.36 0 0 0 .877-.292c.09-.07.138-.09.15-.08.02.02.02.03 0 .05-.05.06-.333.235-.457.286-.25.1-.6.132-.86.08zm.03-.236c-.103-.02-.214-.09-.2-.133.06-.14.06-.328 0-.438-.07-.148-.235-.253-.42-.28-.225-.03-.392-.23-.392-.467 0-.19.114-.365.28-.435.03-.02.12-.02.192-.02.453 0 .937.31 1.17.748.125.235.196.612.158.846-.02.09-.02.1-.104.128s-.09.03-.185 0l-.1-.03.02-.08C2.216 2.408 1.78 1.8 1.22 1.74c-.132-.02-.172 0-.172.09 0 .07.05.104.172.122.143.03.352.136.46.254s.164.218.202.358a1.38 1.38 0 0 1 0 .496c-.03.104-.06.113-.177.09zm-.59-.262c-.12-.04-.158-.21-.07-.292.03-.03.06-.04.127-.03.1 0 .158.08.158.17 0 .1-.13.19-.216.16zM2.707 2.7c0-.106-.07-.4-.128-.54-.23-.554-.79-.967-1.382-1.016h-.133v-.08c0-.07 0-.08.11-.16l.153-.1c.06-.02.356.06.54.143.35.155.668.427.878.752a2.31 2.31 0 0 1 .284.677c.02.132.02.155-.09.306-.06.07-.07.08-.145.08-.08 0-.08 0-.08-.06zm.418-.732c-.1-.263-.29-.545-.523-.77-.198-.19-.394-.327-.635-.442-.09-.04-.155-.08-.148-.09.02-.03.262-.02.418.02a1.25 1.25 0 0 1 .96 1.158c.02.16 0 .18-.02.18s-.04-.03-.05-.05z"/></svg> diff --git a/assets/freertos-linkit.svg b/assets/freertos-linkit.svg new file mode 100644 index 0000000..a564d05 --- /dev/null +++ b/assets/freertos-linkit.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle r="1.908" cy="1.908" cx="1.908" opacity=".12" fill="#f2f2f2"/><g transform="matrix(.005642 0 0 .005642 5.461 .517)"><path d="M-937.285 325.773l569.292 39.05 19.537-21.257 12.645-169.99-8.84-15.064-526.53-13.597-23.445 12.043-55.353 151.915z" fill="#fff" paint-order="markers fill stroke"/><path d="M-388.274 373.585l-67.733-4.897-16.934-1.19-21.695-1.61-7.806-.53-7.143-.497-77.26-5.58-320.742-23.13c-.47-.08-2.76-.264-5.09-.404-15.87-.95-17.76-1.204-22.92-3.085-6.58-2.4-11.95-8.31-14.04-15.436-.15-.51-.41-1.093-.57-1.297-.22-.26-.31-2.643-.31-7.938 0-6.64.1-7.69.47-8.59.25-.564.46-1.244.46-1.513s.16-.88.37-1.356c1.32-3.13 1.79-4.343 2.62-6.822.37-1.092.92-2.64 1.23-3.44 1.12-2.877 1.37-3.584 1.59-4.366.11-.436.4-1.15.64-1.587.23-.437.42-.96.43-1.165 0-.204.28-.978.63-1.72s.72-1.765.83-2.274c.1-.51.36-1.16.56-1.45s.36-.744.36-1.017.17-.812.38-1.198.64-1.476.95-2.422.66-1.84.77-1.984c.12-.146.28-.563.36-.926.22-.99 1.09-3.374 1.87-5.126.38-.855.7-1.808.7-2.117s.19-.794.4-1.076.39-.685.39-.893c0-.316 1.27-3.77 2.11-5.737.12-.29.29-.768.37-1.06.27-1.037 1.17-3.464 1.85-4.993.39-.855.7-1.814.7-2.13s.11-.69.24-.827c.25-.255.93-1.98 1.34-3.427.43-1.474.97-2.793 1.28-3.104.17-.17.31-.612.31-.984s.18-1.015.4-1.432.4-.893.4-1.058.15-.6.34-.962.62-1.436.95-2.382l.81-2.25 1.78-4.762c.76-1.99 1.63-4.467 1.99-5.69.14-.51.36-1.045.48-1.19s.47-1.04.77-1.985c.31-.945.68-1.958.81-2.25.34-.724 1.05-2.74 1.31-3.704.11-.436.52-1.44.9-2.232.39-.79.7-1.625.7-1.852s.18-.755.4-1.17.4-.92.4-1.118.23-.87.51-1.49c.55-1.234 1.99-5.046 2.22-5.894a29.71 29.71 0 0 1 1.93-4.936c.2-.386.36-.963.36-1.282s.17-.896.38-1.282.99-2.43 1.75-4.54l1.6-4.365c.29-.677.99-2.663 1.33-3.76.14-.468.37-.92.51-1.008s.26-.485.26-.885.17-.958.38-1.24c.22-.282.4-.657.4-.832 0-.78 3.33-7.097 4.18-7.98.19-.198.74-.972 1.22-1.72 1.16-1.79 8.545-8.9 9.254-8.9.09 0 .755-.42 1.47-.934 3.34-2.4 9.474-5.3 13.12-6.207 1.31-.325 2.5-.68 2.646-.79s.98-.276 1.852-.37l3.556-.432c1.303-.173 5.33-.17 11.907.012l48.83 1.038c4.075.13 10.86.247 15.08.258s14.46.19 22.754.4l43.127.812c.583.067 9.036.255 18.786.42l40.48.72 27.78.558 45.244.98 48.816.872 19.844.28c4.003.01 9.48.128 12.17.26s10.848.312 18.124.398l22.622.428 70.247 1.394c19.365.223 34.48.678 36.38 1.096.945.208 2.405.455 3.244.55s1.585.266 1.656.382.635.294 1.255.397 1.357.31 1.645.46.88.368 1.323.48c5.318 1.363 11.264 5.01 16.113 9.88 1.728 1.736 3.302 3.544 3.5 4.017s.473.93.61 1.016c.14.09.418.48.622.878l1.34 2.497c.532.976.97 1.907.97 2.068s.314.97.695 1.797c1.882 4.06 1.972 7.33.64 23.597l-4.503 62.177-2.125 28.443-.656 8.467-.675 8.996-1.188 15.213c-1.304 16.97-2.233 22.155-4.956 27.65-3.138 6.33-11.025 13.72-19.116 17.912-7.948 4.117-16.51 5.76-25.93 4.977zm26.3-19.46c1.077-.225 2.167-.52 2.423-.658.54-.29 1.76-3.343 1.95-4.887.07-.582.2-2.157.28-3.5s.203-2.593.272-2.78.302-2.717.52-5.628l1.07-13.23c.08-.728.318-3.645.53-6.482l.78-9.525.794-9.393.936-10.716 1.445-17.065c.19-1.447.868-9.255 1.328-15.346l.79-9.657.656-7.806a575.45 575.45 0 0 1 1.598-16.272c.14-1.09.376-3.59.52-5.556l1.458-17.065.54-7.012c.09-1.528.262-3.254.39-3.836s.257-2.13.284-3.442.148-3.096.267-3.968c.357-2.604 1.098-12.044 1.087-13.817 0-1.825-.905-3.886-1.974-4.553-1.878-1.174-4.677-1.314-39.616-1.99l-111.918-2.392c-3.078-.128-6.565-.2-31.883-.668l-23.68-.524-42.598-.92c-1.672-.068-7.387-.19-12.7-.274l-14.155-.27-243.814-5.28c-2.255-.072-7.275-.15-11.155-.176l-7.053-.046-1.84 1.092c-1.012.6-2.03 1.383-2.262 1.738-.41.628-1.77 3.977-2.697 6.65l-2.588 7.276-2.29 6.35-1.99 5.424c-.34.873-.8 2.123-1.02 2.778s-1.37 3.81-2.56 7.012l-2.51 6.88c-.51 1.56-1.73 4.947-1.99 5.556-.13.29-1.09 2.91-2.14 5.82l-9.68 26.723-1.56 4.233-1.26 3.44-4 11.112-20.92 57.415c-.77 2.005-1.05 3.883-.7 4.553.15.282.73.824 1.29 1.204.85.586 1.47.735 4.05.972 1.67.154 4.05.285 5.29.29a39.31 39.31 0 0 1 4.1.246c1.95.25 3.84.39 10.98.81l11.38.785 5.29.4 20.77 1.312 19.843 1.34 17.198 1.18a232.86 232.86 0 0 0 6.615.41 167.67 167.67 0 0 1 7.144.525 153.48 153.48 0 0 0 7.276.519 196.46 196.46 0 0 1 6.614.399l13.494.94 7.937.526 54.637 3.696 3.97.265 5.556.406 9.657.656 11.907.79 72.628 4.914c1.6.147 4.857.378 7.24.513l16.176 1.063c1.53.144 5.1.383 7.938.532s6.112.382 7.276.517 3.902.316 6.085.402 4.268.21 4.63.273 3.7.298 7.41.52l7.28.522c.293.06 2.137.192 4.1.282s4.76.278 6.215.416 4.725.363 7.268.5l17.867 1.192c.582.07 3.143.252 5.69.396l14.607.947c1.79.1 3.318.212 3.397.26s1.57.158 3.308.25c5.273.278 10.53.642 11.23.777.365.07 3.104.26 6.086.42l6.482.386a440.5 440.5 0 0 0 5.162.364l18.386 1.228 9.922.693 15.478.99 6.085.416c2.305.126 6.21.433 10.187.8 1.156.108 4.212.276 10.186.563 1.6.08 4.162.25 5.69.388s4.744.37 7.144.52l9.525.653 18.917 1.23c1.165.01 2.83.11 3.705.22l1.984.235c.217.02 1.278-.145 2.355-.37zm-36.62-34.794l-32.808-1.96-11.113-.683-8.863-.527-8.996-.53-244.607-14.808c-1.892-.135-5.047-.312-7.012-.394a294.61 294.61 0 0 1-7.143-.398l-8.203-.528-57.017-3.437-8.864-.526-11.377-.664-12.832-.798-10.848-.67c-12.413-.678-14.555-.835-14.86-1.09-.24-.2-.253-.452-.05-.976.153-.39 1.04-2.903 1.968-5.587s1.8-4.99 1.933-5.124c.148-.148 1.987-.126 4.698.06l9.614.572 18.39 1.02 18.256 1.08 9.393.538 9.392.523 32.28 1.845 13.493.796 37.042 2.1 17.86 1.083 9.658.546 9.525.512 167.746 9.655 11.377.66 11.774.662 66.775 3.832c.455 0 .487.162.347 1.79-.162 1.886-.51 4.906-1.048 9.062l-.325 2.513-1.19.03c-.654.02-2.62-.06-4.366-.175zm-494.553-22.72c-1.48-.08-2.77-.235-2.87-.334-.18-.18.26-1.59 1.57-5.077l.69-1.88c0-.03-1.05-.144-2.32-.244-6.78-.533-10.25-2.486-11.39-6.408-.83-2.856-.27-5.037 4.25-16.638.46-1.164.95-2.262 1.11-2.44.22-.25 1.3-.265 4.76-.07 2.47.14 4.6.37 4.73.507.26.254.23.36-1.03 3.587-2.24 5.78-2.53 6.78-2.21 7.733.39 1.17 1.75 1.812 4.4 2.07l1.904.186.536-1.158c.295-.636.968-2.348 1.494-3.803s1.095-2.787 1.262-2.96c.228-.236 1.166-.255 3.744-.08l4.3.248c.603 0 .86.135.86.428 0 .425-1.08 3.598-2.09 6.14-.305.77-.555 1.438-.555 1.487 0 .136 7.394.64 18.124 1.237 5.384.3 9.87.625 9.97.724.17.17-2.763 8.943-3.618 10.83l-.39.86h-2.04c-1.12 0-3.356-.117-4.965-.26s-5.486-.394-8.615-.558l-9.128-.49c-3.677-.205-4.1-.163-4.1.407 0 .2-.287 1.11-.638 2.022l-1.094 2.914-.456 1.256-1.75-.04c-.963-.02-2.963-.114-4.443-.2zm441.372-8.638c-10.118-.693-19.39-2.077-28.437-4.244-2.318-.555-7.284-1.94-8.377-2.336-.212-.08-.146-.64.222-1.915l.74-2.6c.69-2.493 5.56-18.743 5.638-18.824.05-.05 1.217.426 2.588 1.064s3.027 1.347 3.68 1.573l3.175 1.12c5.37 1.916 11.4 3.277 18.786 4.24 4.217.55 13.52.78 16.8.418 8.826-.98 13.27-4.21 13.573-9.865.13-2.485-.384-3.822-2.233-5.8-2.4-2.566-7.9-5.166-15.574-7.36-8.133-2.328-16.04-4.872-18.388-5.918-6.477-2.883-9.483-4.714-12.6-7.675-4.442-4.22-6.453-8.933-6.45-15.13 0-14.235 9.45-24.724 26.8-29.765 4.976-1.446 10.96-2.53 17.782-3.22 4.715-.476 19.86-.397 25.4.133 5.144.493 12.703 1.55 15.534 2.173l2.987.647c1.72.363 4.92 1.245 5.075 1.398.08.08-.233 1.726-.688 3.663l-3.71 15.957c-.24 1-.42.987-4.117-.33-7.59-2.702-15.515-3.97-26.326-4.21-9.236-.206-14.107.458-18.068 2.46-5.075 2.568-6.255 8.065-2.447 11.4 2.513 2.2 6.556 3.737 18.282 6.947 8.578 2.348 12.793 3.678 16.918 5.34 16.295 6.56 22.402 17.416 18.5 32.895-2.8 11.106-11.035 19.212-24.188 23.804-10.398 3.63-25.02 5.047-40.878 3.96zm-118.136-6.353c-13.814-1.113-25.324-4.476-33.245-9.713-6-3.966-10.025-8.372-12.878-14.106-2.796-5.618-3.884-10.63-3.884-17.9 0-17.16 7.335-34.435 19.333-45.535 8.676-8.027 20.818-13.6 33.98-15.597l4.895-.75c3.116-.483 20.35-.49 23.812-.01 10.348 1.434 14.743 2.492 21.035 5.063 7.58 3.095 14.187 8.307 18.224 14.372 6.5 9.772 7.895 23.268 4.01 38.822-1.894 7.588-5.432 15.037-10.173 21.43-1.812 2.443-6.138 6.955-8.623 8.99-1.65 1.352-6.368 4.768-6.585 4.768-.05 0-1.085.572-2.308 1.272-7.114 4.073-15.82 6.83-26.56 8.413-2.508.37-18.018.724-21.034.48zm13.612-21.15c4.342-.7 8.687-2.43 12.383-4.932 2.273-1.54 6.413-5.734 8.215-8.323 2.75-3.952 5.453-10.055 6.662-15.045 1.106-4.566 1.305-6.35 1.313-11.913 0-4.96-.06-5.797-.593-7.805-2.365-8.842-8.07-13.884-17.497-15.458-4.36-.73-10.65-.442-14.703.67-2.36.647-6.48 2.555-8.6 3.983-7.416 4.997-12.567 13.174-15.31 24.3-2.617 10.612-1.416 20.93 3.103 26.643 3.504 4.432 7.745 6.81 14.06 7.88 2.794.474 8.02.474 10.967 0zm-115.345 13.878l-8.863-.508-18.745-1.2c-.13-.128 2.823-11.162 4.99-18.65l5.987-21.696.928-3.307.53-1.852c.127-.437.942-3.414 1.81-6.615l1.82-6.615c.135-.436.56-1.984.95-3.44l1.088-3.97 2.196-7.875c1.267-4.618 1.28-4.7.727-4.814-.307-.06-5.085-.254-10.615-.423-15.32-.47-16.945-.572-16.87-1.043.12-.763 5.054-17.508 5.2-17.655.172-.172 6.183-.133 11.008.07l69.794 2.145c3.535.1 6.496.288 6.578.42s.03.74-.135 1.347c-.16.607-1.016 4.14-1.91 7.85-2.194 9.11-2.194 9.11-2.784 9.23-.5.103-16.367-.368-24.14-.716-2.233-.1-4.17-.114-4.303-.03s-.53 1.273-.883 2.646l-19.65 75.653c-.21.938-.585 1.33-1.233 1.286l-3.474-.244zm-68.66-3.733l-13.76-.742c-15.23-.77-15.065-.755-15.202-1.432-.06-.307-.18-2.464-.265-4.792-.39-10.86-.986-19.204-1.63-22.886-1.207-6.888-3.745-10.2-8.698-11.347-1.438-.333-6.974-.81-9.533-.818h-1.594l-6.32 19.116-6.497 19.323c-.1.113-2.43.08-5.183-.07l-12.15-.667c-13.34-.722-12.568-.648-12.568-1.204 0-.422 2.415-7.592 3.57-10.6.203-.528.84-2.374 1.417-4.1l2.407-6.978 16.727-48.154 2.34-6.747 5.657-16.115.433-1.034 4.957.177 13.952.43c39.43 1.11 42.424 1.338 49.245 3.73 12.474 4.375 16.42 13.34 11.207 25.453-2.795 6.495-8.033 12.117-14.178 15.22-3.335 1.684-7.463 3.072-11.216 3.772-.946.177-1.81.4-1.917.496-.108.09.487.488 1.323.872 4.14 1.903 5.795 5.137 6.673 13.037l.52 4.233 2.117 16.81 1.075 9.25c.493 3.98.602 5.743.363 5.86-.128.06-1.603.02-3.276-.08zm-25.25-60.034c6.32-.88 10.71-3.548 12.765-7.763 2.25-4.615 1.47-8.162-2.226-10.132-3.69-1.966-7.61-2.633-17.066-2.9l-5.98-.168-3.346 10.06-3.345 10.48c0 .346.413.436 2.315.51 5.75.22 15.03.17 16.884-.09zm-86.007 54.242c-1.6-.114-6.6-.4-11.112-.635l-11.945-.682c-2.06-.14-4.797-.256-6.085-.258s-2.513-.112-2.72-.243c-.314-.2-.116-.957 1.156-4.428.844-2.304 1.73-4.604 1.967-5.11l.43-.924 6.084.14-.793-.595c-3.613-2.705-4.22-5.906-2.015-10.633.974-2.09 1.224-2.378 2.015-2.328 2.45.156 9.307.923 9.444 1.057.1.1-.232 1.017-.74 2.04-.977 1.967-1.368 4.106-1.054 5.778.422 2.25 3.172 3.923 7.21 4.386 1.287.148 5.138.393 8.557.545 6.713.3 8.732.483 8.732.794 0 .11-.31 1.017-.69 2.016s-.906 2.473-1.17 3.273c-1.043 3.15-1.905 5.44-2.165 5.755-.296.357-.702.36-5.104.05zm-8.466-25.785c-4.998-1.1-8.387-3.6-9.752-7.195-.798-2.103-.864-5.11-.173-7.883 1.72-6.9 5.926-12.042 12.174-14.887 3.013-1.372 4.412-1.794 8.052-2.43 1.903-.333 8.793-.367 8.993-.04.128.207-.302 1.516-2.358 7.177l-1.817 5.027-2.258 6.207c-.586 1.594-1.012 2.953-.946 3.02s.8-.03 1.632-.22c3.74-.837 6.57-3.874 8.342-8.95.738-2.117.804-2.595.812-5.85v-3.546l4.205.166c2.312.09 4.317.235 4.455.32.386.238.295 4.25-.15 6.57-2.017 10.537-8.71 18.503-18.274 21.75-3.39 1.152-9.543 1.517-12.945.77zm4.578-11.968l3.756-10.708c0-.18-.542-.228-1.52-.137-4.08.383-6.802 2.5-7.88 6.126-.685 2.304.08 3.79 2.52 4.896 1.747.792 2.793.733 3.123-.177zm10.21-26.24c-2.197-.35-5-1.315-6.27-2.157-1.707-1.13-3.3-2.963-4.08-4.694-.934-2.07-.972-5.627-.09-8.513 2.886-9.45 10.275-14.955 21.434-15.97 3.44-.313 5.913-.327 6.806-.04.333.108.338.09-1.365 4.765l-2.103 5.82-2.348 6.54c-.665 1.852-1.126 3.415-1.025 3.475.348.203 2.997-.62 4.132-1.286 1.806-1.058 3.284-2.745 4.404-5.026 1.7-3.46 2.3-7.004 1.747-10.284l-.218-1.29 4.372.106c2.405.06 4.468.202 4.585.32.355.356.18 4.402-.283 6.587-2.326 10.955-9.69 18.805-19.93 21.243-2.053.49-7.752.724-9.767.402zm4.25-13.38l1.963-5.346c.6-1.63 1.092-3.03 1.092-3.108 0-.29-2.545-.13-3.742.234-2.76.837-4.973 3.06-5.51 5.535-.534 2.455.436 4.01 3.013 4.83 2.018.643 2.212.51 3.184-2.146z" fill="#8ac552"/><path d="M-384.305 372.94c-.146-.05-2.527-.236-5.292-.402l-8.07-.555-8.863-.652-3.572-.28-3.307-.248-16.67-1.187-10.98-.797-7.54-.53-7.144-.526a343.83 343.83 0 0 0-7.276-.529l-5.82-.395-14.155-1.054-7.54-.54-5.16-.398-51.46-3.693c-2.686-.235-6.192-.49-12.965-.943l-8.6-.66-5.424-.404-15.213-1.05-8.996-.656-10.187-.767-5.688-.41-3.837-.284c-.653-.07-2.976-.243-5.16-.383l-16.536-1.2c-.654-.08-3.154-.258-5.557-.396l-16.668-1.182-14.817-1.073c-.654-.06-4.763-.358-9.128-.65l-8.732-.657c-.436-.07-3.71-.308-7.276-.53l-7.54-.523c-1.114-.124-3.528-.302-7.276-.534l-11.642-.832-6.614-.507-22.226-1.593-9.92-.757c-.437-.05-2.223-.18-3.97-.278l-4.233-.284c-.583-.06-3.023-.228-5.424-.378l-5.424-.4c-.582-.07-3.083-.252-5.557-.404l-5.82-.394-3.572-.266-5.292-.41-25.4-1.84-12.832-.914c-.582-.07-3.32-.247-6.086-.4l-5.953-.402c-1.092-.15-13.617-1.083-17.992-1.34l-3.44-.25c-.358-.06-1.42-.143-7.144-.533l-6.622-.535c-.1-.05-1.45-.155-3.04-.243l-4.63-.287-7.27-.51-16.89-1.222c-2.01-.153-4.51-.452-5.56-.665-1.04-.213-2.37-.46-2.95-.548s-1.21-.276-1.39-.418-.75-.18-1.27-.08c-.53.1-1.09.06-1.26-.08s-.85-.466-1.51-.723c-4.02-1.575-9.32-7.383-10.97-12.016-1.79-5.064-1.73-4.614-1.73-11.244 0-5.42.1-6.346.53-7.806.29-.91.53-1.89.53-2.177s.18-.836.4-1.222c.4-.724.47-.9 2.36-6.284.62-1.76 1.21-3.328 1.3-3.483.1-.155.64-1.572 1.19-3.15l3.64-9.822c.2-.478.37-1.076.37-1.33s.16-.696.36-.983.6-1.236.91-2.11c1.22-3.54 2.66-7.47 3.09-8.467.26-.582.56-1.416.68-1.852.24-.892 1.45-4.216 1.83-5.027.32-.677 2.13-5.827 2.13-6.053 0-.09.23-.675.51-1.292.29-.617.76-1.836 1.05-2.71l1.09-3.043 2.69-7.41c.59-1.673 1.16-3.16 1.27-3.307.12-.145.63-1.515 1.15-3.043l1.5-4.233.85-2.25c.15-.436.38-.907.51-1.045.12-.138.23-.458.23-.71s.37-1.39.82-2.526c.91-2.313 1.95-5.158 3.15-8.63.45-1.28 1.1-2.97 1.46-3.754s.65-1.663.65-1.954.12-.604.27-.694c.14-.09.26-.4.26-.686s.29-1.084.63-1.77a20.32 20.32 0 0 0 1.05-2.57c.55-1.76 2.24-6.457 2.45-6.798.1-.15.68-1.716 1.3-3.483l1.96-5.462 1.58-4.366c.41-1.164.96-2.593 1.2-3.175.25-.582.74-1.927 1.09-2.99.66-1.997 2.8-6.485 3.45-7.218.21-.23.37-.502.37-.606s.82-1.285 1.83-2.625c1.67-2.227 3.45-4.083 6.645-6.933 1.663-1.482 3.457-2.582 2.7-1.656-.56.686-1.97 3.715-2.71 5.82l-1.724 4.83-1.29 3.704c-.19.582-.59 1.654-.89 2.382-.31.727-1.29 3.346-2.18 5.82l-3.84 10.584-1.72 4.762-1.97 5.424-1.76 4.895c-1 2.95-2.36 6.612-2.65 7.112-.15.274-.43 1.048-.62 1.72s-.85 2.532-1.45 4.133l-1.71 4.63-1.72 4.762-4.65 12.833-6.34 17.462-4.49 12.303-1.68 4.63-2.18 5.954-4.16 11.377-5.1 14.023-2.09 5.688c-1.18 3.034-1.61 4.863-1.61 6.815 0 1.703 0 1.718 1.27 2.898 1.36 1.275 1.82 1.394 6.41 1.682l17.33 1.165 7.8.542 7.28.54c1.74.144 5.14.376 7.54.516l6.35.397 14.02.92 7.673.532 31.883 2.24 16.007 1.076c.655.07 4.167.312 7.805.53l8.07.528c2.124.194 8.04.62 14.684 1.058l35.455 2.38 24.738 1.712 64.823 4.36c.51.05 2.892.22 5.292.38l8.466.56 19.447 1.325c2.33.138 5.305.366 6.615.506s4.06.332 6.112.425l4.762.266a374.59 374.59 0 0 0 5.398.378l7.54.533a229.34 229.34 0 0 0 6.483.406c1.82.08 4.378.264 5.688.402s4.466.372 7.012.52l25.532 1.715 7.54.52c.656.08 2.262.192 3.57.26 4.453.228 12.69.797 15.666 1.083.612.06 3.173.23 5.69.382l10.263.67 9.79.67 9.766.67 21.72 1.434 5.424.395c1.164.156 9.263.723 18.918 1.325l27.763 1.858c1.664.09 3.1.203 3.193.26s1.648.172 3.456.256 4.78.27 6.598.41l8.07.53c2.62.15 5.773.383 7.012.517s4.034.312 6.217.394 4.98.267 6.218.41c1.95.224 7.078.597 12.965.943 2.825.166 6.56-.34 8.122-1.098 2.416-1.175 4.673-4.19 5.332-7.125l.69-3.043c.217-.946.516-3.565.664-5.82l.65-8.07.68-7.937.677-8.6.638-7.937 1.6-18.918c.137-1.892.368-4.57.516-5.953s.458-5.133.698-8.335l1.268-16.14.68-8.202.658-7.41c.21-2.183.453-5.22.54-6.747s.265-3.61.394-4.63.307-3.207.397-4.864.268-3.563.395-4.235.304-2.294.394-3.603l.534-6.612.79-9.922.815-9.922.654-7.673.524-6.35c.143-1.6.336-4.94.43-7.418.196-5.045.217-4.95-1.678-7.177-1.33-1.564-2.884-2.404-5.37-2.906-1.923-.388-11.602-.742-31.353-1.144l-17.86-.404-20.24-.395-18.52-.394c-1.6-.073-6.897-.198-11.774-.277l-75.803-1.58c-1.164-.063-7.712-.18-14.552-.262l-14.552-.274c-2.556-.153-10.006-.333-27.252-.66l-271.198-5.692-28.443-.66-3.836-.01.786-.587c1.39-1.04 6.274-3.006 9.268-3.733 1.6-.39 3.327-.852 3.836-1.03 1.12-.39 27.26-.33 42.202.098l17.727.394 19.446.388 227.675 4.355c1.31.085 10.358.288 20.108.45l20.373.368 13.89.224 27.517.56c2.262.14 34.904.738 63.897 1.168 28.07.417 50.684 1.09 54.107 1.613 2.064.314 4.136.822 5.96 1.46.588.205 1.305.373 1.596.373s.762.156 1.05.346 1.236.572 2.11.85 1.775.657 2.005.845.52.342.643.342c.416 0 2.464 1.08 2.786 1.468.175.21.42.384.546.384.48 0 3.098 1.862 4.923 3.5 2.35 2.11 5.633 5.696 5.633 6.15 0 .186.178.484.392.662s.553.64.75 1.025.794 1.498 1.33 2.474.968 1.91.968 2.074.355 1.067.788 2.005c1.413 3.05 1.882 7.746 1.363 13.594l-1.098 14.552-3.307 45.508-.654 8.864-.66 8.73-2.514 33.734-.667 8.73-1.188 15.346-.396 4.763c-.723 9.465-2.06 15.777-4.27 20.183-3.566 7.106-11.45 14.346-20.206 18.555-5.543 2.665-9.083 3.652-13.36 3.725-1.6.03-3.863.125-5.027.218s-2.236.124-2.38.07zm-16.537-53.88l-40.745-2.493-20.11-1.21-239.05-14.41c-.728-.08-3.406-.253-5.953-.388l-11.51-.66-11.113-.666-8.334-.52-94.59-5.694c-9.465-.526-11.03-.675-10.905-1.034.06-.16.916-2.674 1.908-5.585 1.792-5.25 2.136-6.054 2.498-5.83.103.06 2.56.24 5.46.393l23.926 1.327 11.244.66 76.2 4.388 326.295 18.7c2.106.09 3.59.264 3.726.442.214.28-.31 5.79-1.016 10.68l-.315 2.183-2.42-.04a106.81 106.81 0 0 1-5.199-.245zm-491.264-22.55c-2.7-.184-3.77-.35-3.77-.587 0-.18.52-1.767 1.15-3.523l1.16-3.194-.69-.13c-.38-.07-1.64-.208-2.79-.305-5.04-.42-8.35-2.027-9.74-4.726-.76-1.486-.91-4.266-.35-6.64.44-1.9 4.75-13.295 5.19-13.74.25-.256 8.73.195 9.21.49.19.122-.85 3.183-2.35 6.902-1.45 3.585-1.38 4.805.31 5.806.9.528 4.94.997 5.42.628.326-.252.333-.268 1.925-4.565.59-1.59 1.184-3.005 1.32-3.14.242-.243 8.385.153 8.652.42.07.07-.522 1.836-1.31 3.928s-1.36 3.88-1.268 3.97 3.68.36 7.973.6l18.185 1.035c.906.05 1.716.153 1.8.237.195.195-3.527 10.818-3.943 11.255-.242.254-1.123.273-3.878.08l-9.12-.544-8.812-.546c-1.79-.136-4.058-.248-5.04-.248h-1.784l-.338.992c-.186.546-.755 2.094-1.264 3.44-.795 2.098-1.01 2.44-1.502 2.404l-4.344-.3zm38.96-31.954l-8.73-.5-20.572-1.064c-.733 0-.992-.116-.992-.43 0-.423 2.986-8.51 3.493-9.462.244-.458.51-.483 3.48-.327l3.215.17-.926-.726c-3.003-2.355-4.004-4.463-3.452-7.27.296-1.503 1.878-5.32 2.316-5.592.23-.142 9.84.764 10.022.946.06.06-.326 1.055-.863 2.206-.82 1.76-.98 2.386-.994 3.944-.02 2.26.518 3.338 2.254 4.51 2.096 1.412 5.117 1.805 18.432 2.395 2.073.09 3.77.278 3.77.412s-.643 2.057-1.43 4.27l-1.882 5.414-.453 1.39-1.49-.04c-.82-.02-3.158-.13-5.195-.243zm-5.16-25.433c-5.435-.676-9.33-3.07-11.086-6.816-.623-1.327-.688-1.77-.684-4.642s.08-3.404.834-5.556c2.394-6.872 7.19-11.622 14.11-13.98 3.19-1.086 6.104-1.518 10.283-1.522 2.09 0 3.864.06 3.94.135.08.08-.61 2.197-1.524 4.71l-2.8 7.745-1.982 5.424c-.464 1.237-.902 2.585-.974 2.995-.128.735-.116.743.895.56 4.348-.788 7.68-4.12 9.35-9.345.778-2.437 1.113-5.448.81-7.29-.142-.865-.205-1.626-.138-1.692.235-.235 8.206 0 8.458.24.473.467.174 5.205-.5 7.936-1.365 5.53-3.85 10.068-7.508 13.708-4.294 4.273-9.315 6.655-15.657 7.43-2.53.31-3.04.305-5.827-.04zm4.817-16.98c1.26-3.457 2.01-5.88 1.867-6.025-.377-.377-3.605.185-5.163.898-4.403 2.016-6.18 7.535-3.124 9.71 1.078.767 2.64 1.32 3.553 1.255.74-.05.813-.2 2.867-5.84zm9.735-21.36c-6.09-.682-9.655-2.717-11.47-6.546-.785-1.66-.833-1.932-.812-4.672.02-2.658.102-3.12.948-5.338 1.218-3.197 2.363-5.076 4.45-7.31 3.36-3.595 7.525-5.76 13.356-6.945 2.052-.416 9.443-.688 9.775-.36.09.09-.213 1.157-.666 2.38l-1.172 3.284c-.192.582-.902 2.547-1.577 4.366-2.29 6.17-3.574 9.852-3.574 10.253 0 .61 1.856.267 3.847-.714 4.31-2.122 7.137-7.827 6.952-14.034l-.08-2.776 3.97.09c2.446.06 4.11.205 4.335.39.458.377.26 4.184-.36 6.847-1.552 6.696-4.444 11.53-9.25 15.464-2.475 2.027-7.055 4.34-9.808 4.953-1.628.363-6.388 1.015-6.747.924-.07-.02-1.025-.134-2.116-.256zm3.117-13.864l2.06-5.57c.5-1.316.907-2.494.907-2.615 0-.273-2.962-.02-4.242.36-1.672.498-3.367 1.786-4.4 3.342-.824 1.242-1.01 1.777-1.1 3.16-.1 1.587-.06 1.71.786 2.638.908.99 3.28 2.048 4.316 1.923.407-.05.768-.75 1.672-3.237zm-42.717-38.41a.25.25 0 0 1 .353 0c.1.097.02.176-.176.176s-.274-.08-.177-.176z" fill="#48b049"/><path d="M-376.974 367.724c.07-.198.29-.36.495-.36s.46-.117.56-.26.532-.274.955-.29.903-.115 1.07-.218c.312-.192.916-.35 2.29-.594.436-.08.913-.23 1.058-.336s1.04-.47 1.985-.805c2.214-.786 6.635-2.962 7.538-3.71.174-.145.394-.185.49-.09.1.1.175.06.175-.09s.267-.39.595-.54c.905-.42 5.236-3.735 6.284-4.81l1.72-1.638c1.153-.998 3.886-4.095 4.357-4.94.223-.4.49-.79.593-.867s.43-.673.72-1.323.772-1.538 1.07-1.975.534-1 .534-1.25.344-1.322.762-2.38c.9-2.282 1.815-6.644 2.01-9.598l.4-5.292.403-5.16.26-2.91.252-3.175.57-7.707c.06-.09.225-2.116.375-4.498l.397-5.92.394-5.16.67-8.334.39-5.953.402-5.556c.07-.51.307-3.486.53-6.615l.656-8.334c.14-1.456.498-6.04.797-10.187l.65-8.6a134.53 134.53 0 0 0 .272-3.572l.797-10.583c.05-.364.16-1.674.25-2.91l.275-3.44c.06-.655.185-2.322.27-3.704s.39-5.43.675-8.996l.637-7.938.28-3.836.275-3.525c.635-6.702.698-9.422.262-11.525-.238-1.147-.38-2.14-.31-2.21.495-.495 1.47 2.11 2.12 5.655.476 2.6.41 4.432-.543 15.44l-1.41 19.58-1.863 24.738-.526 7.805c-.138 2.4-.315 4.842-.392 5.424s-.256 2.726-.394 4.763l-1.21 16.404c-.08.582-.315 3.678-.53 6.88l-.643 8.73c-.14 1.6-.437 5.65-.662 8.996l-.672 8.996c-.145 1.6-.383 4.875-.53 7.276s-.325 4.663-.4 5.027-.248 2.27-.383 4.234c-.484 7.09-.81 10.742-1.082 12.17l-.548 2.91c-.527 2.863-1.905 7.042-2.91 8.82-.564.995-1.024 1.9-1.024 2.01 0 .343-3.17 4.477-4.7 6.13-2.186 2.36-5.115 4.6-7.41 5.66l-3.064 1.466c-.56.285-1.185.518-1.39.518s-.373.12-.373.265-.13.264-.29.264-.666.167-1.124.37c-1.553.69-3.21 1.05-4.935 1.076-.944.01-1.868.117-2.048.23-.22.138-.277.08-.177-.183.122-.317-.06-.362-.997-.243-.627.08-1.262.223-1.408.318s-1.262.21-2.48.257c-1.775.07-2.19.01-2.096-.274zm-21.75-48.654l-8.202-.5-17.595-1.092-52.387-3.15-362.546-21.87c-2.286-.115-3.505-.278-3.504-.47 0-.274 3.652-11.046 3.79-11.184.06-.06 4.987.19 13.803.7l11.642.657 11.377.668 9.26.523 7.012.41c1.628.135 5.546.36 16.14.922l8.6.508 34.925 2.017 32.412 1.83 9.525.546 6.35.395 23.548 1.323 22.754 1.33 8.73.516 7.276.404 9.26.533 15.61.906 14.155.8 53.182 3.04 20.24 1.2 7.806.4a242.97 242.97 0 0 1 7.143.395l17.463 1.073 18.785 1.033 11.113.648 11.51.684 16.67.94 16.253.923c2.538.143 4.67.32 4.744.392.127.128-.503 5.97-1.13 10.445l-.314 2.25-.913.03c-.503.02-2.52-.06-4.485-.174zm-493.93-22.76c-1.65-.11-3.05-.258-3.12-.328-.11-.112.18-.96 1.9-5.55.21-.55.38-1.104.38-1.234s-.92-.307-2.05-.393c-6.32-.482-9.66-1.922-11.05-4.762-1.36-2.78-1.09-5.6 1.05-11.134l3.64-9.435 1.23.143c.67.08 2.74.22 4.6.312s3.37.25 3.37.35-.57 1.665-1.26 3.48c-.7 1.815-1.55 4.145-1.9 5.18l-.63 1.88.51.898c.71 1.226 2.02 1.805 4.54 2 2.453.19 2.26.352 3.39-2.867.374-1.066.99-2.688 1.37-3.603.63-1.524.746-1.654 1.37-1.536.376.07 2.26.21 4.19.306 2.346.117 3.505.275 3.502.477 0 .165-.597 1.894-1.323 3.84s-1.32 3.628-1.32 3.736c0 .267 2.302.465 10.716.925l11.906.653 5.093.27c.503 0 .418.445-.7 3.588l-2.772 7.867-3.06-.154-23.362-1.398-2.047-.144-.385.878c-.213.482-.822 2.038-1.355 3.457l-.97 2.58-1.23-.04c-.676-.02-2.578-.13-4.226-.24zm41.758-31.752l-27.842-1.523c-2.435-.118-4.51-.297-4.61-.397s-.05-.45.104-.776c.465-.967 3.243-8.357 3.243-8.626 0-.438.606-.48 3.683-.26 1.66.12 3.09.145 3.175.06s-.204-.416-.64-.726c-4.182-2.972-4.85-5.967-2.447-10.973.443-.922.966-1.74 1.162-1.814.363-.14 9.394.686 9.6.877.06.06-.136.61-.44 1.226-1.59 3.242-1.87 5.457-.93 7.34 1.32 2.637 3.906 3.58 10.913 3.973l13.126.862c.04.04-.564 1.856-1.343 4.035l-1.912 5.417c-.576 1.69-.26 1.603-4.844 1.307zm-8.655-25.76c-4.694-.897-7.906-2.923-9.473-5.975-1.664-3.24-1.51-7.384.447-12.124 3.08-7.457 9.52-12.246 18.383-13.672 2.416-.39 8.842-.555 9.16-.236.09.09-.08.773-.377 1.52-.65 1.64-3.79 10.303-4.473 12.34-.27.8-.97 2.743-1.56 4.318s-1.072 2.988-1.072 3.142c0 .186.466.22 1.373.1 4.245-.563 7.726-4.202 9.51-9.94.508-1.632.617-2.567.615-5.263v-3.28l3.902.15c2.146.08 4.024.226 4.173.32.362.223.347 3.8-.02 5.81-.484 2.616-2.058 7.3-3.074 9.144-1.46 2.648-2.386 3.907-4.54 6.162-3.243 3.394-7.515 5.89-12.14 7.096-2.682.698-8.18.897-10.832.39zm4.083-11.068c.14-.182.738-1.7 1.327-3.374l2.007-5.637c1.082-2.997 1.08-3-1.47-2.732-3.892.407-6.992 2.988-7.884 6.565-.39 1.563-.05 2.71 1.144 3.867 1.388 1.345 4.255 2.116 4.877 1.31zm11.187-27.086c-5.365-.674-9.18-2.896-10.85-6.318-.533-1.092-.663-1.817-.743-4.157-.08-2.502-.01-3.09.607-5.027 2.222-6.92 6.824-11.54 13.935-13.98 3.177-1.09 6.303-1.564 10.373-1.57h3.403l-.177.728c-.1.4-.875 2.632-1.73 4.96-4.254 11.604-5.333 14.753-5.125 14.955.348.34 2.562-.17 4.086-.944 3.18-1.61 5.928-5.867 6.755-10.458.373-2.072.342-4.866-.07-5.93-.127-.334.153-.4 1.686-.4 1.013 0 2.93.08 4.262.172l2.422.172v2.192c0 2.61-.495 5.14-1.694 8.646-2.815 8.235-10.088 14.71-18.525 16.492-1.918.405-6.96.68-8.62.472zm2.85-11.146l2.027-5.588c.973-2.674 1.708-5.017 1.635-5.208-.336-.875-5.252.362-6.99 1.76-2.185 1.755-3.413 5-2.615 6.91.608 1.454 2.898 2.793 4.853 2.837.678.02.882-.117 1.09-.71zm510.062-14.505c-.1-.388-.116-.765-.05-.838s.212.185.31.573.12.765.05.837-.212-.184-.31-.572zm-.68-1.833c0-.07.06-.133.132-.133s.133.06.133.133-.06.132-.133.132-.132-.06-.132-.132z" fill="#0a1811"/></g></svg> diff --git a/assets/git.svg b/assets/git.svg new file mode 100644 index 0000000..aad1e3b --- /dev/null +++ b/assets/git.svg @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" id="svg2951" version="1.1" viewBox="0 0 10.6265 10.6026" height="10.6026mm" width="10.6265mm"> +<defs id="defs2945" /> +<metadata id="metadata2948"> +<rdf:RDF> +<cc:Work rdf:about=""> +<dc:format>image/svg+xml</dc:format> +<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> +<dc:title></dc:title> +</cc:Work> +</rdf:RDF> +</metadata> +<g transform="translate(327.717,39.3157)" id="layer1"> +<g id="g3322-71" transform="matrix(1.26738,-0.135441,0.135441,1.26738,327.733,165.276)"> +<path fill="#fff" id="path3259-1" d="m -493.596,-213.01 -0.576,2.198 3.534,4.241 2.171,-0.121 1.067,-4.722 z" style="fill:#ffffff;stroke-width:0.264583px" /> +<g transform="rotate(-31.0083,-469.827,-165.36)" id="g3239-5"> +<path fill="#ef5034" style="fill:#ef5034;stroke-width:0.026571" d="m -464.419,-215.269 v 1.78 c 0,0.28 -0.04,0.599 0,0.875 0.02,0.118 0.154,0.182 0.227,0.268 0.133,0.158 0.219,0.377 0.182,0.584 -0.136,0.776 -1.235,0.904 -1.492,0.133 -0.09,-0.261 -0.02,-0.536 0.162,-0.741 0.106,-0.122 0.271,-0.169 0.279,-0.348 0.03,-0.574 0,-1.154 0,-1.727 0,-0.255 0.06,-0.601 0,-0.846 -0.06,-0.212 -0.367,-0.312 -0.442,-0.563 -0.05,-0.171 0.05,-0.381 0,-0.531 -0.114,-0.302 -0.59,-0.622 -0.818,-0.85 -0.07,-0.07 -0.183,-0.234 -0.292,-0.234 -0.09,0 -0.18,0.121 -0.239,0.181 l -0.558,0.557 -1.727,1.728 c -0.239,0.238 -0.612,0.499 -0.679,0.85 -0.101,0.534 0.535,0.946 0.865,1.275 l 3.029,3.029 c 0.361,0.361 0.814,1.066 1.408,0.826 0.263,-0.107 0.468,-0.39 0.664,-0.586 l 1.302,-1.302 2.153,-2.153 c 0.262,-0.262 0.694,-0.562 0.659,-0.983 -0.04,-0.513 -0.719,-0.963 -1.058,-1.302 l -2.577,-2.577 c -0.329,-0.329 -0.731,-0.915 -1.169,-1.086 -0.53,-0.208 -0.893,0.304 -1.223,0.634 -0.102,0.102 -0.327,0.255 -0.362,0.399 -0.04,0.138 0.25,0.339 0.336,0.425 0.216,0.216 0.468,0.582 0.744,0.716 0.178,0.09 0.41,-0.02 0.611,0.08 0.238,0.12 0.415,0.372 0.443,0.638 0.02,0.145 -0.06,0.271 0.04,0.398 0.192,0.24 0.442,0.448 0.659,0.665 0.104,0.103 0.214,0.255 0.346,0.324 0.129,0.07 0.327,-0.02 0.478,0.03 0.449,0.14 0.725,0.653 0.491,1.083 -0.25,0.461 -0.862,0.571 -1.232,0.185 -0.139,-0.144 -0.215,-0.331 -0.215,-0.53 0,-0.09 0.05,-0.207 0.02,-0.292 -0.04,-0.124 -0.194,-0.23 -0.283,-0.319 -0.23,-0.23 -0.452,-0.504 -0.718,-0.691" id="path3236-9" /> +</g> +</g> +</g> +</svg> diff --git a/assets/gs/admin-login.html b/assets/gs/admin-login.html new file mode 100644 index 0000000..113df9c --- /dev/null +++ b/assets/gs/admin-login.html @@ -0,0 +1,5 @@ +<html> +This is an example destination that will appear after successful Admin login. + +This URL cannot be served if you're not logged in as admin. +</html> diff --git a/assets/gs/index.html b/assets/gs/index.html new file mode 100644 index 0000000..d40904a --- /dev/null +++ b/assets/gs/index.html @@ -0,0 +1,56 @@ +<html> + <head> + <meta charset="UTF-8"> + <script src="/lws-common.js"></script> + <link rel="stylesheet" type="text/css" href="lwsgs.css"/> + <script src="lwsgs.js"></script> + </head> + + <body class="seats"> + <table class="lwsgs"> + <tr> + <td class="logo"> + <img src="lwsgs-logo.png"> + </td> + <td class=""> + <div id=lwsgs class="lwsgs"></div> + </td> + </tr> + + <tr><td colspan=2 class="h99"> + <table class="c100"><tr> + <td class="c"> + <span id="nolog" class="group2"> + This is a demo application for lws generic-sessions.<br><br> + It's a simple messageboard.<br><br> + What's interesting about it is there is <b>no serverside scripting</b>,<br> + instead client js makes a wss:// connection back to the server<br> + and then reacts to JSON from the ws protocol. Sessions stuff is <br> + handled by lws generic sessions, making the <a href="https://libwebsockets.org/git/libwebsockets/tree/plugins/generic-sessions/protocol_generic_sessions.c">actual<br> + test application</a> <a href="https://libwebsockets.org/git/libwebsockets/tree/plugins/generic-sessions/protocol_lws_messageboard.c">very small</a>.<br><br> + And because it's natively websocket, it's naturally connected<br> + for dynamic events and easy to maintain. + <br><br> + Register / Login at the top right to see and create new messages. + </span> + <span id="logged" class="group2"> + <div id="newmsg"> + <form action="msg" method="post" target="hidden"> + New message<br> + <textarea id="msg" placeholder="type your message here" cols="40" rows="5" name="msg"> + </textarea><br> + <input type="submit" id="send" name="send" disabled=1> + </form> + </div> + </span> + <div id="dmessages"> + <span id="messages" ></span> + </div> + <span id="debug" class="group2"></span> + </td></tr></table> + </td></tr> + </table> + </form> + <iframe name="hidden" class="hidden"></iframe> + </body> +</html> diff --git a/assets/gs/lwsgs-logo.png b/assets/gs/lwsgs-logo.png new file mode 100644 index 0000000..723a124 Binary files /dev/null and b/assets/gs/lwsgs-logo.png differ diff --git a/assets/gs/lwsgs.css b/assets/gs/lwsgs.css new file mode 100644 index 0000000..9dfde75 --- /dev/null +++ b/assets/gs/lwsgs.css @@ -0,0 +1,134 @@ +.body { font-size: 12px } +.gstitle { font-size: 18px } + +.group1 { + vertical-align:middle; + text-align:center; + background:#f0f0e0; + padding:12px; + border-radius:10px; +} +.group2 { + display:none; + vertical-align:middle; + font-size: 22px; + text-align:center; + margin:auto; + align:center; + background-color: rgba(255, 255, 255, 0.8); + padding:12px; + border-radius:10px; +} + +body.seats { + background-image:url(seats.jpg) +} + +div.lwsgs { + z-index: 3; + text-align:right; + background-color: rgba(255, 255, 255, 0.8); +} + +table.lwsgs { + width:100%; + height:100%; + transition: max-height 2s; +} +table.c100 { + text-align:center; + width:100%; +} + +table.r { + vertical-align:top; + text-align:right; +} + +table.l { + vertical-align:top; + text-align:left; +} + +table.fixed { + table-layout: fixed; +} + +td.logo { + vertical-align:top; + text-align:left; + width:200px +} + +td.lwsgs { + vertical-align:top; + float:right; +} + +td.h99 { + height:99%; + vertical-align:middle; +} + +td.c { + margin:auto; + align:center +} + +td.tac { + text-align:center +} + +td.ava { + display:inline-block; + vertical-align:top; + word-wrap:break-word; +} + +iframe.hidden { + display:none; +} + +div.hidden { + display:none; +} + +div.hiddenr { + display:none; + text-align:right; +} + +input { + margin: 2px; + padding: 2px; +} + +input.em { + margin: 4px; + font-weight:bold; +} + +input.wide { + margin: 6px; + padding: 6px; +} + +input.hidden { + display: none; +} + +form.r { + text-align:right; +} + +span.bad { + color: red; +} + +span.small { + font-size:8pt; +} + +.green { + color: green; +} diff --git a/assets/gs/lwsgs.js b/assets/gs/lwsgs.js new file mode 100644 index 0000000..b3ae489 --- /dev/null +++ b/assets/gs/lwsgs.js @@ -0,0 +1,617 @@ +<!-- lwsgs rewrites the below $vars $v $v into the correct values on the fly --> + +var lwsgs_user = "$lwsgs_user"; +var lwsgs_auth = parseInt("$lwsgs_auth", 10); +var lwsgs_email = "$lwsgs_email"; + +/* + * auth: + * b0: LWSGS_AUTH_LOGGED_IN + * b1: LWSGS_AUTH_ADMIN + * b2: LWSGS_AUTH_VERIFIED + * b3: LWSGS_AUTH_FORGOT_FLOW + */ + +var lwsgs_html = '\ + <div id="dlogin" class="hidden"> \ + <form action="lwsgs-login" method="post"> \ + <input type="hidden" name="admin" value="needadmin/admin-login.html"> \ + <input type="hidden" name="good" value="index.html"> \ + <input type="hidden" name="bad" value="failed-login.html"> \ + <input type="hidden" name="forgot-good" value="sent-forgot-ok.html"> \ + <input type="hidden" name="forgot-bad" value="sent-forgot-fail.html">\ + <input type="hidden" name="forgot-post-good" value="post-forgot-ok.html">\ + <input type="hidden" name="forgot-post-bad" value="post-forgot-fail.html">\ + <table class="r">\ + <tr>\ + <td>User Name\ + <input type="text" size="10" id="username" name="username"></td>\ + <td>Password\ + <input type="password" id="password" size="10" name="password"><div id="pw1"></div></td>\ + </tr><tr>\ + <td colspan="2" class="c"><input type="submit" id="login" name="login" value="Login" class="em">\ + &nbsp;<input type="submit" id="forgot" name="forgot" value="Forgot password">\ + &nbsp;<input id="doreg" type="button" value="Sign up"></td>\ + </tr>\ + </table>\ + </form>\ + </div>\ +\ + <div id="dlogout" class="hiddenr">\ + <form action="lwsgs-logout" method="post" class="r">\ + <input type="hidden" name="good" value="index.html">\ + <table class="r">\ + <tr><td><span id=grav></span></td>\ + <td class="tac"><table><tr><td class="tac">\ + <a href="#" id="clink">\ + <span id="curuser"></span></a></td></tr><tr>\ + <td class="tac"><input type="submit" name="logout" value="Logout"></td>\ + </tr></table></td></tr>\ + </table>\ + </form></div>\ +\ + <div id="dwaitverify" class="hiddenr">\ + <table class="r">\ + <tr><td><span id=grav1></span></td>\ + <td class="tac"><table><tr><td class="tac">\ + <a href="#" id="clink">\ + <span id="curuser1"></span></a></td></tr><tr>\ + <td class="tac">Awaiting account verification</td>\ + </tr></table></td></tr>\ + </table>\ + </div>\ +\ + <div id="dregister" class="hidden">\ + <form action="lwsgs-login" method="post">\ + <input type="hidden" name="admin" value="needadmin/admin-login.html">\ + <input type="hidden" name="good" value="successful-login.html">\ + <input type="hidden" name="bad" value="failed-login.html">\ + <input type="hidden" name="reg-good" value="post-register-ok.html">\ + <input type="hidden" name="reg-bad" value="post-register-fail.html">\ + <input type="hidden" name="forgot-good" value="sent-forgot-ok.html">\ + <input type="hidden" name="forgot-bad" value="sent-forgot-fail.html">\ + <input type="hidden" name="forgot-post-good" value="post-forgot-ok.html">\ + <input type="hidden" name="forgot-post-bad" value="post-forgot-fail.html">\ + <table class="l">\ + <tr>\ + <td colspan=2 align=center>\ + <span id="curuser"></span>\ + <b>Please enter your details to register</b>:\ + </td>\ + </tr>\ + <tr><td align=right>\ + User Name:</td>\ + <td><input type="text" size="10" id="rusername" name="username" &nbsp;<span id=uchk></span></td>\ + </tr>\ + <tr>\ + <td align=right>Password:</td>\ + <td><input type="password" size="10" id="rpassword" name="password">&nbsp;<span id="rpw1"></span></td>\ + </tr>\ + <tr>\ + </tr>\ + <tr>\ + <td align=right><span id="pw2">Password (again):</span></td>\ + <td><input type="password" size="10" id="password2" name="password2">&nbsp;<span id="match"></span></td>\ + </tr>\ + <tr>\ + <td align=right>Email:</td>\ + <td><input type="email" size="10" id="email" name="email"\ + placeholder="me@example.com" &nbsp;<span id=echk></span></td>\ + </tr>\ + <tr>\ + <td colspan=2 align=center>\ +<input type="submit" id="register" name="register" value="Register" >\ +<input type="submit" id="rforgot" name="forgot" value="Forgot Password" class="hidden">\ +<input type="button" id="cancel" name="cancel" value="Cancel">\ + </td>\ + </tr>\ + </table>\ + </form>\ + </div>\ + \ + <div id="dchange" class="hidden">\ + <form action="lwsgs-change" method="post">\ + <input type="hidden" id="cusername" name="username">\ + <input type="hidden" name="admin" value="needadmin/admin-login.html">\ + <input type="hidden" name="good" value="index.html">\ + <input type="hidden" name="bad" value="failed-login.html">\ + <input type="hidden" name="reg-good" value="post-register-ok.html">\ + <input type="hidden" name="reg-bad" value="post-register-fail.html">\ + <input type="hidden" name="forgot-good" value="sent-forgot-ok.html">\ + <input type="hidden" name="forgot-bad" value="sent-forgot-fail.html">\ + <input type="hidden" name="forgot-post-good" value="post-forgot-ok.html">\ + <input type="hidden" name="forgot-post-bad" value="post-forgot-fail.html">\ + <table class="l">\ + <tr>\ + <td colspan=2 align=center>\ + <span id="ccuruser"></span>\ + <b>Please enter your details to change</b>:\ + </td>\ + </tr>\ + <tr><td align=right id="ccurpw_name">\ + Current Password:</td>\ + <td><input type="password" size="10" id="ccurpw" name="curpw"\ + >&nbsp;<span id=cuchk></span></td>\ + </tr>\ + <tr>\ + <td align=right>Password:</td>\ + <td><input type="password" size="10" id="cpassword" name="password"\ + &nbsp;<span id="cpw1"></span></td>\ + </tr>\ + <tr>\ + <td align=right><span id="pw2">Password (again)</span></td>\ + <td><input type="password" size="10" id="cpassword2" name="password2"\ + >&nbsp;<span id="cmatch"></span></td>\ + </tr>\ + <!-- not supported yet\ + <tr>\ + <td align=right id="cemail_name">Email:</td>\ + <td><input type="email" size="10" id="cemail" name="email"\ + placeholder="?" \ + &nbsp;<span id=cechk></span></td>\ + </tr> -->\ + <tr>\ + <td colspan=2 align=center>\ + <input type="submit" id="change" name="change"\ + value="Change" class="wide">\ + <input type="submit" id="cforgot" name="forgot"\ + value="Forgot Password" class="wide hidden">\ + <input type="button" id="cancel2" name="cancel"\ + value="Cancel" class="wide">\ + </td>\ + </tr>\ + <tr>\ + <td colspan=2>\ + <input type="checkbox" id="showdel" name="showdel"\ + > Show Delete&nbsp;\ + <input type="submit" id="delete" name="delete" \ + value="Delete Account" class="wide hidden">\ + </td>\ + </tr>\ + </table>\ + </form>\ + </div>\ + \ + <div id="dadmin" class="hidden">\ + Admin settings TBD\ + </div>\ +'; + +/*-- this came from + -- https://raw.githubusercontent.com/blueimp/JavaScript-MD5/master/js/md5.min.js + -- under MIT license */ +!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t),e=(n>>16)+(t>>16)+(r>>16);return e<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[(r+64>>>9<<4)+14]=r;var e,i,a,h,d,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,h=v,d=m,l=o(l,g,v,m,n[e],7,-680876936),m=o(m,l,g,v,n[e+1],12,-389564586),v=o(v,m,l,g,n[e+2],17,606105819),g=o(g,v,m,l,n[e+3],22,-1044525330),l=o(l,g,v,m,n[e+4],7,-176418897),m=o(m,l,g,v,n[e+5],12,1200080426),v=o(v,m,l,g,n[e+6],17,-1473231341),g=o(g,v,m,l,n[e+7],22,-45705983),l=o(l,g,v,m,n[e+8],7,1770035416),m=o(m,l,g,v,n[e+9],12,-1958414417),v=o(v,m,l,g,n[e+10],17,-42063),g=o(g,v,m,l,n[e+11],22,-1990404162),l=o(l,g,v,m,n[e+12],7,1804603682),m=o(m,l,g,v,n[e+13],12,-40341101),v=o(v,m,l,g,n[e+14],17,-1502002290),g=o(g,v,m,l,n[e+15],22,1236535329),l=u(l,g,v,m,n[e+1],5,-165796510),m=u(m,l,g,v,n[e+6],9,-1069501632),v=u(v,m,l,g,n[e+11],14,643717713),g=u(g,v,m,l,n[e],20,-373897302),l=u(l,g,v,m,n[e+5],5,-701558691),m=u(m,l,g,v,n[e+10],9,38016083),v=u(v,m,l,g,n[e+15],14,-660478335),g=u(g,v,m,l,n[e+4],20,-405537848),l=u(l,g,v,m,n[e+9],5,568446438),m=u(m,l,g,v,n[e+14],9,-1019803690),v=u(v,m,l,g,n[e+3],14,-187363961),g=u(g,v,m,l,n[e+8],20,1163531501),l=u(l,g,v,m,n[e+13],5,-1444681467),m=u(m,l,g,v,n[e+2],9,-51403784),v=u(v,m,l,g,n[e+7],14,1735328473),g=u(g,v,m,l,n[e+12],20,-1926607734),l=c(l,g,v,m,n[e+5],4,-378558),m=c(m,l,g,v,n[e+8],11,-2022574463),v=c(v,m,l,g,n[e+11],16,1839030562),g=c(g,v,m,l,n[e+14],23,-35309556),l=c(l,g,v,m,n[e+1],4,-1530992060),m=c(m,l,g,v,n[e+4],11,1272893353),v=c(v,m,l,g,n[e+7],16,-155497632),g=c(g,v,m,l,n[e+10],23,-1094730640),l=c(l,g,v,m,n[e+13],4,681279174),m=c(m,l,g,v,n[e],11,-358537222),v=c(v,m,l,g,n[e+3],16,-722521979),g=c(g,v,m,l,n[e+6],23,76029189),l=c(l,g,v,m,n[e+9],4,-640364487),m=c(m,l,g,v,n[e+12],11,-421815835),v=c(v,m,l,g,n[e+15],16,530742520),g=c(g,v,m,l,n[e+2],23,-995338651),l=f(l,g,v,m,n[e],6,-198630844),m=f(m,l,g,v,n[e+7],10,1126891415),v=f(v,m,l,g,n[e+14],15,-1416354905),g=f(g,v,m,l,n[e+5],21,-57434055),l=f(l,g,v,m,n[e+12],6,1700485571),m=f(m,l,g,v,n[e+3],10,-1894986606),v=f(v,m,l,g,n[e+10],15,-1051523),g=f(g,v,m,l,n[e+1],21,-2054922799),l=f(l,g,v,m,n[e+8],6,1873313359),m=f(m,l,g,v,n[e+15],10,-30611744),v=f(v,m,l,g,n[e+6],15,-1560198380),g=f(g,v,m,l,n[e+13],21,1309151649),l=f(l,g,v,m,n[e+4],6,-145523070),m=f(m,l,g,v,n[e+11],10,-1120210379),v=f(v,m,l,g,n[e+2],15,718787259),g=f(g,v,m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,h),m=t(m,d);return[l,g,v,m]}function a(n){var t,r="";for(t=0;t<32*n.length;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function h(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;for(t=0;t<8*n.length;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function d(n){return a(i(h(n),8*n.length))}function l(n,t){var r,e,o=h(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;16>r;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(h(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="0123456789abcdef",o="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),o+=e.charAt(t>>>4&15)+e.charAt(15&t);return o}function v(n){return unescape(encodeURIComponent(n))}function m(n){return d(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this); + +if (lwsgs_user.substring(0, 1) == "$") { + alert("lwsgs.js: lws generic sessions misconfigured and not providing vars"); +} +function lwsgs_san(s) +{ + if (s.search("<") != -1) + return "invalid string"; + + return s; +} + +function lwsgs_update() +{ + var en_login = 1, en_forgot = 1; + + if (document.getElementById('password').value.length && + document.getElementById('password').value.length < 8) + en_login = 0; + + if (!document.getElementById('username').value || + !document.getElementById('password').value) + en_login = 0; + + if (!document.getElementById('username').value || + document.getElementById('password').value) + en_forgot = 0; + + document.getElementById('login').disabled = !en_login; + document.getElementById('forgot').disabled = !en_forgot; + + if (lwsgs_user) + document.getElementById("curuser1").innerHTML = + document.getElementById("curuser").innerHTML = + lwsgs_san(lwsgs_user); + + if (lwsgs_user === "") + document.getElementById("dlogin").style.display = "inline"; + else { + if (lwsgs_auth & 4) + document.getElementById("dlogout").style.display = "inline"; + else + document.getElementById("dwaitverify").style.display = "inline"; + } + } + +function lwsgs_open_registration() +{ + document.getElementById("dadmin").style.display = "none"; + document.getElementById("dlogin").style.display = "none"; + document.getElementById("dlogout").style.display = "none"; + document.getElementById("dchange").style.display = "none"; + document.getElementById("dregister").style.display = "inline"; +} + +function lwsgs_cancel_registration() +{ + document.getElementById("dadmin").style.display = "none"; + document.getElementById("dregister").style.display = "none"; + document.getElementById("dchange").style.display = "none"; + + if (lwsgs_user === "" || !(lwsgs_auth & 1)) + document.getElementById("dlogin").style.display = "inline"; + else + document.getElementById("dlogout").style.display = "inline"; +} + +function lwsgs_select_change() +{ + document.getElementById("dlogin").style.display = "none"; + document.getElementById("dlogout").style.display = "none"; + document.getElementById("dregister").style.display = "none"; + if (lwsgs_auth & 2) { + document.getElementById("dadmin").style.display = "inline"; + document.getElementById("dchange").style.display = "none"; + } else { + document.getElementById("dadmin").style.display = "none"; + document.getElementById("dchange").style.display = "inline"; + } + + event.preventDefault() +} + +var lwsgs_user_check = '0'; +var lwsgs_email_check = '0'; + +function lwsgs_rupdate() +{ + var en_register = 1, en_forgot = 0; + + if (document.getElementById('rpassword').value == + document.getElementById('password2').value) { + if (document.getElementById('rpassword').value.length) + document.getElementById('match').innerHTML = + "<b class=\"green\">\u2713</b>"; + else + document.getElementById('match').innerHTML = ""; + document.getElementById('pw2').style = ""; + } else { + if (document.getElementById('password2').value || + document.getElementById('email').value) { // ie, he is filling in "register" path and cares + document.getElementById('match').innerHTML = + "<span class=\"bad\">\u2718 <b>Passwords do not match</b></span>"; + } else + document.getElementById('match').innerHTML = + "<span class=\"bad\">\u2718 Passwords do not match</span>"; + + en_register = 0; + } + + if (document.getElementById('rpassword').value.length && + document.getElementById('rpassword').value.length < 8) { + en_register = 0; + document.getElementById('rpw1').innerHTML = "Need 8 chars"; + } else + if (document.getElementById('rpassword').value.length) + document.getElementById('rpw1').innerHTML = "<b class=\"green\">\u2713</b>"; + else + document.getElementById('rpw1').innerHTML = ""; + + if (!document.getElementById('rpassword').value || + !document.getElementById('password2').value || + !document.getElementById('rusername').value || + !document.getElementById('email').value || + lwsgs_email_check === '1'|| + lwsgs_user_check === '1') + en_register = 0; + + document.getElementById('register').disabled = !en_register; + document.getElementById('rpassword').disabled = lwsgs_user_check === '1'; + document.getElementById('password2').disabled = lwsgs_user_check === '1'; + document.getElementById('email').disabled = lwsgs_user_check === '1'; + + if (lwsgs_user_check === '0') { + var uc = document.getElementById('uchk'); + + if (uc) { + if (document.getElementById('rusername').value) + uc.innerHTML = "<b class=\"green\">\u2713</b>"; + else + uc.innerHTML = ""; + } + } else { + if (document.getElementById('uchk')) + ocument.getElementById('uchk').innerHTML = "<b class=\"red\">\u2718 Already registered</b>"; + en_forgot = 1; + } + + if (lwsgs_email_check === '0') { + var ec = document.getElementById('echk'); + + if (ec) { + if (document.getElementById('email').value) + ec.innerHTML = "<b class=\"green\">\u2713</b>"; + else + ec.innerHTML = ""; + } + } else { + if (document.getElementById('echk')) + document.getElementById('echk').innerHTML = "<b class=\"red\">\u2718 Already registered</b>"; + en_forgot = 1; + } + + if (en_forgot) + document.getElementById('rforgot').style.display = "inline"; + else + document.getElementById('rforgot').style.display = "none"; + + if (lwsgs_user_check === '1') + op = '0.5'; + else + op = '1.0'; + document.getElementById('rpassword').style.opacity = op; + document.getElementById('password2').style.opacity = op; + document.getElementById('email').style.opacity = op; + } + +function lwsgs_cupdate() +{ + var en_change = 1, en_forgot = 1, pwok = 1; + + if (lwsgs_auth & 8) { + document.getElementById('ccurpw').style.display = "none"; + document.getElementById('ccurpw_name').style.display = "none"; + } else { + if (!document.getElementById('ccurpw').value || + document.getElementById('ccurpw').value.length < 8) { + en_change = 0; + pwok = 0; + document.getElementById('cuchk').innerHTML = "<b class=\"red\">\u2718</b>"; + } else { + en_forgot = 0; + document.getElementById('cuchk').innerHTML = ""; + } + document.getElementById('ccurpw').style.display = "inline"; + document.getElementById('ccurpw_name').style.display = "inline"; + } + + if (document.getElementById('cpassword').value == + document.getElementById('cpassword2').value) { + if (document.getElementById('cpassword').value.length) + document.getElementById('cmatch').innerHTML = "<b class=\"green\">\u2713</b>"; + else + document.getElementById('cmatch').innerHTML = ""; + document.getElementById('pw2').style = ""; + } else { + if (document.getElementById('cpassword2').value //|| + //document.getElementById('cemail').value + ) { // ie, he is filling in "register" path and cares + document.getElementById('cmatch').innerHTML = + "<span class=\"red\">\u2718 <b>Passwords do not match</b></span>"; + } else + document.getElementById('cmatch').innerHTML = "<span class=\"red\">\u2718 Passwords do not match</span>"; + + en_change = 0; + } + + if (document.getElementById('cpassword').value.length && + document.getElementById('cpassword').value.length < 8) { + en_change = 0; + document.getElementById('cpw1').innerHTML = "Need 8 chars"; + } else { + var cpw = document.getElementById('cpw1'); + + if (cpw) { + if (document.getElementById('cpassword').value.length) + cpw.innerHTML = "<b class=\"green\">\u2713</b>"; + else + cpw.innerHTML = ""; + } + } + + if (!document.getElementById('cpassword').value || + !document.getElementById('cpassword2').value || + pwok === 0) + en_change = 0; + + if (document.getElementById('showdel').checked) + document.getElementById('delete').style.display = "inline"; + else + document.getElementById('delete').style.display = "none"; + + document.getElementById('change').disabled = !en_change; + document.getElementById('cpassword').disabled = pwok === 0; + document.getElementById('cpassword2').disabled = pwok === 0; + document.getElementById('showdel').disabled = pwok === 0; + document.getElementById('delete').disabled = pwok === 0; + //document.getElementById('cemail').disabled = pwok === 0; + + /* + if (lwsgs_auth.parseInt() & 8) { + document.getElementById('cemail').style.display = "none"; + document.getElementById('cemail_name').style.display = "none"; + } else { + document.getElementById('cemail').style.display = "inline"; + document.getElementById('cemail_name').style.display = "inline"; + if (lwsgs_email_check === '0' && + document.getElementById('cemail').value != lwsgs_email) { + if (document.getElementById('cemail').value) + document.getElementById('cechk').innerHTML = "<b style=\"color:green\">\u2713</b>"; + else + document.getElementById('cechk').innerHTML = ""; + } else { + document.getElementById('cechk').innerHTML = "<b style=\"color:red\">\u2718 Already registered</b>"; + en_forgot = 1; + } + } */ + + if (lwsgs_auth & 8) + en_forgot = 0; + + if (en_forgot) + document.getElementById('cforgot').style.display = "inline"; + else + document.getElementById('cforgot').style.display = "none"; + + if (pwok === 0) + op = '0.5'; + else + op = '1.0'; + document.getElementById('cpassword').style.opacity = op; + document.getElementById('cpassword2').style.opacity = op; + // document.getElementById('cemail').style.opacity = op; + } + +function lwsgs_check_user() +{ + var xmlHttp = new XMLHttpRequest(); + xmlHttp.onreadystatechange = function() { + if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { + lwsgs_user_check = xmlHttp.responseText; + lwsgs_rupdate(); + } + } + xmlHttp.open("GET", "lwsgs-check/username="+document.getElementById('rusername').value, true); + xmlHttp.send(null); +} + +function lwsgs_check_email(id) +{ + var xmlHttp = new XMLHttpRequest(); + xmlHttp.onreadystatechange = function() { + if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { + lwsgs_email_check = xmlHttp.responseText; + lwsgs_rupdate(); + } + } + xmlHttp.open("GET", "lwsgs-check/email="+document.getElementById(id).value, true); + xmlHttp.send(null); +} + +function rupdate_user() +{ + lwsgs_rupdate(); + lwsgs_check_user(); +} + +function rupdate_email() +{ + lwsgs_rupdate(); + lwsgs_check_email('email'); +} + +function cupdate_email() +{ + lwsgs_cupdate(); + lwsgs_check_email('cemail'); +} + + +function lwsgs_initial() +{ + document.getElementById('lwsgs').innerHTML = lwsgs_html; + + if (lwsgs_user) { + document.getElementById("curuser").innerHTML = + "currently logged in as " + lwsgs_san(lwsgs_user) + "</br>"; + + document.getElementById("ccuruser").innerHTML = + "<span class=\"gstitle\">Login settings for " + + lwsgs_san(lwsgs_user) + "</span></br>"; + } + + document.getElementById('username').oninput = lwsgs_update; + document.getElementById('username').onchange = lwsgs_update; + document.getElementById('password').oninput = lwsgs_update; + document.getElementById('password').onchange = lwsgs_update; + document.getElementById('doreg').onclick = lwsgs_open_registration; + document.getElementById('clink').onclick = lwsgs_select_change; + document.getElementById('cancel').onclick =lwsgs_cancel_registration; + document.getElementById('cancel2').onclick =lwsgs_cancel_registration; + document.getElementById('rpassword').oninput = lwsgs_rupdate; + document.getElementById('password2').oninput = lwsgs_rupdate; + document.getElementById('rusername').oninput = rupdate_user; + document.getElementById('email').oninput = rupdate_email; + document.getElementById('ccurpw').oninput = lwsgs_cupdate; + document.getElementById('cpassword').oninput = lwsgs_cupdate; + document.getElementById('cpassword2').oninput = lwsgs_cupdate; +<!-- document.getElementById('cemail').oninput = cupdate_email;--> + document.getElementById('showdel').onchange = lwsgs_cupdate; + + if (lwsgs_email) + document.getElementById('grav1').innerHTML = + document.getElementById('grav').innerHTML = + "<img src=\"https://www.gravatar.com/avatar/" + md5(lwsgs_email) + + "?d=identicon\">"; + //if (lwsgs_email) + //document.getElementById('cemail').placeholder = lwsgs_email; + document.getElementById('cusername').value = lwsgs_user; + lwsgs_update(); + lwsgs_cupdate(); +} + +window.addEventListener("load", function() { + lwsgs_initial(); + document.getElementById("nolog").style.display = !!lwsgs_user ? "none" : "inline-block"; + document.getElementById("logged").style.display = !lwsgs_user ? "none" : "inline-block"; + + //document.getElementById("msg").onkeyup = mupd; + //document.getElementById("msg").onchange = mupd; + + var ws; + + function mb_format(s) + { + var r = "", n, wos = 0; + + for (n = 0; n < s.length; n++) { + if (s[n] == ' ') + wos = 0; + else { + wos++; + if (wos === 40) { + wos = 0; + r = r + ' '; + } + } + if (s[n] == '<') { + r = r + "&lt;"; + continue; + } + if (s[n] == '\n') { + r = r + "<br>"; + continue; + } + + r = r + s[n]; + } + + return r; + } + + function add_div(n, m) + { + var q = document.getElementById(n); + var d = new Date(m.time * 1000); + + q.innerHTML = "<br><div class=\"group2\"><table class=\"fixed\"><tr><td>" + + "<img src=\"https://www.gravatar.com/avatar/" + md5(m.email) + + "?d=identicon\"><br>" + + "<b>" + lwsgs_san(m.username) + "</b><br>" + + "<span class=\"small\">" + d.toDateString() + + "<br>" + d.toTimeString() + "</span><br>" + + "IP: " + lwsgs_san(m.ip) + + "</td><td class=\"ava\"><span>" + + mb_format(m.content) + + "</span></td></tr></table></div><br>" + q.innerHTML; + } + + function mupd() + { + //document.getElementById("send").disabled = !document.getElementById("msg").value; + } +}, false); diff --git a/assets/gs/md5.min.js b/assets/gs/md5.min.js new file mode 100644 index 0000000..4bd9de1 --- /dev/null +++ b/assets/gs/md5.min.js @@ -0,0 +1,2 @@ +!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t),e=(n>>16)+(t>>16)+(r>>16);return e<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[(r+64>>>9<<4)+14]=r;var e,i,a,h,d,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,h=v,d=m,l=o(l,g,v,m,n[e],7,-680876936),m=o(m,l,g,v,n[e+1],12,-389564586),v=o(v,m,l,g,n[e+2],17,606105819),g=o(g,v,m,l,n[e+3],22,-1044525330),l=o(l,g,v,m,n[e+4],7,-176418897),m=o(m,l,g,v,n[e+5],12,1200080426),v=o(v,m,l,g,n[e+6],17,-1473231341),g=o(g,v,m,l,n[e+7],22,-45705983),l=o(l,g,v,m,n[e+8],7,1770035416),m=o(m,l,g,v,n[e+9],12,-1958414417),v=o(v,m,l,g,n[e+10],17,-42063),g=o(g,v,m,l,n[e+11],22,-1990404162),l=o(l,g,v,m,n[e+12],7,1804603682),m=o(m,l,g,v,n[e+13],12,-40341101),v=o(v,m,l,g,n[e+14],17,-1502002290),g=o(g,v,m,l,n[e+15],22,1236535329),l=u(l,g,v,m,n[e+1],5,-165796510),m=u(m,l,g,v,n[e+6],9,-1069501632),v=u(v,m,l,g,n[e+11],14,643717713),g=u(g,v,m,l,n[e],20,-373897302),l=u(l,g,v,m,n[e+5],5,-701558691),m=u(m,l,g,v,n[e+10],9,38016083),v=u(v,m,l,g,n[e+15],14,-660478335),g=u(g,v,m,l,n[e+4],20,-405537848),l=u(l,g,v,m,n[e+9],5,568446438),m=u(m,l,g,v,n[e+14],9,-1019803690),v=u(v,m,l,g,n[e+3],14,-187363961),g=u(g,v,m,l,n[e+8],20,1163531501),l=u(l,g,v,m,n[e+13],5,-1444681467),m=u(m,l,g,v,n[e+2],9,-51403784),v=u(v,m,l,g,n[e+7],14,1735328473),g=u(g,v,m,l,n[e+12],20,-1926607734),l=c(l,g,v,m,n[e+5],4,-378558),m=c(m,l,g,v,n[e+8],11,-2022574463),v=c(v,m,l,g,n[e+11],16,1839030562),g=c(g,v,m,l,n[e+14],23,-35309556),l=c(l,g,v,m,n[e+1],4,-1530992060),m=c(m,l,g,v,n[e+4],11,1272893353),v=c(v,m,l,g,n[e+7],16,-155497632),g=c(g,v,m,l,n[e+10],23,-1094730640),l=c(l,g,v,m,n[e+13],4,681279174),m=c(m,l,g,v,n[e],11,-358537222),v=c(v,m,l,g,n[e+3],16,-722521979),g=c(g,v,m,l,n[e+6],23,76029189),l=c(l,g,v,m,n[e+9],4,-640364487),m=c(m,l,g,v,n[e+12],11,-421815835),v=c(v,m,l,g,n[e+15],16,530742520),g=c(g,v,m,l,n[e+2],23,-995338651),l=f(l,g,v,m,n[e],6,-198630844),m=f(m,l,g,v,n[e+7],10,1126891415),v=f(v,m,l,g,n[e+14],15,-1416354905),g=f(g,v,m,l,n[e+5],21,-57434055),l=f(l,g,v,m,n[e+12],6,1700485571),m=f(m,l,g,v,n[e+3],10,-1894986606),v=f(v,m,l,g,n[e+10],15,-1051523),g=f(g,v,m,l,n[e+1],21,-2054922799),l=f(l,g,v,m,n[e+8],6,1873313359),m=f(m,l,g,v,n[e+15],10,-30611744),v=f(v,m,l,g,n[e+6],15,-1560198380),g=f(g,v,m,l,n[e+13],21,1309151649),l=f(l,g,v,m,n[e+4],6,-145523070),m=f(m,l,g,v,n[e+11],10,-1120210379),v=f(v,m,l,g,n[e+2],15,718787259),g=f(g,v,m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,h),m=t(m,d);return[l,g,v,m]}function a(n){var t,r="";for(t=0;t<32*n.length;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function h(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;for(t=0;t<8*n.length;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function d(n){return a(i(h(n),8*n.length))}function l(n,t){var r,e,o=h(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;16>r;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(h(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="0123456789abcdef",o="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),o+=e.charAt(t>>>4&15)+e.charAt(15&t);return o}function v(n){return unescape(encodeURIComponent(n))}function m(n){return d(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this); +//# sourceMappingURL=md5.min.js.map \ No newline at end of file diff --git a/assets/gs/post-forgot-fail.html b/assets/gs/post-forgot-fail.html new file mode 100644 index 0000000..ead3d13 --- /dev/null +++ b/assets/gs/post-forgot-fail.html @@ -0,0 +1,5 @@ +<html> +Sorry, something went wrong. + +Click <a href="../">here</a> to continue. +</html> diff --git a/assets/gs/post-forgot-ok.html b/assets/gs/post-forgot-ok.html new file mode 100644 index 0000000..3e8e9cf --- /dev/null +++ b/assets/gs/post-forgot-ok.html @@ -0,0 +1,6 @@ +<html> +This is a one-time password recovery login. + +Please click <a href="./">here</a> and click your username at the top to reset your password. +</html> + diff --git a/assets/gs/post-verify-fail.html b/assets/gs/post-verify-fail.html new file mode 100644 index 0000000..d1d89ca --- /dev/null +++ b/assets/gs/post-verify-fail.html @@ -0,0 +1,20 @@ +<html> + <head> + <script src="lwsgs.js"></script> + </head> + <body> + <table> + <tr> + <td colspan=2 align=center> + <img src="lwsws-logo.png"> + </td> + </tr> + <tr> + <td> + Sorry, the link was invalid. + </td> + </tr> + </table> + </body> +</html> + diff --git a/assets/gs/post-verify-ok.html b/assets/gs/post-verify-ok.html new file mode 100644 index 0000000..ae647fc --- /dev/null +++ b/assets/gs/post-verify-ok.html @@ -0,0 +1,25 @@ +<html> + <head> + <script src="lwsgs.js"></script> + </head> + <body> + <table> + <tr> + <td colspan=2 align=center> + <img src="lwsgs-logo.png"> + </td> + </tr> + <tr> + <td> + Thanks for signing up, your registration as <span id="u"></span> is verified.<br> + <br> + Click <a href="/lwsgs">here</a> to continue. + </td> + </tr> + </table> + </body> + <script nonce="lwscaro"> + document.getElementById('u').innerHTML = "<b>" + san(lwsgs_user) + "</b>"; + </script> +</html> + diff --git a/assets/gs/sent-forgot-fail.html b/assets/gs/sent-forgot-fail.html new file mode 100644 index 0000000..ead3d13 --- /dev/null +++ b/assets/gs/sent-forgot-fail.html @@ -0,0 +1,5 @@ +<html> +Sorry, something went wrong. + +Click <a href="../">here</a> to continue. +</html> diff --git a/assets/gs/sent-forgot-ok.html b/assets/gs/sent-forgot-ok.html new file mode 100644 index 0000000..83df751 --- /dev/null +++ b/assets/gs/sent-forgot-ok.html @@ -0,0 +1,4 @@ +An email has been sent to your registered address. + +Please follow the instructions to reset your password. + diff --git a/assets/gs/successful-login.html b/assets/gs/successful-login.html new file mode 100644 index 0000000..dfc25cf --- /dev/null +++ b/assets/gs/successful-login.html @@ -0,0 +1,4 @@ +<html> +This is an example destination that will appear after successful non-Admin login +</html> + diff --git a/assets/index.html b/assets/index.html index 11a3f7b..1125093 100644 --- a/assets/index.html +++ b/assets/index.html @@ -2,16 +2,34 @@ <head> <meta charset=utf-8 http-equiv="Content-Language" content="en"/> <link rel="stylesheet" type="text/css" href="sai.css"/> - <script type='text/javascript' src="lws-common.js"></script> - <script type='text/javascript' src='sai.js'></script> - + <link rel="icon" href="sai-icon.svg" sizes="any" type="image/svg+xml"/> + <script type='text/javascript' src='sai.js'></script> + <script type='text/javascript' src="lws-common.js"></script> + <title>Sai</title> </head> <body> - <table class="summary"><tr><td class="logo"> - <img src="sai.svg"><br> - </td><td class="summary" id="summary"> + <div class="summary nailed"> + <table><tr> + <td class="logo"> + <img src="sai.svg"> + </td> + <td class="builder"> + <div id="sai_builders"></div> + </td> + </tr> + <tr><td colspan="2"><div id="sai_sticky"></div></td></tr> + </table> + </div> + <div id="p2" class="undernailed"> - </td></tr></table> + + <td colspan=2 class="summary" id="summary"> + <noscript><span id="noscript" class="noscript">Please enable Javascript</span></noscript> + <div id="sai_overview"></div> + <div id="sai_task"></div> + </td> + </tr> + </div> </body> </html> diff --git a/assets/iosevka-term-ss01-regular.ttf b/assets/iosevka-term-ss01-regular.ttf new file mode 100644 index 0000000..30c2355 Binary files /dev/null and b/assets/iosevka-term-ss01-regular.ttf differ diff --git a/assets/jsplease.svg b/assets/jsplease.svg new file mode 100644 index 0000000..910f1c8 --- /dev/null +++ b/assets/jsplease.svg @@ -0,0 +1,801 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="210mm" + height="297mm" + viewBox="0 0 210 297" + version="1.1" + id="svg8" + inkscape:version="0.92.4 (unknown)" + sodipodi:docname="jsplease.svg"> + <defs + id="defs2"> + <inkscape:path-effect + effect="perspective-envelope" + up_left_point="185.09711,93.617201" + up_right_point="197.14436,94.946763" + down_left_point="185.11892,101.19867" + down_right_point="194.86691,103.92322" + id="path-effect1445" + is_visible="true" + deform_type="perspective" + horizontal_mirror="false" + vertical_mirror="false" + overflow_perspective="false" /> + <linearGradient + inkscape:collect="always" + id="linearGradient1254"> + <stop + style="stop-color:#808080;stop-opacity:1;" + offset="0" + id="stop1250" /> + <stop + style="stop-color:#808080;stop-opacity:0;" + offset="1" + id="stop1252" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient1246"> + <stop + style="stop-color:#808080;stop-opacity:1;" + offset="0" + id="stop1242" /> + <stop + style="stop-color:#808080;stop-opacity:0;" + offset="1" + id="stop1244" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient1238"> + <stop + style="stop-color:#cccccc;stop-opacity:1;" + offset="0" + id="stop1234" /> + <stop + style="stop-color:#cccccc;stop-opacity:0;" + offset="1" + id="stop1236" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient1230"> + <stop + style="stop-color:#cccccc;stop-opacity:1;" + offset="0" + id="stop1226" /> + <stop + style="stop-color:#cccccc;stop-opacity:0;" + offset="1" + id="stop1228" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient1052"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop1048" /> + <stop + style="stop-color:#ffffff;stop-opacity:0;" + offset="1" + id="stop1050" /> + </linearGradient> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter920" + x="-0.15533496" + width="1.3106699" + y="-0.17190397" + height="1.3438079"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.71788603" + id="feGaussianBlur922" /> + </filter> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter932" + x="-0.1776" + width="1.3552" + y="-0.1776" + height="1.3552"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.79111772" + id="feGaussianBlur934" /> + </filter> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter940" + x="-0.14339009" + width="1.2867802" + y="-0.23324818" + height="1.4664964"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.97406385" + id="feGaussianBlur942" /> + </filter> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter948" + x="-0.1317639" + width="1.2635278" + y="-0.2143361" + height="1.4286722"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.89508543" + id="feGaussianBlur950" /> + </filter> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter992" + x="-0.49549423" + width="1.9909885" + y="-0.49549423" + height="1.9909885"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="1.8760989" + id="feGaussianBlur994" /> + </filter> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter996" + x="-0.49549423" + width="1.9909885" + y="-0.49549423" + height="1.9909885"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="1.8760989" + id="feGaussianBlur998" /> + </filter> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient1052" + id="radialGradient1056" + cx="93.143433" + cy="151.93947" + fx="93.143433" + fy="151.93947" + r="1.4365737" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.27122491,0.51582673,-0.64533239,-0.33931981,206.58582,64.335317)" /> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter1132" + x="-0.1992" + width="1.3984" + y="-0.1992" + height="1.3984"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.23847124" + id="feGaussianBlur1134" /> + </filter> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient1052" + id="radialGradient1056-6" + cx="93.143433" + cy="151.93947" + fx="93.143433" + fy="151.93947" + r="1.4365737" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.27122491,0.51582674,-0.6453324,-0.33931981,216.22385,158.38964)" /> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter1132-1" + x="-0.1992" + width="1.3983999" + y="-0.1992" + height="1.3983999"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.23847124" + id="feGaussianBlur1134-2" /> + </filter> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient1052" + id="radialGradient1056-6-0" + cx="93.143433" + cy="151.93947" + fx="93.143433" + fy="151.93947" + r="1.4365737" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.27122491,0.51582674,-0.6453324,-0.33931981,216.22385,158.38964)" /> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter1132-1-6" + x="-0.1992" + width="1.3983999" + y="-0.1992" + height="1.3983999"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.23847124" + id="feGaussianBlur1134-2-0" /> + </filter> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient1052" + id="radialGradient1056-62" + cx="93.143433" + cy="151.93947" + fx="93.143433" + fy="151.93947" + r="1.4365737" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.27122491,0.51582674,-0.6453324,-0.33931981,231.9535,65.773107)" /> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter1132-6" + x="-0.1992" + width="1.3983999" + y="-0.1992" + height="1.3983999"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.23847124" + id="feGaussianBlur1134-1" /> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient1230" + id="linearGradient1232" + x1="117.4561" + y1="161.8735" + x2="118.11756" + y2="158.47173" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-10.438852,-91.586822)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient1238" + id="linearGradient1240" + x1="91.659225" + y1="161.21205" + x2="92.509674" + y2="156.67635" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-9.2104285,-91.681316)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient1246" + id="linearGradient1248" + x1="119.44048" + y1="138.53348" + x2="118.77901" + y2="144.5811" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-9.1159345,-93.949173)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient1254" + id="linearGradient1256" + x1="94.021576" + y1="136.6436" + x2="93.832588" + y2="143.35268" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-9.1159345,-93.949173)" /> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter1274" + x="-0.11357139" + width="1.2271428" + y="-0.044166673" + height="1.0883333"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.42890698" + id="feGaussianBlur1276" /> + </filter> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter1286" + x="-0.13612515" + width="1.2722503" + y="-0.043559985" + height="1.08712"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.36431684" + id="feGaussianBlur1288" /> + </filter> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter1294" + x="-0.054125118" + width="1.1082502" + y="-0.061551673" + height="1.1231033"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.29534737" + id="feGaussianBlur1296" /> + </filter> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter1326" + x="-0.023647035" + width="1.0472941" + y="-0.033139832" + height="1.0662797"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.66722122" + id="feGaussianBlur1328" /> + </filter> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.7" + inkscape:cx="427.67147" + inkscape:cy="680.71208" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:snap-global="false" + inkscape:window-width="1598" + inkscape:window-height="1071" + inkscape:window-x="536" + inkscape:window-y="665" + inkscape:window-maximized="0" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + style="fill:#333333;stroke:#000000;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 47.844128,67.986748 c 0,0 -4.376486,-1.010899 -9.242875,3.043186 -2.156435,1.796485 -4.306374,7.23555 -4.306374,7.23555 l 2.180339,3.581993 c 0,0 1.947173,-6.151597 4.310329,-8.087313 4.23404,-3.468194 8.771706,-2.502907 8.771706,-2.502907 z" + id="path1447" + inkscape:connector-curvature="0" + sodipodi:nodetypes="csccscc" /> + <circle + style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.71303433;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path1401-9-1" + cx="91.265984" + cy="79.667137" + r="1.1193705" /> + <circle + style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.644831;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path1401-7" + cx="91.626122" + cy="79.618477" + r="1.0123003" /> + <circle + style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.71303433;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path1401-9" + cx="91.966797" + cy="71.4422" + r="1.1193705" /> + <circle + style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.644831;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path1401" + cx="92.326942" + cy="71.393524" + r="1.0123003" /> + <path + style="fill:#1a1a1a;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 82.446234,63.320141 h -1.101236 l -2.532848,1.927158 -17.399548,2.31261 -0.770868,3.909393 0.936052,4.845445 3.138525,0.991117 6.497297,4.019513 8.149161,3.248655 -0.991118,-7.433358 z" + id="path1383" + inkscape:connector-curvature="0" /> + <path + style="fill:#333333;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 61.13685,66.10459 -9.889395,0.700821 h -3.114773 c -1.975659,2.819722 -1.934674,3.575076 0.389349,5.995934 l 12.147605,1.09017 z" + id="path1338" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccc" /> + <path + style="fill:#b3b3b3;stroke:#4d4d4d;stroke-width:1.56500006;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter1326)" + d="m 80.28572,37.744627 c 11.8044,0.53454 23.60881,1.06907 35.41321,1.60361 24.57023,8.63233 16.20577,47.2319 -9.08717,46.71161 -9.08716,-0.60344 -18.17432,-1.20689 -27.26148,-1.81033 -25.193152,-7.9214 -19.266982,-42.63337 0.93544,-46.50489 z" + id="path826" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccc" + transform="matrix(0.5600609,0,0,0.5600609,11.454186,6.3530312)" /> + <path + style="fill:url(#linearGradient1232);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter932)" + d="m 105.15521,62.023077 -3.20723,7.75081 10.69078,2.93997 -3.20724,-10.42351 z" + id="path906" + inkscape:connector-curvature="0" + transform="matrix(0.5600609,0,0,0.5600609,11.454186,6.3530312)" /> + <path + style="fill:url(#linearGradient1248);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter940)" + d="m 113.16419,51.034517 5.21176,-8.01809 -3.87541,-0.93544 -7.08265,-0.13363 -5.34539,0.13363 5.47903,9.88897 z" + id="path857-6" + inkscape:connector-curvature="0" + transform="matrix(0.5600609,0,0,0.5600609,11.454186,6.3530312)" /> + <path + style="fill:url(#linearGradient1256);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter948)" + d="m 81.3548,49.638117 -5.21176,-8.01809 3.87541,-0.93544 7.08264,-0.13363 5.34539,0.13363 -5.47902,9.88897 z" + id="path857" + inkscape:connector-curvature="0" + transform="matrix(0.5600609,0,0,0.5600609,11.454186,6.3530312)" /> + <path + style="fill:#000000;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter1294)" + d="m 101.66729,81.710467 -12.16077,-0.53454 1.73725,-8.28536 c 3.78473,-3.53951 7.14427,-3.82653 11.35895,0.53454 z" + id="path832" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccc" + transform="matrix(0.5600609,0,0,0.5600609,11.454186,6.3530312)" /> + <path + style="fill:url(#linearGradient1240);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter920)" + d="m 80.32486,61.260407 -3.20723,8.01809 11.09168,2.00452 -1.87088,-9.35443 z" + id="path904" + inkscape:connector-curvature="0" + transform="matrix(0.5600609,0,0,0.5600609,11.454186,6.3530312)" /> + <path + style="fill:#000000;stroke:none;stroke-width:0.3204501px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter1274)" + d="m 88.19256,45.410897 -5.98851,-0.97111 -3.07519,21.52628 7.44518,1.78036 z" + id="path834" + inkscape:connector-curvature="0" + transform="matrix(0.5600609,0,0,0.5600609,11.454186,6.3530312)" /> + <path + style="fill:#000000;stroke:none;stroke-width:0.39741623px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter1286)" + d="m 111.53332,48.278297 -5.01814,-0.20073 -1.40507,19.67111 5.82102,0.40145 z" + id="path836" + inkscape:connector-curvature="0" + transform="matrix(0.5600609,0,0,0.5600609,11.454186,6.3530312)" /> + <circle + style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:0.14818278;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.44454831, 0.14818277;stroke-dashoffset:0;stroke-opacity:1" + id="path838" + cx="57.866787" + cy="41.578876" + r="1.8710901" /> + <circle + style="opacity:1;fill:#e6e6e6;fill-opacity:1;stroke:none;stroke-width:0.35983333;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:1.07949994, 0.35983331;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter996)" + id="path838-7" + cx="69.327667" + cy="68.213348" + r="4.543582" + transform="matrix(0.5600609,0,0,0.5600609,11.454186,6.3530312)" /> + <circle + style="opacity:1;fill:#e6e6e6;fill-opacity:1;stroke:none;stroke-width:0.35983333;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:1.07949995, 0.35983331;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter992)" + id="path838-7-5" + cx="121.84612" + cy="71.821503" + r="4.543582" + transform="matrix(0.5600609,0,0,0.5600609,11.454186,6.3530312)" /> + <circle + style="opacity:1;fill:#502d16;fill-opacity:1;stroke:none;stroke-width:1.01736069;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path1007-3" + cx="57.813595" + cy="42.192844" + r="1.2162087" /> + <circle + style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.67302322;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path1007" + cx="57.813595" + cy="42.230267" + r="0.80456877" /> + <circle + style="opacity:0.70400002;fill:url(#radialGradient1056);fill-opacity:1;stroke:none;stroke-width:1.20169652;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter1132)" + id="path1007-5" + cx="83.204727" + cy="61.159195" + r="1.4365737" + transform="matrix(0.5600609,0,0,0.5600609,11.454186,6.3530312)" /> + <circle + style="opacity:0.70400002;fill:url(#radialGradient1056-6);fill-opacity:1;stroke:none;stroke-width:1.20169652;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter1132-1)" + id="path1007-5-7" + cx="92.842751" + cy="155.21352" + r="1.4365737" + transform="matrix(0.35981029,0,0,0.35981029,25.006564,-13.710704)" /> + <circle + style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:0.14818278;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.44454831, 0.14818277;stroke-dashoffset:0;stroke-opacity:1" + id="path838-8" + cx="72.074226" + cy="42.384117" + r="1.8710901" /> + <circle + style="opacity:1;fill:#502d16;fill-opacity:1;stroke:none;stroke-width:1.01736069;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path1007-3-7" + cx="72.232727" + cy="42.892242" + r="1.2162087" /> + <circle + style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.67302322;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path1007-9" + cx="72.232727" + cy="42.929665" + r="0.80456877" /> + <circle + style="opacity:0.70400002;fill:url(#radialGradient1056-62);fill-opacity:1;stroke:none;stroke-width:1.20169652;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter1132-6)" + id="path1007-5-2" + cx="108.5724" + cy="62.59697" + r="1.4365737" + transform="matrix(0.5600609,0,0,0.5600609,11.454186,6.3530312)" /> + <circle + style="opacity:0.70400002;fill:url(#radialGradient1056-6-0);fill-opacity:1;stroke:none;stroke-width:1.20169652;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter1132-1-6)" + id="path1007-5-7-0" + cx="92.842751" + cy="155.21352" + r="1.4365737" + transform="matrix(0.35981029,0,0,0.35981029,39.425698,-13.011302)" /> + <path + style="fill:#4d4d4d;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 62.162512,65.637376 1.09017,-0.545096 1.713125,0.545096 2.024598,0.545085 3.348377,-1.791002 9.266431,0.155742 2.024609,-1.557389 h 1.012299 l -3.036908,13.704998 -0.07787,1.946727 0.856563,5.295118 -0.155736,0.233601 -0.856563,0.311484 -0.934434,-5.840203 -0.155736,-1.946727 3.34838,-13.159908 -1.868867,1.245906 -3.036897,10.590225 -6.463147,-0.389349 -2.647554,-1.16804 -1.713122,-2.258211 -4.750027,-0.545079 v -0.934433 z" + id="path1330" + inkscape:connector-curvature="0" /> + <path + style="fill:#666666;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 82.642121,62.911946 0.46722,9.50005 -2.336081,11.602524 h -0.389349 l -1.012299,-5.450849 0.233601,-2.491817 z" + id="path1346" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 61.072342,71.16609 1.012299,4.983635 2.881166,0.545085 2.413947,1.557384 3.659849,2.180339 6.618883,2.803295 -1.16804,-6.69676 -6.463148,-0.778692 -2.647544,-1.401642 -1.868867,-2.024603 z" + id="path1332" + inkscape:connector-curvature="0" /> + <path + style="fill:#333333;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 63.719896,65.014409 -1.012299,-0.856551 h -1.168041 l -1.479513,7.397575 1.245906,6.151676 1.479513,-0.389343 1.713126,-7.864796 z" + id="path1334" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccc" /> + <path + style="fill:#666666;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 68.937134,67.350502 7.086098,-1.012305 1.09017,1.245906 -1.245906,4.672157 -1.557384,0.622956 -6.307411,-3.27051 z" + id="path1340" + inkscape:connector-curvature="0" /> + <path + style="fill:#e6e6e6;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 82.330615,68.129233 0.15586,4.516325 11.758075,1.479608 1.323956,-1.090169 0.07772,-2.569778 -1.635255,-1.479395 z m 9.616913,2.180339 a 1.1291041,1.1291041 0 0 1 1.129133,1.129133 1.1291041,1.1291041 0 0 1 -1.129133,1.129134 1.1291041,1.1291041 0 0 1 -1.129134,-1.129134 1.1291041,1.1291041 0 0 1 1.129134,-1.129133 z" + id="path1342" + inkscape:connector-curvature="0" /> + <path + style="fill:#ececec;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 81.318372,75.916068 0.311512,4.360679 11.602423,1.868833 2.18034,-1.245822 v -2.258266 l -1.557322,-1.401681 z m 9.967175,2.608741 a 1.1291042,1.1291042 0 0 1 1.129133,1.12892 1.1291042,1.1291042 0 0 1 -1.129133,1.129133 1.1291042,1.1291042 0 0 1 -1.129133,-1.129133 1.1291042,1.1291042 0 0 1 1.129133,-1.12892 z" + id="path1344" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 62.084641,76.305461 2.413947,0.934434 6.930358,4.049201 8.020537,3.348369 -1.168041,-7.475441 -2.024603,-0.700827 -6.229541,-1.012299 -2.803286,-1.557383 -2.258205,-2.258211 -4.204943,-0.545079 z" + id="path1367" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 48.69113,69.063622 -0.311478,0.934428 v 1.323782 l 1.09017,1.479513 0.778692,0.233607 -0.934434,-1.868862 -0.07786,-1.401642 0.311472,-0.700826 z" + id="path1369" + inkscape:connector-curvature="0" /> + <path + style="fill:#4d4d4d;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 70.112372,75.709069 0.660743,4.625196 6.772609,2.808151 -1.046177,-6.60742 z" + id="path1371" + inkscape:connector-curvature="0" /> + <path + style="fill:#4d4d4d;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 65.487179,72.515479 -0.385434,4.074577 -2.642966,-0.385434 -1.431611,-5.175819 z" + id="path1373" + inkscape:connector-curvature="0" /> + <path + style="fill:#4d4d4d;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 63.50495,65.687804 -1.211356,4.790386 3.634079,0.660737 0.936052,-4.625195 z" + id="path1375" + inkscape:connector-curvature="0" /> + <path + style="fill:#e6e6e6;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 82.33611,68.165592 0.275314,-0.660743 11.838299,0.880987 -0.550618,0.605683 z" + id="path1377" + inkscape:connector-curvature="0" /> + <path + style="fill:#f2f2f2;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 81.289933,75.874248 0.220249,-0.495553 12.884481,1.266421 -0.440493,0.660748 z" + id="path1379" + inkscape:connector-curvature="0" /> + <path + style="fill:#cccccc;stroke:none;stroke-width:0.14818278px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 49.110015,25.66026 41.277498,37.303193 28.78781,32.857709 h 11.219548 l -13.971514,-8.04421 15.876724,5.080553 -7.409141,-6.562379 9.526037,2.540274 -9.737723,-11.431241 z" + id="path1381" + inkscape:connector-curvature="0" /> + <g + aria-label="Please +enable +Javascript" + inkscape:transform-center-x="13.407495" + inkscape:transform-center-y="-45.676368" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.43051434px;line-height:1.25;font-family:'PT Sans Narrow';-inkscape-font-specification:'PT Sans Narrow';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:middle;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.86920941;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="text1387" + transform="matrix(0.5600609,0,0,0.5600609,11.454186,6.3530312)"> + <path + d="M 143.72112,61.002755 V 69.222 h 1.86706 v -2.618059 h 3.03528 c 2.09653,0 2.60763,-0.928316 2.60763,-2.826669 0,-2.106964 -0.76143,-2.774517 -2.55548,-2.774517 z m 4.61028,1.710604 c 0.6154,0 1.02219,0.04172 1.02219,1.084774 0,0.938746 -0.32334,1.095204 -1.02219,1.095204 h -2.74322 v -2.179978 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1698" + inkscape:connector-curvature="0" /> + <path + d="M 152.29498,61.002755 V 69.222 h 1.7419 v -8.219245 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1700" + inkscape:connector-curvature="0" /> + <path + d="m 161.53641,66.635233 v -0.469374 c 0,-1.387258 0,-1.908784 -0.50067,-2.482462 -0.45894,-0.511095 -1.17865,-0.688414 -2.67021,-0.688414 -2.92054,0 -3.13959,0.876163 -3.13959,3.306473 0,2.451171 0.60497,3.014419 3.41078,3.014419 2.05481,0 2.85796,-0.542387 2.88926,-2.242561 h -1.72104 c -0.0104,0.584109 -0.2399,0.677984 -1.27252,0.677984 -1.09521,0 -1.33511,-0.09388 -1.4707,-0.271194 -0.0939,-0.135596 -0.11474,-0.20861 -0.11474,-0.844871 z m -4.58943,-1.20994 c 0,-0.677984 0.12517,-0.865733 1.38726,-0.865733 1.32467,0 1.4707,0.166888 1.4707,0.865733 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1702" + inkscape:connector-curvature="0" /> + <path + d="m 168.53528,65.018503 c 0,-0.20861 -0.0626,-0.959607 -0.32335,-1.314245 -0.53196,-0.709275 -1.53329,-0.709275 -2.81624,-0.709275 -2.07567,0 -2.82667,0.40679 -2.8371,2.054811 h 1.66888 c -0.0313,-0.531956 0.1356,-0.604969 1.43941,-0.604969 0.98047,0 1.25167,0.01043 1.25167,0.500664 0,0.333777 -0.22948,0.344207 -0.36507,0.354638 l -2.62849,0.177318 c -0.17732,0.01043 -0.70928,0.114736 -0.93875,0.271194 -0.55282,0.365068 -0.62583,1.032621 -0.62583,1.62716 0,1.846201 1.11607,1.929645 2.49289,1.929645 1.2308,0 1.77319,-0.06258 2.14869,-0.657122 0.0104,0.187749 0.0313,0.385929 0.0521,0.573678 h 1.6063 c -0.073,-0.385929 -0.12516,-0.719705 -0.12516,-1.491563 z m -1.59587,2.03395 c 0,0.677984 -0.60497,0.792719 -1.64802,0.792719 -1.06392,0 -1.30382,-0.09387 -1.30382,-0.490234 0,-0.239902 0.0313,-0.479804 0.57368,-0.521526 l 1.79405,-0.125166 c 0.43808,-0.03129 0.53195,-0.156458 0.58411,-0.250332 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1704" + inkscape:connector-curvature="0" /> + <path + d="m 175.5863,65.049794 c -0.0104,-1.470702 -0.15646,-2.054811 -3.077,-2.054811 -1.83577,0 -2.94141,0.239902 -2.94141,1.950506 0,0.59454 0.15646,1.575008 1.42898,1.689744 l 2.62849,0.239901 c 0.42765,0.04172 0.49024,0.19818 0.49024,0.427651 0,0.344207 -0.0209,0.542387 -1.44985,0.542387 -0.30248,0 -0.99089,-0.02086 -1.26209,-0.125166 -0.19818,-0.07301 -0.19818,-0.135597 -0.20861,-0.667553 h -1.64802 c 0.0104,1.877493 0.5111,2.252991 3.18131,2.252991 1.91921,0 3.06657,-0.146027 3.06657,-1.971367 0,-1.481133 -0.60497,-1.762757 -1.58544,-1.846201 l -2.55548,-0.20861 c -0.36506,-0.03129 -0.42765,-0.146027 -0.42765,-0.323346 0,-0.490234 0.41722,-0.490234 1.39769,-0.490234 1.31425,0 1.30382,0.177318 1.28296,0.584108 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1706" + inkscape:connector-curvature="0" /> + <path + d="m 182.9711,66.635233 v -0.469374 c 0,-1.387258 0,-1.908784 -0.50067,-2.482462 -0.45894,-0.511095 -1.17865,-0.688414 -2.67021,-0.688414 -2.92054,0 -3.13959,0.876163 -3.13959,3.306473 0,2.451171 0.60497,3.014419 3.41078,3.014419 2.05481,0 2.85796,-0.542387 2.88926,-2.242561 h -1.72104 c -0.0104,0.584109 -0.2399,0.677984 -1.27252,0.677984 -1.09521,0 -1.33511,-0.09388 -1.47071,-0.271194 -0.0939,-0.135596 -0.11473,-0.20861 -0.11473,-0.844871 z m -4.58943,-1.20994 c 0,-0.677984 0.12517,-0.865733 1.38726,-0.865733 1.32467,0 1.4707,0.166888 1.4707,0.865733 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1708" + inkscape:connector-curvature="0" /> + <path + d="m 150.42793,80.820732 v -0.469373 c 0,-1.387258 0,-1.908784 -0.50066,-2.482462 -0.45894,-0.511096 -1.17865,-0.688414 -2.67021,-0.688414 -2.92055,0 -3.13959,0.876163 -3.13959,3.306473 0,2.45117 0.60497,3.014418 3.41078,3.014418 2.05481,0 2.85796,-0.542386 2.88925,-2.24256 h -1.72103 c -0.0104,0.584108 -0.2399,0.677983 -1.27253,0.677983 -1.0952,0 -1.3351,-0.09387 -1.4707,-0.271193 -0.0939,-0.135597 -0.11473,-0.208611 -0.11473,-0.844872 z m -4.58942,-1.20994 c 0,-0.677983 0.12516,-0.865732 1.38725,-0.865732 1.32468,0 1.47071,0.166888 1.47071,0.865732 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1710" + inkscape:connector-curvature="0" /> + <path + d="M 151.36667,77.263927 V 83.4075 h 1.73147 v -3.44207 c 0,-0.730136 0.0626,-1.189079 1.41855,-1.189079 0.94918,0 1.22037,0.250333 1.22037,0.740567 V 83.4075 h 1.73146 v -4.24522 c 0,-1.773187 -1.20994,-1.981797 -2.39901,-1.981797 -1.2621,0 -1.70018,0.260762 -2.03395,0.719705 l -0.0313,-0.636261 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1712" + inkscape:connector-curvature="0" /> + <path + d="m 164.5717,79.204002 c 0,-0.20861 -0.0626,-0.959607 -0.32335,-1.314244 -0.53195,-0.709275 -1.53328,-0.709275 -2.81624,-0.709275 -2.07567,0 -2.82666,0.40679 -2.8371,2.054811 h 1.66889 c -0.0313,-0.531956 0.13559,-0.60497 1.43941,-0.60497 0.98047,0 1.25166,0.01043 1.25166,0.500665 0,0.333776 -0.22947,0.344207 -0.36507,0.354637 l -2.62849,0.177319 c -0.17732,0.01043 -0.70927,0.114736 -0.93874,0.271193 -0.55282,0.365068 -0.62583,1.032621 -0.62583,1.627161 0,1.846201 1.11606,1.929645 2.49289,1.929645 1.2308,0 1.77319,-0.06258 2.14868,-0.657123 0.0104,0.18775 0.0313,0.385929 0.0522,0.573679 h 1.6063 c -0.073,-0.385929 -0.12517,-0.719706 -0.12517,-1.491564 z m -1.59587,2.033951 c 0,0.677983 -0.60497,0.792719 -1.64802,0.792719 -1.06391,0 -1.30381,-0.09387 -1.30381,-0.490234 0,-0.239902 0.0313,-0.479804 0.57367,-0.521526 l 1.79405,-0.125166 c 0.43808,-0.03129 0.53196,-0.156458 0.58411,-0.250333 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1714" + inkscape:connector-curvature="0" /> + <path + d="M 165.6669,75.188254 V 83.4075 h 1.6063 l 0.0417,-0.60497 c 0.28162,0.344207 0.64669,0.688414 1.96094,0.688414 2.48246,0 2.58676,-1.470703 2.58676,-3.316904 0,-2.013089 -0.38593,-2.993557 -2.62849,-2.993557 -0.89702,0 -1.38725,0.135596 -1.8462,0.584108 v -2.576337 z m 3.05614,3.535945 c 1.24123,0 1.40812,0.271193 1.40812,1.460272 0,1.366397 0,1.773187 -1.28296,1.773187 -1.25166,0 -1.49156,-0.166888 -1.49156,-1.449841 0,-1.585439 0.17732,-1.783618 1.3664,-1.783618 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1716" + inkscape:connector-curvature="0" /> + <path + d="M 173.0517,75.188254 V 83.4075 h 1.74189 v -8.219246 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1718" + inkscape:connector-curvature="0" /> + <path + d="m 182.29312,80.820732 v -0.469373 c 0,-1.387258 0,-1.908784 -0.50067,-2.482462 -0.45894,-0.511096 -1.17865,-0.688414 -2.67021,-0.688414 -2.92054,0 -3.13958,0.876163 -3.13958,3.306473 0,2.45117 0.60497,3.014418 3.41077,3.014418 2.05482,0 2.85797,-0.542386 2.88926,-2.24256 h -1.72104 c -0.0104,0.584108 -0.2399,0.677983 -1.27252,0.677983 -1.0952,0 -1.33511,-0.09387 -1.4707,-0.271193 -0.0939,-0.135597 -0.11474,-0.208611 -0.11474,-0.844872 z m -4.58943,-1.20994 c 0,-0.677983 0.12517,-0.865732 1.38726,-0.865732 1.32467,0 1.4707,0.166888 1.4707,0.865732 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1720" + inkscape:connector-curvature="0" /> + <path + d="m 136.25289,89.373754 v 5.538603 c 0,1.053482 -0.4798,1.053482 -1.7106,1.053482 -0.97004,0 -1.3664,-0.114736 -1.39769,-0.782289 -0.0104,-0.166888 0,-0.970038 -0.0104,-1.147356 h -1.89836 v 0.354637 c 0,2.764087 0.53196,3.285612 3.64025,3.285612 1.71061,0 3.26476,-0.219041 3.26476,-2.732795 v -5.569894 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1722" + inkscape:connector-curvature="0" /> + <path + d="m 145.45259,93.389502 c 0,-0.20861 -0.0626,-0.959607 -0.32334,-1.314245 -0.53196,-0.709275 -1.53329,-0.709275 -2.81624,-0.709275 -2.07567,0 -2.82667,0.40679 -2.8371,2.054811 h 1.66888 c -0.0313,-0.531956 0.1356,-0.604969 1.43941,-0.604969 0.98047,0 1.25166,0.01043 1.25166,0.500664 0,0.333777 -0.22947,0.344207 -0.36506,0.354638 l -2.62849,0.177318 c -0.17732,0.01043 -0.70928,0.114736 -0.93875,0.271194 -0.55282,0.365068 -0.62583,1.032621 -0.62583,1.62716 0,1.846201 1.11606,1.929645 2.49289,1.929645 1.2308,0 1.77319,-0.06258 2.14869,-0.657122 0.0104,0.187749 0.0313,0.385929 0.0522,0.573678 h 1.6063 c -0.073,-0.385929 -0.12517,-0.719705 -0.12517,-1.491563 z m -1.59586,2.03395 c 0,0.677984 -0.60497,0.792719 -1.64803,0.792719 -1.06391,0 -1.30381,-0.09387 -1.30381,-0.490234 0,-0.239902 0.0313,-0.479804 0.57368,-0.521526 l 1.79405,-0.125166 c 0.43808,-0.03129 0.53195,-0.156458 0.58411,-0.250332 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1724" + inkscape:connector-curvature="0" /> + <path + d="m 146.15143,91.449426 2.34687,6.143573 h 2.17998 l 2.34686,-6.143573 h -1.90878 l -1.52286,4.485121 -1.53328,-4.485121 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1726" + inkscape:connector-curvature="0" /> + <path + d="m 159.74239,93.389502 c 0,-0.20861 -0.0626,-0.959607 -0.32335,-1.314245 -0.53195,-0.709275 -1.53328,-0.709275 -2.81624,-0.709275 -2.07567,0 -2.82667,0.40679 -2.8371,2.054811 h 1.66889 c -0.0313,-0.531956 0.13559,-0.604969 1.43941,-0.604969 0.98047,0 1.25166,0.01043 1.25166,0.500664 0,0.333777 -0.22947,0.344207 -0.36507,0.354638 l -2.62849,0.177318 c -0.17732,0.01043 -0.70927,0.114736 -0.93875,0.271194 -0.55281,0.365068 -0.62583,1.032621 -0.62583,1.62716 0,1.846201 1.11607,1.929645 2.4929,1.929645 1.2308,0 1.77318,-0.06258 2.14868,-0.657122 0.0104,0.187749 0.0313,0.385929 0.0522,0.573678 h 1.60629 c -0.073,-0.385929 -0.12516,-0.719705 -0.12516,-1.491563 z m -1.59587,2.03395 c 0,0.677984 -0.60497,0.792719 -1.64802,0.792719 -1.06391,0 -1.30382,-0.09387 -1.30382,-0.490234 0,-0.239902 0.0313,-0.479804 0.57368,-0.521526 l 1.79405,-0.125166 c 0.43808,-0.03129 0.53196,-0.156458 0.58411,-0.250332 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1728" + inkscape:connector-curvature="0" /> + <path + d="m 166.79341,93.420793 c -0.0104,-1.470702 -0.15646,-2.054811 -3.077,-2.054811 -1.83577,0 -2.94141,0.239902 -2.94141,1.950506 0,0.59454 0.15646,1.575008 1.42899,1.689744 l 2.62848,0.239901 c 0.42766,0.04172 0.49024,0.19818 0.49024,0.427651 0,0.344207 -0.0209,0.542387 -1.44984,0.542387 -0.30249,0 -0.9909,-0.02086 -1.26209,-0.125166 -0.19818,-0.07301 -0.19818,-0.135597 -0.20862,-0.667553 h -1.64802 c 0.0104,1.877493 0.5111,2.252991 3.18131,2.252991 1.91922,0 3.06657,-0.146027 3.06657,-1.971367 0,-1.481133 -0.60497,-1.762757 -1.58544,-1.846201 l -2.55547,-0.20861 c -0.36507,-0.03129 -0.42765,-0.146028 -0.42765,-0.323346 0,-0.490234 0.41722,-0.490234 1.39769,-0.490234 1.31424,0 1.30381,0.177318 1.28295,0.584108 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1730" + inkscape:connector-curvature="0" /> + <path + d="m 174.1052,93.806722 c -0.0104,-0.82401 -0.0834,-1.575007 -0.83444,-2.002658 -0.70928,-0.40679 -1.39769,-0.438082 -2.30515,-0.438082 -2.62849,0 -3.05614,0.730136 -3.05614,3.150015 0,2.618059 0.56325,3.160446 3.25432,3.160446 2.44074,0 2.96227,-0.60497 2.9727,-2.597198 h -1.72104 c 0.0104,1.00133 -0.35463,1.00133 -1.46027,1.00133 -1.25166,0 -1.31424,-0.271194 -1.31424,-1.481133 0,-1.595869 0.13559,-1.637591 1.48113,-1.637591 1.00133,0 1.27252,0.09387 1.26209,0.844871 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1732" + inkscape:connector-curvature="0" /> + <path + d="m 175.12738,91.449426 v 6.143573 h 1.73147 v -3.577666 c 0,-0.980469 1.3664,-0.980469 1.81491,-0.980469 0.0313,0 0.43808,0 0.91788,0.03129 v -1.679313 c -0.13559,0 -0.78229,-0.02086 -0.92831,-0.02086 -1.37683,0 -1.66889,0.469373 -1.81491,0.698845 l -0.0522,-0.615401 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1734" + inkscape:connector-curvature="0" /> + <path + d="m 180.56166,89.373754 v 1.512424 h 1.77319 v -1.512424 z m 0.0209,2.075672 v 6.143573 h 1.7419 v -6.143573 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1736" + inkscape:connector-curvature="0" /> + <path + d="m 183.62821,91.449426 v 7.541262 h 1.72104 v -1.940076 c 0.60497,0.625831 1.38726,0.625831 1.73146,0.625831 2.3573,0 2.74323,-1.032621 2.74323,-3.202168 0,-1.940075 -0.26076,-3.108293 -2.53462,-3.108293 -1.36639,0 -1.67931,0.354638 -1.96093,0.667553 l -0.0313,-0.584109 z m 3.09787,1.481133 c 1.12649,0 1.38726,0.187749 1.38726,1.533286 0,1.22037 -0.0209,1.637591 -1.3664,1.637591 -1.17865,0 -1.39769,-0.260763 -1.39769,-1.470703 0,-1.314245 0.073,-1.700174 1.37683,-1.700174 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1738" + inkscape:connector-curvature="0" /> + <path + d="m 191.67014,91.449426 h -1.22037 v 1.595869 h 1.22037 v 2.930974 c 0,1.366398 0.5841,1.700174 2.79537,1.700174 0.41722,0 0.62583,-0.02086 0.82401,-0.05215 v -1.595869 c -1.88792,0.104305 -1.88792,-0.104305 -1.88792,-0.6154 v -2.367727 h 1.8462 v -1.595869 h -1.8462 v -1.481133 h -1.73146 z" + style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Ultra-Bold';text-align:center;text-anchor:middle;stroke-width:0.86920941" + id="path1740" + inkscape:connector-curvature="0" /> + </g> + <path + style="fill:#f2f2f2;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 94.487807,68.376096 1.615787,1.42111 -0.428279,0.642424 -1.713125,-1.518454 z" + id="path1393" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 96.084132,69.738803 -0.03892,2.375017 -0.486676,0.992825 0.09733,-2.705951 z" + id="path1395" + inkscape:connector-curvature="0" /> + <path + style="fill:#f2f2f2;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 94.429404,76.66917 1.304314,1.051235 -0.369881,0.91496 -1.49898,-1.343245 z" + id="path1397" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:none;stroke-width:0.10901695px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 95.772648,77.720405 -0.0584,2.063533 -0.311472,1.206976 0.03892,-2.491818 z" + id="path1399" + inkscape:connector-curvature="0" /> + <g + aria-label="JS" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.09837246px;line-height:1.25;font-family:'PT Sans Narrow';-inkscape-font-specification:'PT Sans Narrow';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#333333;fill-opacity:1;stroke:none;stroke-width:0.75819767;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="text1438" + inkscape:path-effect="#path-effect1445" + transform="matrix(-0.033623,0.41065842,-0.41065842,-0.033623,121.10258,-5.5148654)"> + <path + d="m 188.43439,94.081465 c 0,0 -0.37358,5.583243 -0.37358,5.583243 -0.069,1.030872 -0.38057,0.952312 -1.08201,0.775482 -0.6401,-0.16138 -0.89429,-0.30312 -0.89863,-0.962364 -0.003,-0.134808 0.0103,-0.814808 0.0134,-0.971972 0,0 -0.98298,-0.21108 -0.98298,-0.21108 0.007,2.282516 0.11075,2.932516 1.87342,3.425176 1.02548,0.28663 1.95563,0.4256 2.15915,-1.758063 0,0 0.53505,-5.740673 0.53505,-5.740673 0,0 -1.2438,-0.139749 -1.2438,-0.139749 z" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Bold';fill:#333333;stroke-width:0.75819767" + id="path1440" + inkscape:original-d="m 186.41216,98.102379 v 4.840331 c 0,1.03722 -0.45492,1.03722 -1.50123,1.03722 -0.98263,0 -1.38295,-0.0819 -1.41025,-0.76427 -0.009,-0.13647 -0.009,-0.81885 -0.009,-0.97352 h -1.51943 c 0,2.40197 0.17287,3.10254 3.02976,3.10254 1.56492,0 2.91148,-0.12737 2.91148,-2.35648 v -4.885821 z" + inkscape:connector-curvature="0" /> + <path + d="m 196.01501,98.244806 c 0.51534,-2.168703 0.48979,-3.368626 -2.46756,-3.695007 -1.35212,-0.149222 -2.81152,-0.134579 -3.11049,2.445753 -0.11387,0.982734 -0.025,2.129767 1.02013,2.494917 0,0 2.19003,0.767331 2.19003,0.767331 0.40752,0.14288 0.58778,0.43162 0.46142,1.01003 -0.20498,0.9383 -0.74604,0.80169 -1.7064,0.55921 -1.34158,-0.33873 -1.29744,-0.64479 -1.16094,-1.59149 0,0 -1.18451,-0.268876 -1.18451,-0.268876 -0.22542,2.079016 0.18509,2.663826 2.20668,3.228856 1.37256,0.38363 2.6542,0.528 3.20213,-1.63163 0.13619,-0.53676 0.52856,-2.196283 -1.25936,-2.758804 0,0 -1.99591,-0.632998 -1.99591,-0.632998 -0.54169,-0.177298 -0.61478,-0.505108 -0.55071,-0.95055 0.15237,-1.059404 0.77979,-0.968187 1.65489,-0.840957 1.37373,0.199722 1.44032,0.550838 1.21621,1.609191 0,0 1.48439,0.255024 1.48439,0.255024 z" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'A-OTF Shin Go Pro';-inkscape-font-specification:'A-OTF Shin Go Pro Bold';fill:#333333;stroke-width:0.75819767" + id="path1442" + inkscape:original-d="m 195.24667,100.40427 c -0.0182,-1.58312 -0.30025,-2.374678 -3.12984,-2.374678 -1.40115,0 -2.97517,0.127377 -2.97517,2.138118 0,0.82795 0.27295,1.80148 1.58312,1.92885 l 2.60213,0.25476 c 0.46402,0.0455 0.71877,0.25475 0.71877,0.77336 0,0.87344 -0.62778,0.87344 -1.77418,0.87344 -1.6741,0 -1.6741,-0.30024 -1.6741,-1.20098 h -1.50123 c 0.0182,2.06533 0.65508,2.54754 3.30271,2.54754 1.6832,0 3.12074,-0.20016 3.12074,-2.2382 0,-0.47311 -0.0273,-1.85606 -1.91976,-2.02893 l -2.24729,-0.20927 c -0.63689,-0.0637 -0.77337,-0.31844 -0.77337,-0.68237 0,-0.837054 0.69148,-0.837054 1.62861,-0.837054 1.41025,0 1.54673,0.245656 1.54673,1.055414 z" + inkscape:connector-curvature="0" /> + </g> + </g> +</svg> diff --git a/assets/linux-android.svg b/assets/linux-android.svg new file mode 100644 index 0000000..1cddd64 --- /dev/null +++ b/assets/linux-android.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle cx="1.908" cy="1.908" r="1.908" opacity=".12" fill="#f9f9f9"/><path d="M1.576 3.312c-.042-.012-.086-.05-.106-.092-.016-.033-.02-.082-.02-.246v-.205h-.076c-.103 0-.172-.035-.21-.108-.027-.053-.027-.055-.027-.626V1.46h1.547v.573l-.027.626c-.037.072-.106.108-.21.108h-.076v.207c0 .242-.01.273-.097.318a.16.16 0 0 1-.177-.008c-.065-.046-.074-.085-.074-.312V2.77h-.228v.205c0 .227-.01.266-.074.312-.036.026-.105.038-.148.026zM.802 2.46c-.07-.058-.07-.073-.07-.498l.02-.43c.06-.12.23-.13.304-.017.02.032.023.063.023.448s-.001.416-.023.448c-.036.055-.084.08-.153.08-.05 0-.07-.006-.1-.032zm2.023.01a.24.24 0 0 1-.057-.057c-.02-.032-.023-.063-.023-.448s.001-.416.023-.448c.075-.114.243-.105.304.017.02.037.02.084.02.43 0 .425-.002.44-.07.498-.048.04-.143.045-.197.01zm-1.68-1.125a.72.72 0 0 1 .32-.517l.068-.045-.06-.107a.49.49 0 0 1-.056-.124c.008-.044.036-.02.095.088.053.096.068.114.088.11l.108-.03a.87.87 0 0 1 .408 0l.108.03c.02.005.034-.013.088-.11.06-.107.087-.132.095-.088a.48.48 0 0 1-.056.124l-.06.107.068.045a.72.72 0 0 1 .32.517l.007.057h-1.55zm.46-.245c.032-.032 0-.106-.047-.106s-.078.072-.045.106c.008.008.028.014.046.014s.038-.007.046-.014zm.713-.01c.02-.03 0-.08-.034-.09-.056-.014-.102.06-.062.1.02.02.08.013.097-.01z"/><path d="M1.514 3.27c-.055-.05-.063-.094-.064-.33v-.183h-.087c-.1 0-.15-.023-.19-.087-.023-.038-.024-.06-.024-.624V1.46h1.523v.585l-.024.624c-.04.064-.09.087-.19.087h-.087v.183c0 .236-.01.28-.064.33a.12.12 0 0 1-.109.039c-.056 0-.072-.005-.108-.037-.024-.02-.048-.056-.054-.08a1.58 1.58 0 0 1-.012-.239v-.195H1.8v.195c0 .107-.005.215-.012.24s-.03.06-.054.08c-.036.032-.05.037-.108.037a.12.12 0 0 1-.109-.039zm-.687-.797C.743 2.43.742 2.427.742 1.965c0-.384.002-.416.023-.45.045-.074.157-.1.23-.05.08.054.083.065.083.5l-.02.43c-.043.086-.142.12-.23.076zm2-.007c-.08-.054-.083-.065-.083-.5s.002-.446.083-.5c.073-.05.185-.025.23.05.02.034.023.067.023.45s-.002.416-.023.45c-.045.074-.157.1-.23.05zM1.15 1.356a.66.66 0 0 1 .067-.246C1.28.987 1.365.896 1.51.8l.028-.02-.062-.11C1.41.555 1.403.534 1.433.54a.99.99 0 0 1 .139.216c.002.005.047-.005.1-.023a.61.61 0 0 1 .24-.031c.123 0 .158.005.24.03.053.017.097.028.1.023A.99.99 0 0 1 2.39.54c.03-.006.024.014-.042.132l-.062.11.028.02c.145.095.23.186.292.31a.66.66 0 0 1 .067.246V1.4H1.15zm.44-.24c.007-.005.02-.02.026-.034.034-.065-.068-.124-.11-.063-.025.036-.024.053.005.082s.056.03.08.015zm.724-.015c.03-.03.03-.046.005-.082-.042-.06-.144-.002-.11.063.026.05.068.057.105.02z"/></svg> diff --git a/assets/linux-centos-8.svg b/assets/linux-centos-8.svg new file mode 100644 index 0000000..a54a759 --- /dev/null +++ b/assets/linux-centos-8.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.886 3.886" height="14.687" width="14.687"><defs><style>@font-face{font-family:"PT Sans Narrow";src:url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAicAA8AAAAADQQAAgDFAAAAAAAAAAAAAAAAAAAAAAAAAABHUE9TAAABWAAAABAAAAAQABkADE9TLzIAAAFoAAAAWAAAAGBqGxzMY21hcAAAAcAAAAA3AAABQgANAt5jdnQgAAAB+AAAADAAAAAwCb4BwGZwZ20AAAIoAAABGAAAAZOeSOJvZ2FzcAAAA0AAAAAQAAAAEAAaAAlnbHlmAAADUAAAAlkAAAOUaCkgs2hlYWQAAAWsAAAANgAAADb7Yii/aGhlYQAABeQAAAAgAAAAJAW/AOpobXR4AAAGBAAAAAgAAAAIBLAAbGxvY2EAAAYMAAAABgAAAAYBygAAbWF4cAAABhQAAAAeAAAAIAIRAypuYW1lAAAGNAAAAZcAAALjXFyVG3Bvc3QAAAfMAAAAFAAAACD/uAA0cHJlcAAAB+AAAAC6AAABXCq2+IAAAQAAAAoADAAOAAAAAAAAeJxjYGE8xDiBgZmBgamLKYKBgcEbQjPGMRgx/OJgYuJmZWNiYmZiYmlgYFAXYECAAMcgRwYHBgsGC+Zf/94wMDD/YhRRYGCYDpJj/MK0h0EBCFkAeoMOYXicY2BgYGaAYBkGRgYQsAHyGMF8FgYFIM0ChCC+xf//EPL/SahKBkY2BrIBI/laBxUAAHLRBuoAABEAQQBFADwANwBIAEsATwBAAFYAAAAO/zgADAFAAAgBcQALAfQADgINAAwCvAAPeJxdkD1LxEAQhmdNPI2VYCMswi4hFsce9lYpkoCkiReLHQs/4A7M/QchhYJs4W8Zu7XLHxNvsheuuGY+3hmefXc8gKk9JI39EeIbvfj/9PBx9QsJRM9PCw/CKFV2BYkXbo4MC3PNVWRURVFWLW2Kyil3t3KqUm+vK4qzkHmwdnijCFrbcXywmnKU+3KNeMuceOTEgeOQCZuJsAkEBvzx0rGpFUXXjb231BeS8gKl1qqkobE0FFIj8tZs75Tze3c5eT5hz7M5F6c7SmsplwTo3K5LNfXOScf/mHoPw4Eg4FDIJ4EvMRKjrPSib8KoT7UchVSnmn1iwW8npm5tyU716PRsPPnF0pL68ufwaD2A5MNiCIstm9904wAAAAMACAACABMAAf//AAN4nD3QzU/TcBzH8V9bh7jBaGH9hT2V7IFO2j3AKGs2cDExM5HbEkm2GLgSY7x6wKMugsAJx4ISDz7wIJq0DSf+Ao94WDT6NxgvS/Sm388MXF6//h7SX/tmEqv87Qkb4mcWZyabF386frPhcSaZ3i8mLHusDyckxpXRssNkZ/DcGSw4XHaC506w4Cmc0YanScr/fTfh69Gum/H1vKwiYTEouwVaHJRd29drMofVGk6gEHUDvNrsT8I0CfurTU8LhPGybDiAQdKyGJSsRoObFnpOQHaLQm96Ruh/py7Sh/0G61eIjQFi7xrhB1FwArbAup84AntgZ4jwgRbg4ABkwFdgDeMIaIFdcAimFExHietjwvKpb1wdF+nQOC28B7tgP4JLo8QxeAU+xOh4IBaN0fHX9OwZGvF2ApcBHTwCq+Ab+GPgRnAfVMBz8Bh8BMcm1nL07meVToXeXZunhQ7RZN4Z+pjgB9hCqU2UaqMKB4fgCaq0wQE4A++QQb2M1AU6cdoabg/TPSXUOLhMsoMk5hi+CAUi4CXYBB0EOUKGPbBFnHZih2ixjRbH2kWQLv77CKyBBZADD0E3dVHFAGvgATgBN8A2+IQqT6mK990iXlTwG6CNOrcJ2yrZeWnOKs0WuRoauKpJZCqZF+2goIb4bLE0Z9GkKsxZeioZFNWQJlbsUigVGVlarNWzBVWPy/fqmQWD7+enitOZ8qimDyTTMwk1wYfkWPrNrRV9JGFYyfrK0mK5YYykc+XJpdVI/uaX5F1jtlauTkxPTtxJ1gzbVrQMj5qp+D8gHwquAAAAAAEAAAACAMWmgoIRXw889QgZA+gAAAAAyTePIAAAAADJOVU4ADr/9AGJAsgAAAAJAAIAAAAAAAB4nGNgZGBg/vXvDQMD4yEGKwZLxk4GoAgKYAIAfq4ErALuADIBwgA6AAAAAAHKAAB4nGNgZGBgYGKwYWBmAAFGMMkNEmJ6C2ICAAvdAUAAAHicfY9BSsNAFIb/0aq4sIhLFRnBrWmsghjcaLEgFA1adCnRjmkkNGEmWnsCwQt4AFcewAu40HN4E/9MRrCCZpjM9+b9/3tvAMzjHQLVt8VdsUCdUcUTmMG+40ms4tBxjZobx1NYwL3jaczh0XEdK3iiS9RmGV3j1bHAslhyPIG62HY8iX1x4LhGzYPjKayJZ8fTWBQfjuvYFZ+tLB/pJO4X8u1FNn1/R4aRjrqjXMlO0fPkXppKmzdSK6P0nep5YVeeRgMjjyKts+GJim/TSH/b6ArkuCJg4Q1//O5MaZNkA9n0fH/zXB63O2G3TK9X6X5R5EGjYa50khfGM0nqZTpuUHYxVJdoIUOOETQSxOijgMQLdxM+1w4pRMRshC5VORRvOlT14JH2kHLJH25jI8VT8bzjv1SGdEucssrAKo5sTc3eQ5xQE+OWdcq7392qXoGd4+8agZt4g/s/3ZmdynDajNnS49l3buKc0THa7Ff6v93rY+7yfQWnCtDgMriy787tqz1bNeWZ8TZmvqp2QZ/C5Rf8SYgRAHicY2BmAIP/WxmMGDABEwAsxAHseJxlzj8OAVEQx/Hv+rdLJEREFBIbiWoTNAqNiKDQEIlGo9GoUQo3cBStvOYdwREcwRGYXRMRmyk+b36ZN+/dIMDgkCIROIa0lBysMKVPhwZlwAssmVjixhKPFRO6NL9JliG9nxmsPLb8G0owl7HfJBlblGLDggEtap9Fhpz8wA1u4I+P2+rIcCaPszP4oXtDUSrsI6V/UcI5GApUovyqPtRZqMydVKs+1Xqo3G+ra/WiWvUe+QZevjc4AAA=) format("woff"); font-weight:normal;font-style:normal;}</style></defs><circle cx="1.943" cy="1.943" r="1.943" fill="#f9f9f9" fill-opacity=".12"/><path d="M2.116 2.714V2.31l.567.567-.237.238h-.33zm.56.283l.12-.12-.34-.34c-.322-.322-.34-.342-.34-.392s0-.052.06-.052.062.003.396.336l.336.336.112-.112.122-.112a1.93 1.93 0 0 1 .011.287v.287h-.594zm-.05-.623l-.28-.28h.805v.316l-.123.122-.123.122zm-.71-.71l-.038-.04V.666h-.33l.413-.413.413.413h-.343v.975L2 1.672c-.018.017-.036.03-.04.03a.21.21 0 0 1-.045-.039z" fill="#eea623"/><path d="M1.752 3.403L1.55 3.2h.33v-.48c0-.466.001-.482.03-.518.036-.045.055-.046.094-.005.03.032.03.042.034.515l.004.482.316.014-.4.4zm-.98-1.796v-.165l.238-.237.567.567H.772zm.578-.175l-.34-.34-.238.237V.736h.287l.287.01a.98.98 0 0 1-.112.122l-.112.11.336.336c.333.333.336.337.336.396s0 .06-.053.06-.07-.017-.392-.34zm.16-.175L1.234.98 1.48.736h.315v.4l-.004.4c-.002 0-.128-.125-.28-.277z" fill="#9bcc29"/><path d="M.485 2.122L.29 1.926l.4-.4v.33h.958l.078.075-.06.065H.69l-.004.16-.004.16zm1.645-.417c0-.05.017-.07.34-.392l.34-.34L2.586.75h.552v.552l-.224-.223-.34.34c-.322.322-.342.34-.392.34s-.052-.001-.052-.052zm.504-.22l.273-.273.23.23v.316H2.36zm-.504-.347V.75h.316l.23.23-.546.545z" fill="#922178"/><path d="M.772 2.833V2.55l.11.108c.06.06.115.108.123.108a6.3 6.3 0 0 0 .35-.336c.32-.32.338-.336.388-.336s.053.001.053.053-.017.07-.336.388l-.336.35c0 .008.05.063.108.123l.108.11H.772zm.588.164l-.12-.12.553-.553v.79h-.316zm-.473-.473L.772 2.41v-.314h.79l-.273.273-.28.273a1.42 1.42 0 0 1-.123-.116zm2.346-.36l-.004-.16-.97-.007-.065-.07.033-.035.033-.035h.974l.007-.31.385.386-.39.39z" fill="#252476"/><text xml:space="preserve" x="121.011" y="130.007" font-size="1.839" font-family="PT Sans Narrow" letter-spacing="0" word-spacing="0" writing-mode="lr-tb" direction="ltr" dominant-baseline="auto" baseline-shift="baseline"><tspan x="1.246" y="2.682" font-weight="600" font-family="A-OTF Shin Go Pro">8</tspan></text></svg> diff --git a/assets/linux-fedora-32-riscv.svg b/assets/linux-fedora-32-riscv.svg new file mode 100644 index 0000000..8b826dd --- /dev/null +++ b/assets/linux-fedora-32-riscv.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle cx="1.908" cy="1.908" r="1.908" opacity=".12" fill="#f9f9f9"/><path d="M.63 2.073v-1.06l.334.003.385.018c.157.047.26.15.3.298a.59.59 0 0 1 0 .224c-.02.076-.058.142-.11.196s-.094.08-.172.107c-.052.02-.053.02-.314.022l-.262.004-.005.17.895 1.06.015.02H.63v-1.06zm1.634 1.05l.363-.502.38-.53.117-.16c.008-.01.01.086.01.596v.606h-.438c-.35 0-.436-.002-.43-.01zm-.32-.26l-.505-.598-.06-.075c.003-.002.022-.006.045-.01a.75.75 0 0 0 .357-.171c.055-.05.12-.136.154-.2a.78.78 0 0 0 .083-.338c.01-.22-.067-.433-.212-.59a.82.82 0 0 0-.193-.143.66.66 0 0 0-.138-.05c-.03-.007-.055-.015-.06-.02A27.8 27.8 0 0 1 2.27.666l.86.002.005.638-.098.137-.07.1-.477.668-.242.34-.267.346c-.005 0-.02-.014-.033-.03z" fill="#f5b118"/><path d="M.63 2.073v-1.06l.334.003.385.018c.157.047.26.15.3.298a.59.59 0 0 1 0 .224c-.02.076-.058.142-.11.196s-.094.08-.172.107c-.052.02-.053.02-.314.022l-.262.004-.005.17.895 1.06.015.02H.63v-1.06zm1.634 1.05l.363-.502.38-.53.117-.16c.008-.01.01.086.01.596v.606h-.438c-.35 0-.436-.002-.43-.01z" fill="#686e93"/><path d="M.635 3.128C.633 3.125.63 2.65.63 2.072V1.02H.95l.37.01a.42.42 0 0 1 .253.153c.16.215.086.53-.15.646-.086.042-.108.045-.386.048l-.253.003v.178l.088.105.768.91.04.05c.004.007-.1.01-.518.01-.288 0-.526-.002-.528-.005zm1.63 0a.37.37 0 0 1 .034-.047l.785-1.093.043-.058v.6l-.003.6h-.43a10.96 10.96 0 0 1-.43-.004z" fill="#273170"/></svg> diff --git a/assets/linux-fedora-32.svg b/assets/linux-fedora-32.svg new file mode 100644 index 0000000..bfd8b52 --- /dev/null +++ b/assets/linux-fedora-32.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.886 3.886" height="14.687" width="14.687"><defs><style>@font-face{font-family:"PT Sans Narrow";src:url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAiAAA8AAAAADDgAAgDFAAAAAAAAAAAAAAAAAAAAAAAAAABHUE9TAAABWAAAABAAAAAQABkADE9TLzIAAAFoAAAAVwAAAGBqFRzHY21hcAAAAcAAAABAAAABSgJJAehjdnQgAAACAAAAADAAAAAwCb4BwGZwZ20AAAIwAAABGAAAAZOeSOJvZ2FzcAAAA0gAAAAQAAAAEAAaAAlnbHlmAAADWAAAAjEAAAK8b2pFIWhlYWQAAAWMAAAANgAAADb7Wii/aGhlYQAABcQAAAAgAAAAJAW3AO9obXR4AAAF5AAAAAoAAAAKBOcAe2xvY2EAAAXwAAAACAAAAAgAvgFebWF4cAAABfgAAAAeAAAAIAIQAbpuYW1lAAAGGAAAAZcAAALjXFyVG3Bvc3QAAAewAAAAFAAAACD/uAA1cHJlcAAAB8QAAAC6AAABXCq2+IAAAQAAAAoADAAOAAAAAAAAeJxjYGE8xDiBgZmBgamLKYKBgcEbQjPGMRgx/OJgYuJmZWNiYmZiYmlgYFAXYECAAMcgRwYHoCpj5l//3jAwMP9iFFFgYJgOkmP8wrSHQQEIWQB5OA5WAHicY2BgYGaAYBkGRgYQcAHyGMF8FgYNIM0GpBkZmBiMGIz//wfywfT/C//PQdUDASMbAxmAiZGwmqEDAALwCS4AEQBBAEUAPAA3AEgASwBPAEAAVgAAAA7/OAAMAUAACAFxAAsB9AAOAg0ADAK8AA94nF2QPUvEQBCGZ008jZVgIyzCLiEWxx72VimSgKSJF4sdCz/gDsz9ByGFgmzhbxm7tcsfE2+yF664Zj7eGZ59dzyAqT0kjf0R4hu9+P/08HH1CwlEz08LD8IoVXYFiRdujgwLc81VZFRFUVYtbYrKKXe3cqpSb68rirOQebB2eKMIWttxfLCacpT7co14y5x45MSB45AJm4mwCQQG/PHSsakVRdeNvbfUF5LyAqXWqqShsTQUUiPy1mzvlPN7dzl5PmHPszkXpztKaymXBOjcrks19c5Jx/+Yeg/DgSDgUMgngS8xEqOs9KJvwqhPtRyFVKeafWLBbyembm3JTvXo9Gw8+cXSkvry5/BoPYDkw2IIiy2b33TjAAAAAwAIAAIAEwAB//8AA3icPZG7b9NAHMfvbMV5OCHOw84L241DHmqo3CQYU1S5QuVZAQsMjZARUoVYGBgCArEg1I0NFSok2KqgCgnOp8BCB0akLv4DKsTCxMDiGfj9LMry8e93d9/z3ecIJZf+RHRT2CNtmmeZ/jrX22KfE536TFeCVCKCD8sdcDNHff4FwPIhf52H5h3iIwKyPmsrrBCygs1yClNDfqBSf/ZZ3VcFn/+EmpVDvl+Gxc/r1B8TRs6ts6LdCIqaN44bCxor4405sYqF4lIg0ohX45JV7UAWI1ZUggKNFgc0PuezDGw2gZ35DuJ2XMHeM6mu1eGnGybOI57OATYQE8T0GB6AbxGoExDgPxAa4g4ufw/zfBeu5Bp0NPQE50SnT50Vmmw5TTVPVXdoCGr5iNCyOl2/NJhrLDSLZ04vT7ruG2o9MXTzVK9qOhd6La9yWc5qPbe5dlb/Pp9c1X/pglQbXFw8eX3JyMqEEpcQ+lj4SkzyLb4TyYp9llWYFH7alLakHUn0GVFYOmRpG4qgJEU4XQ9Z3f7nUAdt+qFDGRoZHAYpkJdNySgPXlGj8SvWQlazIXxo8IEIF32LeFUAdAyUA5gljYoBBrdxwAEls5fm1ISBihmbe4SRXUQCV7z4n6sYXcxhhE8RGODbAAq+UN1IbTpeAoy2LCnZ9cCvRu81RytGz/nwe680dEa9mr2gu+Wjjav9tStCy314/+78cTG9Olc5f82/dbM/WlYVp9q4Qchf23HDqAAAAAABAAAAAgDFlWBCPV8PPPUIGQPoAAAAAMk3jyAAAAAAyTlVOAA3//QBhALIAAAACQACAAAAAAAAeJxjYGRgYP717w0DA+MhBnMGO8YWBqAICmACAH5zBKkC7gAyAcIASQA3AAAAAAAAAL4BXnicY2BkYGBgZlBjANEMUJKbgYGJgXEyiAkAB9wAzgAAeJx9j0FKw0AUhv/RqriwiEsVGcGtaayCGNxosSAUDVp0KdGOaSQ0YSZaewLBC3gAVx7AC7jQc3gT/0xGsIJmmMz35v3/e28AzOMdAtW3xV2xQJ1RxROYwb7jSazi0HGNmhvHU1jAveNpzOHRcR0reKJL1GYZXePVscCyWHI8gbrYdjyJfXHguEbNg+MprIlnx9NYFB+O69gVn60sH+kk7hfy7UU2fX9HhpGOuqNcyU7R8+RemkqbN1Iro/Sd6nlhV55GAyOPIq2z4YmKb9NIf9voCuS4ImDhDX/87kxpk2QD2fR8f/NcHrc7YbdMr1fpflHkQaNhrnSSF8YzSeplOm5QdjFUl2ghQ44RNBLE6KOAxAt3Ez7XDilExGyELlU5FG86VPXgkfaQcskfbmMjxVPxvOO/VIZ0S5yyysAqjmxNzd5DnFAT45Z1yrvf3apegZ3j7xqBm3iD+z/dmZ3KcNqM2dLj2Xdu4pzRMdrsV/q/3etj7vJ9BacK0OAyuLLvzu2rPVs15ZnxNma+qnZBn8LlF/xJiBEAeJxjYGYAg/9bGYwYMAEzACzFAe14nGXOPw4BURDH8e/6t0skREQUEhuJahM0Co2IoNAQiUaj0ahRCjdwFK285h3BERzBEZhdExGbKT5vfpk3790gwOCQIhE4hrSUHKwwpU+HBmXACyyZWOLGEo8VE7o0v0mWIb2fGaw8tvwbSjCXsd8kGVuUYsOCAS1qn0WGnPzADW7gj4/b6shwJo+zM/ihe0NRKuwjpX9RwjkYClSi/Ko+1FmozJ1Uqz7Veqjcb6tr9aJa9R75Bl6+NzgAAA==) format("woff"); font-weight:normal;font-style:normal;}</style></defs><circle cx="1.943" cy="1.943" r="1.943" fill="#f9f9f9" fill-opacity=".12"/><path d="M.296 3.233C.334 2.69.23 2.125.362 1.598.492.966 1.027.458 1.664.353c.86-.22 1.744.512 1.877 1.35.06.583-.143 1.223-.662 1.544-.434.373-1.01.325-1.542.32-.363-.03-.907.152-1.04-.333z" fill="#51a2da" paint-order="markers fill stroke"/><path d="M1.814 1.75l.007.27-.33-.013c-.174.064-.357.247-.34.42.002.05-.013.104.025.144.056.08.128.208.222.214.12.035.236.05.342-.007.118-.095.195-.204.23-.34V1.334c.03-.192.127-.407.398-.427.077 0 .157-.016.23.015.12.03.2.12.264.222.042.093.093.19.082.295.006.114-.086.12-.166.147-.034-.032-.093-.046-.1-.098.004-.117-.023-.21-.097-.295-.123-.048-.19-.02-.312.027-.02.075-.064.146-.054.226l.004.293.225.001c.093.098.066.3-.067.273h-.16l.007.566c-.02.052-.026.11-.052.16-.185.235-.406.334-.673.358-.293-.06-.507-.243-.604-.498l-.038-.26a.79.79 0 0 1 .491-.566c.077-.015.153-.034.23-.026l.236.005z" fill="#fff" paint-order="markers fill stroke"/><text xml:space="preserve" x="-626.964" y="-109.218" font-size=".987" font-family="PT Sans Narrow" letter-spacing="0" word-spacing="0" writing-mode="lr-tb" direction="ltr" dominant-baseline="auto" baseline-shift="baseline"><tspan x="1.789" y="3.178" font-weight="600" font-family="A-OTF Shin Go Pro">32</tspan></text></svg> diff --git a/assets/linux-gentoo.svg b/assets/linux-gentoo.svg new file mode 100644 index 0000000..293551a --- /dev/null +++ b/assets/linux-gentoo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><defs><linearGradient y2="648.024" x2="323.018" y1="-1398.277" x1="-67.697" id="A" xlink:href="#B" /><linearGradient id="B" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".342" stop-color="#fff"/><stop offset=".52" stop-color="#bebdf3"/><stop offset=".754" stop-color="#8f82f9"/><stop offset=".871" stop-color="#9f99c7"/><stop offset="1" stop-color="#524c76"/></linearGradient><linearGradient y2="-43.679" x2="294.938" y1="485.472" x1="412.955" id="C" xlink:href="#B" /><radialGradient gradientTransform="matrix(3.28737,0,0,2.51965,111.689,-642.721)" r="31.949" fy="320.086" fx="49.53" cy="320.086" cx="49.53" id="D" xlink:href="#B" /><radialGradient r="218.022" fy="590.59" fx="332.723" cy="590.59" cx="332.723" gradientTransform="matrix(-0.06604,4.19027,-3.57375,-0.056268,2168.15,-1171.42)" id="E" xlink:href="#B" /></defs><g transform="translate(668.589 111.794)"><circle cx="-666.681" cy="-109.886" r="1.908" opacity=".12" fill="#f9f9f9"/><g transform="matrix(.003973 0 0 .00507 -667.79907 -111.555391)"><path d="M25.757 521.914c.068-26.26 22.392-62.924 54.112-96.233 21.198-22.26 42.727-41.8 89.657-81.813-32.462-17.813-82.95-39.304-108.283-67.45-9.374-10.414-26.363-34.008-23.068-65.53 6.716-64.246 86.015-161.746 198.73-182.36 37.504-6.86 80.564-1.908 116.003 14.025C464.088 92.54 600.056 216.717 616.785 277.35c5.888 21.342 4.757 55.36-5.395 74.32-12.58 23.498-56.135 71.225-115.725 120.473-93.5 77.274-224.52 160.25-322.352 174.21-31.61 4.51-63.908-.194-86.573-12.983-35.46-20.01-47.232-46.723-51.37-54.73-11.003-21.293-9.637-48.267-9.613-56.726zM288.9 226.874c1.208-14.89 59.594-.07 55.905 11.725-3.892 12.44-57.29 5.355-55.905-11.725z" fill="url(#A)" fill-rule="evenodd"/><g fill-rule="evenodd"><path d="M201.125 319.265c-30.724 25.94-51.152 43.432-76.763 66.84-61.676 56.37-103.582 99.28-81.977 157.932 24.923 67.66 79.556 64.23 143.622 51.654 165.136-32.57 436.592-241.26 422.756-318.342-11.196-62.37-162.855-186.374-252.65-230.5-28.052-13.785-60.382-20.915-92.81-19.39-123.47 5.828-219.894 118.242-214.97 179.15 4.792 59.256 154.556 110.888 152.79 112.655zM182.59 112.402c-37.153 41.336-13.398 121.31 48.215 164.903 72.096 51.007 188.845 60.242 212.078 20.905 43.335-73.373-3.98-150.955-74.61-185.563-69.795-34.2-162.522-26.012-185.682-.245z" fill="url(#C)"/><path d="M309.296 275.57c52.16 7.25 97.117-13.64 104.147-65.144 5.355-39.234-54.69-82.1-90.79-91.987-30.146-8.256-95.595 2.36-100.008 59.378-4.586 59.272 56.237 93.525 86.65 97.753zm-25.543-98.58c-34.28 53.103 49.77 69.43 62.638 59.745 55.477-41.752-47.536-83.142-62.638-59.745z" fill="url(#D)"/></g><path d="M253.353 41.57c-60.634 6.39-72.247 14.194-127.176 55.753-18.41 13.928-52.294 47.82-60.975 88.025-3.306 15.313.602 28.533 11.63 37.804 35.313 29.688 94.416 53.774 142.904 77.057 14.203 6.82 4.8 19.186-18.104 39.013-57.79 50.032-135.926 110.934-141.38 157.342-2.57 21.868 4.916 51.583 31.46 66.79 31.007 17.764 95.372 6.17 138.35-9.835 62.806-23.388 145.308-75.004 210.807-125.74 68.11-52.76 131.437-119.773 134.57-130.703 3.847-13.422 3.463-25.818-3.96-40.07-11.658-22.382-27.244-39.013-43.98-57.628-44.902-47.34-92.412-88.935-149.254-120.81C337.197 55.548 301.08 36.54 253.353 41.57zM342.42 147.7c43.805 21.18 88.79 76.807-.39 108.825-24.25 8.707-101.158-21.382-98.803-62.444 3.433-60.655 45.707-72.24 99.194-46.38z" fill="url(#E)" fill-rule="evenodd"/></g></g></svg> diff --git a/assets/linux-ubuntu-1804.svg b/assets/linux-ubuntu-1804.svg new file mode 100644 index 0000000..e795c3a --- /dev/null +++ b/assets/linux-ubuntu-1804.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.886 3.886" height="14.687" width="14.687"><defs><style>@font-face{font-family:"PT Sans Narrow";src:url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAfUAA8AAAAAC1wAAgDFAAAAAAAAAAAAAAAAAAAAAAAAAABHUE9TAAABWAAAAFIAAAB2tl+9QU9TLzIAAAGsAAAAWAAAAGBqRRzmY21hcAAAAgQAAAA3AAABQgENAghjdnQgAAACPAAAADAAAAAwCb4BwGZwZ20AAAJsAAABGAAAAZOeSOJvZ2FzcAAAA4QAAAAQAAAAEAAaAAlnbHlmAAADlAAAAVMAAAGEftlGE2hlYWQAAAToAAAANgAAADb7Zyi2aGhlYQAABSAAAAAgAAAAJAXEAMZobXR4AAAFQAAAAAgAAAAIBKAAbWxvY2EAAAVIAAAABgAAAAYAwgAAbWF4cAAABVAAAAAcAAAAIAIQAbJuYW1lAAAFbAAAAZcAAALjXFyVG3Bvc3QAAAcEAAAAFAAAACD/uAA0cHJlcAAABxgAAAC6AAABXCq2+IB4nGNgZGBg4GIwYHBhYEquLMph4MtJLMljkGJgAYoz/P/PAJJHsBkZmLJTi/IY+CAkUJgRjFkYmMA0B5AWAauXYlACskGyDGB9CAhUCQDUqgwLAAB4nGNgYdzEOIGBmYGBqYspgoGBwRtCM8YxGDH84mBi4mZlY2JiZmJiaWBgUBdgQIAAxyBHBgeGJIYk5l//3jAwMP9iFFFgYJgOkmP8wrSHQQEIWQB+iw6leJxjYGBgZoBgGQZGBhCwAfIYwXwWBgUgzQKEIH7S//8Q8v98qEoGRjYGOgBGelhCNgAAoRUHFAAAEQBBAEUAPAA3AEgASwBPAEAAVgAAAA7/OAAMAUAACAFxAAsB9AAOAg0ADAK8AA94nF2QPUvEQBCGZ008jZVgIyzCLiEWxx72VimSgKSJF4sdCz/gDsz9ByGFgmzhbxm7tcsfE2+yF664Zj7eGZ59dzyAqT0kjf0R4hu9+P/08HH1CwlEz08LD8IoVXYFiRdujgwLc81VZFRFUVYtbYrKKXe3cqpSb68rirOQebB2eKMIWttxfLCacpT7co14y5x45MSB45AJm4mwCQQG/PHSsakVRdeNvbfUF5LyAqXWqqShsTQUUiPy1mzvlPN7dzl5PmHPszkXpztKaymXBOjcrks19c5Jx/+Yeg/DgSDgUMgngS8xEqOs9KJvwqhPtRyFVKeafWLBbyembm3JTvXo9Gw8+cXSkvry5/BoPYDkw2IIiy2b33TjAAAAAwAIAAIAEwAB//8AA3icLY6/S8NAFMfvLiY9GzhjY9rQ4g9aaymBRpNakWJxsNDBgmM7RIcOFZ3qINJJpKCDjoIOjuKaHKWI7So4ZtL/wingqPeKy4f34b335YsIqv7+4DsyQQwt4oIft1qcMskaIqYx4vEjhs+4DQgZ9jhiuMc1oT7Tgjk58kk4QlSjK1Ty+CUVFxoVtwcwhYBdins+1QJDiXi4jL028lG95ct2JpATtfZUsBCc/BdViBqvtTmR1fnEdpDAkS9rQRpH6xt4Wu9BErnPCvaGMSWliI5fYuYfgNeYwFVcrE70vk680bF5Yd6YotujKTZjEwrwewiYgQBDyUPAO/x+Al4AY0g51wX68PUGOAXcAp4EdNdwK66TNBaUXHatsLWEXaeyWS6RXFaJVRtSca/Z7B6W9vUGK6bKO2oqn161VTL5dvqD64HBbDrb6Vp1J2PlEfoDRYBrZgAAAQAAAAIAxQxeziFfDzz1CBkD6AAAAADJN48gAAAAAMk5VTgAO//3AY0CvAAAAAkAAgAAAAAAAHicY2BkYGD+9e8NAwPjJgZrBlXGXgagCApgAgB73QSNAu4AMgGyADsAAAAAAMIAAHicY2BkYGBgYpADYhBgBJPcICHGySAmAAcQAMZ4nH2PQUrDQBSG/9GquLCISxUZwa1prIIY3GixIBQNWnQp0Y5pJDRhJlp7AsELeABXHsALuNBzeBP/TEawgmaYzPfm/f97bwDM4x0C1bfFXbFAnVHFE5jBvuNJrOLQcY2aG8dTWMC942nM4dFxHSt4okvUZhld49WxwLJYcjyButh2PIl9ceC4Rs2D4ymsiWfH01gUH47r2BWfrSwf6STuF/LtRTZ9f0eGkY66o1zJTtHz5F6aSps3Uiuj9J3qeWFXnkYDI48irbPhiYpv00h/2+gK5LgiYOENf/zuTGmTZAPZ9Hx/81wetztht0yvV+l+UeRBo2GudJIXxjNJ6mU6blB2MVSXaCFDjhE0EsToo4DEC3cTPtcOKUTEbIQuVTkUbzpU9eCR9pByyR9uYyPFU/G8479UhnRLnLLKwCqObE3N3kOcUBPjlnXKu9/dql6BnePvGoGbeIP7P92Zncpw2ozZ0uPZd27inNEx2uxX+r/d62Pu8n0FpwrQ4DK4su/O7as9WzXlmfE2Zr6qdkGfwuUX/EmIEQB4nGNgZgCD/1sZjBgwARMALMQB7HicZc4/DgFREMfx7/q3SyRERBQSG4lqEzQKjYig0BCJRqPRqFEKN3AUrbzmHcERHMERmF0TEZspPm9+mTfv3SDA4JAiETiGtJQcrDClT4cGZcALLJlY4sYSjxUTujS/SZYhvZ8ZrDy2/BtKMJex3yQZW5Riw4IBLWqfRYac/MANbuCPj9vqyHAmj7Mz+KF7Q1Eq7COlf1HCORgKVKL8qj7UWajMnVSrPtV6qNxvq2v1olr1HvkGXr43OAAA) format("woff"); font-weight:normal;font-style:normal;}</style></defs><circle r="1.943" cy="1.943" cx="1.943" fill="#f9f9f9" fill-opacity=".522"/><g transform="matrix(1.48833 0 0 1.48833 901.015 169.1445)"><circle cx="-604.071" cy="-112.35" r="1.126" fill="#fff" paint-order="markers fill stroke"/><path d="M-604.277-111.167c-.26-.05-.475-.16-.662-.354a1.14 1.14 0 0 1-.283-.478 1.2 1.2 0 0 1 .794-1.496c.418-.127.868-.02 1.18.282.2.193.31.405.355.678.09.54-.21 1.075-.723 1.293a1.35 1.35 0 0 1-.661.08zm.715-.347c.08-.07.08-.174 0-.247-.05-.05-.14-.06-.2-.02-.08.05-.103.15-.05.227.03.05.08.08.147.08.05 0 .06 0 .1-.04zm-.37-.145c.03-.01.05-.02.07-.08.04-.09.1-.12.206-.117.08 0 .08 0 .118-.05.08-.1.16-.29.16-.386v-.03h-.24v.04c-.01.06-.05.142-.09.2a.48.48 0 0 1-.5.159c-.04-.01-.07-.01-.07-.01l-.06.103-.05.09.03.02c.09.05.33.08.434.05zm-.467-.207l.06-.1-.06-.06c-.03-.03-.08-.09-.1-.135-.03-.06-.03-.09-.03-.188s0-.125.03-.185c.02-.04.06-.1.09-.136l.06-.07-.06-.1c-.03-.05-.06-.1-.07-.1s-.1.07-.144.123c-.05.06-.117.17-.135.234-.01.05-.01.06.01.08.08.08.07.212 0 .302-.02.03-.01.07.05.185.04.07.19.237.22.237.01 0 .04-.04.07-.1zm-.424-.334c.09-.04.12-.174.05-.252s-.18-.09-.253 0c-.03.03-.04.05-.04.1 0 .13.12.21.237.154zm1.448-.214c0-.1-.06-.26-.148-.37l-.05-.06h-.09c-.08 0-.09 0-.137-.04-.03-.02-.06-.05-.07-.09-.03-.07-.04-.07-.218-.07-.108 0-.15 0-.215.03-.04.02-.09.03-.09.04a1.45 1.45 0 0 0 .109.207l.08-.02a.46.46 0 0 1 .439.114.54.54 0 0 1 .135.246v.05h.12c.117 0 .12 0 .12-.03zm-.18-.526c.106-.1.05-.267-.1-.28-.133-.01-.22.113-.164.234.02.05.04.06.1.08s.12 0 .164-.04z" fill="#ff7f2a"/></g><text y="-230.755" x="-637.093" xml:space="preserve" font-size="1.839" font-family="PT Sans Narrow" letter-spacing="0" word-spacing="0" writing-mode="lr-tb" direction="ltr" dominant-baseline="auto" baseline-shift="baseline"><tspan y="2.509" x="1.362" font-weight="600" font-family="A-OTF Shin Go Pro">b</tspan></text></svg> \ No newline at end of file diff --git a/assets/linux-ubuntu-2004.svg b/assets/linux-ubuntu-2004.svg new file mode 100644 index 0000000..8c389d5 --- /dev/null +++ b/assets/linux-ubuntu-2004.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.886 3.886" height="14.687" width="14.687"><defs><style>@font-face{font-family:"PT Sans Narrow";src:url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAeQAA8AAAAACwAAAgDFAAAAAAAAAAAAAAAAAAAAAAAAAABHUE9TAAABWAAAAFIAAAB2tl+9QU9TLzIAAAGsAAAAWAAAAGBqSRw5Y21hcAAAAgQAAAA3AAABQgENAgxjdnQgAAACPAAAADAAAAAwCb4BwGZwZ20AAAJsAAABGAAAAZOeSOJvZ2FzcAAAA4QAAAAQAAAAEAAaAAlnbHlmAAADlAAAAQ0AAAEoYDnOOmhlYWQAAASkAAAANgAAADb6zCjFaGhlYQAABNwAAAAgAAAAJAUq/9NobXR4AAAE/AAAAAgAAAAIA+8AQmxvY2EAAAUEAAAABgAAAAYAlAAAbWF4cAAABQwAAAAcAAAAIAIPAa1uYW1lAAAFKAAAAZcAAALjXFyVG3Bvc3QAAAbAAAAAFAAAACD/uAA0cHJlcAAABtQAAAC6AAABXCq2+IB4nGNgZGBg4GIwYHBhYEquLMph4MtJLMljkGJgAYoz/P/PAJJHsBkZmLJTi/IY+CAkUJgRjFkYmMA0B5AWAauXYlACskGyDGB9CAhUCQDUqgwLAAB4nGNgYWRknMDAzMDA1MUUwcDA4A2hGeMYjBh+cTAxcbOyMTExMzGxNDAwqAswIECAY5AjgwNDGkMa869/bxgYmH8xiigwMEwHyTF+YdrDoACELAA/Lg38eJxjYGBgZoBgGQZGBhCwAfIYwXwWBgUgzQKEIH7a//8Q8v9sqEoGRjYGOgFGellEMgAApX0HGAAAEQBBAEUAPAA3AEgASwBPAEAAVgAAAA7/OAAMAUAACAFxAAsB9AAOAg0ADAK8AA94nF2QPUvEQBCGZ008jZVgIyzCLiEWxx72VimSgKSJF4sdCz/gDsz9ByGFgmzhbxm7tcsfE2+yF664Zj7eGZ59dzyAqT0kjf0R4hu9+P/08HH1CwlEz08LD8IoVXYFiRdujgwLc81VZFRFUVYtbYrKKXe3cqpSb68rirOQebB2eKMIWttxfLCacpT7co14y5x45MSB45AJm4mwCQQG/PHSsakVRdeNvbfUF5LyAqXWqqShsTQUUiPy1mzvlPN7dzl5PmHPszkXpztKaymXBOjcrks19c5Jx/+Yeg/DgSDgUMgngS8xEqOs9KJvwqhPtRyFVKeafWLBbyembm3JTvXo9Gw8+cXSkvry5/BoPYDkw2IIiy2b33TjAAAAAwAIAAIAEwAB//8AA3icLY7NSsNQEIVncm80RCm3SZopURRa6KIF/2K7soUuBBe6c9GAUZdduIlrH6KP0Dfw5hq6Fp8gDxDwFVxkLU6KDJwz3zAcDiAoADyyvqALlXZHC+OQGJlnwsy8E6bmu5EnwtdiRWuysgJIkZUWmj7ZNCmNpdEOpgU4ytle8sCudbvUgdJ+ZX58DgAfs+a3WxkVMa9YEtBwvdB4epCjN0u2YDPY4T+EDKHLYKt8H2uNqklFlRNDUJ6dY1O4cEUkuNCVuGXbuDKSQynSzYN8kW+8JGAGElM/Hl9O4ouQdk+w32tZnbjTb/Oo6WQeDUPPO24F4Z5YLm+mWB8+3u+4M8dWvfFg/nuHH3+/r09lAAAAAAEAAAACAMVNvcSJXw889QgZA+gAAAAAyTePIAAAAADJOVU4ABAAAAEeAsIAAAAJAAIAAAAAAAB4nGNgZGBg/vXvDQMDIyODwP/HjHIMQBEUwAQAg/oE/wLuADIBAQAQAAAAAACUAAB4nGNgZGBgYGKQZADRDFCSGyTEOBnEBAAGfADAeJx9j0FKw0AUhv/RqriwiEsVGcGtaayCGNxosSAUDVp0KdGOaSQ0YSZaewLBC3gAVx7AC7jQc3gT/0xGsIJmmMz35v3/e28AzOMdAtW3xV2xQJ1RxROYwb7jSazi0HGNmhvHU1jAveNpzOHRcR0reKJL1GYZXePVscCyWHI8gbrYdjyJfXHguEbNg+MprIlnx9NYFB+O69gVn60sH+kk7hfy7UU2fX9HhpGOuqNcyU7R8+RemkqbN1Iro/Sd6nlhV55GAyOPIq2z4YmKb9NIf9voCuS4ImDhDX/87kxpk2QD2fR8f/NcHrc7YbdMr1fpflHkQaNhrnSSF8YzSeplOm5QdjFUl2ghQ44RNBLE6KOAxAt3Ez7XDilExGyELlU5FG86VPXgkfaQcskfbmMjxVPxvOO/VIZ0S5yyysAqjmxNzd5DnFAT45Z1yrvf3apegZ3j7xqBm3iD+z/dmZ3KcNqM2dLj2Xdu4pzRMdrsV/q/3etj7vJ9BacK0OAyuLLvzu2rPVs15ZnxNma+qnZBn8LlF/xJiBEAeJxjYGYAg/9bGYwYMAETACzEAex4nGXOPw4BURDH8e/6t0skREQUEhuJahM0Co2IoNAQiUaj0ahRCjdwFK285h3BERzBEZhdExGbKT5vfpk3790gwOCQIhE4hrSUHKwwpU+HBmXACyyZWOLGEo8VE7o0v0mWIb2fGaw8tvwbSjCXsd8kGVuUYsOCAS1qn0WGnPzADW7gj4/b6shwJo+zM/ihe0NRKuwjpX9RwjkYClSi/Ko+1FmozJ1Uqz7Veqjcb6tr9aJa9R75Bl6+NzgAAA==) format("woff"); font-weight:normal;font-style:normal;}</style></defs><circle cx="1.943" cy="1.943" r="1.943" fill="#f9f9f9" fill-opacity=".522"/><g transform="matrix(1.48833 0 0 1.48833 901.017 169.1449)"><circle r="1.126" cy="-112.35" cx="-604.071" fill="#fff" paint-order="markers fill stroke"/><path d="M-604.277-111.167c-.26-.05-.475-.16-.662-.354a1.14 1.14 0 0 1-.283-.478 1.2 1.2 0 0 1 .794-1.496c.418-.127.868-.02 1.18.282.2.193.31.405.355.678.09.54-.21 1.075-.723 1.293a1.35 1.35 0 0 1-.661.08zm.715-.347c.08-.07.08-.174 0-.247-.05-.05-.14-.06-.2-.02-.08.05-.103.15-.05.227.03.05.08.08.147.08.05 0 .06 0 .1-.04zm-.37-.145c.03-.01.05-.02.07-.08.04-.09.1-.12.206-.117.08 0 .08 0 .118-.05.08-.1.16-.29.16-.386v-.03h-.24v.04c-.01.06-.05.142-.09.2a.48.48 0 0 1-.5.159c-.04-.01-.07-.01-.07-.01l-.06.103-.05.09.03.02c.09.05.33.08.434.05zm-.467-.207l.06-.1-.06-.06c-.03-.03-.08-.09-.1-.135-.03-.06-.03-.09-.03-.188s0-.125.03-.185c.02-.04.06-.1.09-.136l.06-.07-.06-.1c-.03-.05-.06-.1-.07-.1s-.1.07-.144.123c-.05.06-.117.17-.135.234-.01.05-.01.06.01.08.08.08.07.212 0 .302-.02.03-.01.07.05.185.04.07.19.237.22.237.01 0 .04-.04.07-.1zm-.424-.334c.09-.04.12-.174.05-.252s-.18-.09-.253 0c-.03.03-.04.05-.04.1 0 .13.12.21.237.154zm1.448-.214c0-.1-.06-.26-.148-.37l-.05-.06h-.09c-.08 0-.09 0-.137-.04-.03-.02-.06-.05-.07-.09-.03-.07-.04-.07-.218-.07-.108 0-.15 0-.215.03-.04.02-.09.03-.09.04a1.45 1.45 0 0 0 .109.207l.08-.02a.46.46 0 0 1 .439.114.54.54 0 0 1 .135.246v.05h.12c.117 0 .12 0 .12-.03zm-.18-.526c.106-.1.05-.267-.1-.28-.133-.01-.22.113-.164.234.02.05.04.06.1.08s.12 0 .164-.04z" fill="#ff7f2a"/></g><text xml:space="preserve" x="-613.117" y="-109.556" font-size="1.839" font-family="PT Sans Narrow" letter-spacing="0" word-spacing="0" writing-mode="lr-tb" direction="ltr" dominant-baseline="auto" baseline-shift="baseline"><tspan x="1.58" y="2.65" font-weight="600" font-family="A-OTF Shin Go Pro">f</tspan></text></svg> \ No newline at end of file diff --git a/assets/linux-ubuntu-xenial-amd64.svg b/assets/linux-ubuntu-xenial-amd64.svg new file mode 100644 index 0000000..f4387a7 --- /dev/null +++ b/assets/linux-ubuntu-xenial-amd64.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.886 3.886" height="14.687" width="14.687"><defs><style>@font-face{font-family:"PT Sans Narrow";src:url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAgEAA8AAAAAC9gAAgDFAAAAAAAAAAAAAAAAAAAAAAAAAABHUE9TAAABWAAAAFIAAAB2tmW9GU9TLzIAAAGsAAAAWAAAAGBqWxzmY21hcAAAAgQAAAA3AAABQgANAx5jdnQgAAACPAAAADAAAAAwCb4BwGZwZ20AAAJsAAABGAAAAZOeSOJvZ2FzcAAAA4QAAAAQAAAAEAAaAAlnbHlmAAADlAAAAYIAAAIAFzlWsmhlYWQAAAUYAAAANgAAADb7Oif3aGhlYQAABVAAAAAgAAAAJAWYAJ5obXR4AAAFcAAAAAgAAAAIBIoARWxvY2EAAAV4AAAABgAAAAYBAAAAbWF4cAAABYAAAAAcAAAAIAIPAdVuYW1lAAAFnAAAAZcAAALjXFyVG3Bvc3QAAAc0AAAAFAAAACD/uAA0cHJlcAAAB0gAAAC6AAABXCq2+IB4nGNgZGBg4GIwYHBhYEquLMph4MtJLMljkGJgAYoz/P/PAJJHsBkZmLJTi/IY+CAkUJgRjFkYmMA0B5CWAKuXA0ImkOj/p0D8BawXDgEYaQ/lAAB4nGNgYZzDOIGBmYGBqYspgoGBwRtCM8YxGDH84mBi4mZlY2JiZmJiaWBgUBdgQIAAxyBHBgeGCoYK5l//3jAwMP9iFFFgYJgOkmP8wrSHQQEIWQB7tQ67eJxjYGBgZoBgGQZGBhCwAfIYwXwWBgUgzQKEIH7F//8Q8n8nVCUDIxvDgAHGgbMaBQAAuVEHKgAAEQBBAEUAPAA3AEgASwBPAEAAVgAAAA7/OAAMAUAACAFxAAsB9AAOAg0ADAK8AA94nF2QPUvEQBCGZ008jZVgIyzCLiEWxx72VimSgKSJF4sdCz/gDsz9ByGFgmzhbxm7tcsfE2+yF664Zj7eGZ59dzyAqT0kjf0R4hu9+P/08HH1CwlEz08LD8IoVXYFiRdujgwLc81VZFRFUVYtbYrKKXe3cqpSb68rirOQebB2eKMIWttxfLCacpT7co14y5x45MSB45AJm4mwCQQG/PHSsakVRdeNvbfUF5LyAqXWqqShsTQUUiPy1mzvlPN7dzl5PmHPszkXpztKaymXBOjcrks19c5Jx/+Yeg/DgSDgUMgngS8xEqOs9KJvwqhPtRyFVKeafWLBbyembm3JTvXo9Gw8+cXSkvry5/BoPYDkw2IIiy2b33TjAAAAAwAIAAIAEwAB//8AA3icNZA9SwNBEIZn9867zXreh/EDtLBQNJIiikUURCstgqBIECMc+BfiH9BCCwvRQhGLgIqIEIt1OAJia6fFFna2/gALEQQRZyQ2z77vO3fDzICAPIDYFx8QiUeji+uowCniCIh6dgxXIOvYApHiDCMCsYWfrDyuv8AbyBSrpDHmdJNgIDbCYqzIlxn3hNaDelKvyqnjgaIWEWOb8cxfDChq8MUqx6pKaO2oI3WuHOrJuasg6Z4xbmykxUVJiePKduJZrHiUdLgeJyo2vs0q/oYvUzY5i+UclQNQXA5ioy12BrptQotdQciG5o4sTiQ0wXlCP5BKs5VkM5FpDQwsrBuvNHjndc/V/owkI/9NSCbUbaPJaDIYSG6MbsijIgQuP8oN6JmYFH/HLvDZzgThkre65UVOfEKTUaDdM60GFF15lJc4ZdwwmprrAeE6JIxHhAahlh+bGhLzYsrPD5dEJIYvdtcqPb1Ly3sHq9W+/uqygI/D6enD95/vxuxs4xdx2pDtAAAAAQAAAAIAxd3KqINfDzz1CBkD6AAAAADJN48gAAAAAMk5VTgAEwAAAYkB9AAAAAkAAgAAAAAAAHicY2BkYGD+9e8NAwPjHAZhBmHGTgagCApgAgB0WQQ5Au4AMgGcABMAAAAAAQAAAHicY2BkYGBgYhBgANEMUJIbJMR4BMQEAAaQAOh4nH2PQUrDQBSG/9GquLCISxUZwa1prIIY3GixIBQNWnQp0Y5pJDRhJlp7AsELeABXHsALuNBzeBP/TEawgmaYzPfm/f97bwDM4x0C1bfFXbFAnVHFE5jBvuNJrOLQcY2aG8dTWMC942nM4dFxHSt4okvUZhld49WxwLJYcjyButh2PIl9ceC4Rs2D4ymsiWfH01gUH47r2BWfrSwf6STuF/LtRTZ9f0eGkY66o1zJTtHz5F6aSps3Uiuj9J3qeWFXnkYDI48irbPhiYpv00h/2+gK5LgiYOENf/zuTGmTZAPZ9Hx/81wetztht0yvV+l+UeRBo2GudJIXxjNJ6mU6blB2MVSXaCFDjhE0EsToo4DEC3cTPtcOKUTEbIQuVTkUbzpU9eCR9pByyR9uYyPFU/G8479UhnRLnLLKwCqObE3N3kOcUBPjlnXKu9/dql6BnePvGoGbeIP7P92Zncpw2ozZ0uPZd27inNEx2uxX+r/d62Pu8n0FpwrQ4DK4su/O7as9WzXlmfE2Zr6qdkGfwuUX/EmIEQB4nGNgZgCD/1sZjBgwARMALMQB7HicZc4/DgFREMfx7/q3SyRERBQSG4lqEzQKjYig0BCJRqPRqFEKN3AUrbzmHcERHMERmF0TEZspPm9+mTfv3SDA4JAiETiGtJQcrDClT4cGZcALLJlY4sYSjxUTujS/SZYhvZ8ZrDy2/BtKMJex3yQZW5Riw4IBLWqfRYac/MANbuCPj9vqyHAmj7Mz+KF7Q1Eq7COlf1HCORgKVKL8qj7UWajMnVSrPtV6qNxvq2v1olr1HvkGXr43OAAA) format("woff"); font-weight:normal;font-style:normal;}</style></defs><circle cx="1.943" cy="1.943" r="1.943" fill="#f9f9f9" fill-opacity=".522"/><g transform="matrix(1.48833 0 0 1.48833 901.016 169.1453)"><circle r="1.126" cy="-112.35" cx="-604.071" fill="#fff" paint-order="markers fill stroke"/><path d="M-604.277-111.167c-.26-.05-.475-.16-.662-.354a1.14 1.14 0 0 1-.283-.478 1.2 1.2 0 0 1 .794-1.496c.418-.127.868-.02 1.18.282.2.193.31.405.355.678.09.54-.21 1.075-.723 1.293a1.35 1.35 0 0 1-.661.08zm.715-.347c.08-.07.08-.174 0-.247-.05-.05-.14-.06-.2-.02-.08.05-.103.15-.05.227.03.05.08.08.147.08.05 0 .06 0 .1-.04zm-.37-.145c.03-.01.05-.02.07-.08.04-.09.1-.12.206-.117.08 0 .08 0 .118-.05.08-.1.16-.29.16-.386v-.03h-.24v.04c-.01.06-.05.142-.09.2a.48.48 0 0 1-.5.159c-.04-.01-.07-.01-.07-.01l-.06.103-.05.09.03.02c.09.05.33.08.434.05zm-.467-.207l.06-.1-.06-.06c-.03-.03-.08-.09-.1-.135-.03-.06-.03-.09-.03-.188s0-.125.03-.185c.02-.04.06-.1.09-.136l.06-.07-.06-.1c-.03-.05-.06-.1-.07-.1s-.1.07-.144.123c-.05.06-.117.17-.135.234-.01.05-.01.06.01.08.08.08.07.212 0 .302-.02.03-.01.07.05.185.04.07.19.237.22.237.01 0 .04-.04.07-.1zm-.424-.334c.09-.04.12-.174.05-.252s-.18-.09-.253 0c-.03.03-.04.05-.04.1 0 .13.12.21.237.154zm1.448-.214c0-.1-.06-.26-.148-.37l-.05-.06h-.09c-.08 0-.09 0-.137-.04-.03-.02-.06-.05-.07-.09-.03-.07-.04-.07-.218-.07-.108 0-.15 0-.215.03-.04.02-.09.03-.09.04a1.45 1.45 0 0 0 .109.207l.08-.02a.46.46 0 0 1 .439.114.54.54 0 0 1 .135.246v.05h.12c.117 0 .12 0 .12-.03zm-.18-.526c.106-.1.05-.267-.1-.28-.133-.01-.22.113-.164.234.02.05.04.06.1.08s.12 0 .164-.04z" fill="#ff7f2a"/></g><text xml:space="preserve" x="-618.107" y="-109.781" font-size="1.839" font-family="PT Sans Narrow" letter-spacing="0" word-spacing="0" writing-mode="lr-tb" direction="ltr" dominant-baseline="auto" baseline-shift="baseline"><tspan x="1.362" y="2.509" font-weight="600" font-family="A-OTF Shin Go Pro">x</tspan></text></svg> \ No newline at end of file diff --git a/assets/linux.svg b/assets/linux.svg new file mode 100644 index 0000000..ce7d516 --- /dev/null +++ b/assets/linux.svg @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" id="svg2951" version="1.1" viewBox="0 0 3.88606 3.88606" height="3.88606mm" width="3.88606mm"> +<defs id="defs2945" /> +<metadata id="metadata2948"> +<rdf:RDF> +<cc:Work rdf:about=""> +<dc:format>image/svg+xml</dc:format> +<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> +<dc:title></dc:title> +</cc:Work> +</rdf:RDF> +</metadata> +<g transform="translate(319.929,37.1042)" id="layer1"> +<path id="path2788" fill="#fff" d="m -318.033,-36.0432 c -0.542,0.03 -0.631,0.671 -0.631,0.671 l -0.193,0.365 0.14,0.836 0.189,0.236 0.377,0.212 0.601,-0.02 0.273,-0.125 0.127,-0.166 0.09,-1.051 -0.418,-0.596 z" style="fill:#ffffff;stroke:#000000;stroke-width:0.013903px" /> +<path d="m -318.204,-36.7692 c 0.212,-0.03 0.415,0.111 0.602,0.22 0.292,0.248 0.296,0.255 0.415,0.601 0.13,0.559 0.417,1.519 0.06,1.933 -0.07,0.16 -0.425,0.268 -0.658,0.274 0.764,-0.399 0.723,-1.155 0.55,-1.46 -0.08,0.207 -0.257,0.632 -0.336,0.839 -0.09,0.08 -0.307,0.334 -0.276,0.05 -0.01,-0.265 0.113,-0.512 0.152,-0.771 0.06,-0.228 0.145,-0.434 0.05,-0.603 -0.1,-0.15 -0.203,-0.223 -0.498,-0.188 -0.293,0.04 -0.416,0.296 -0.383,0.5 -0.04,0.355 0.133,0.699 0.191,1.046 0.02,0.125 0.07,0.435 -0.09,0.204 -0.177,-0.178 -0.291,-0.389 -0.352,-0.632 -0.05,-0.1 -0.05,-0.454 -0.05,-0.152 0,0.268 0.122,0.566 0.198,0.819 0.06,0.126 0.242,0.269 0.533,0.341 -0.385,0.01 -0.706,-0.37 -0.762,-0.649 -0.07,-0.349 -0.1,-0.755 -0.04,-1.057 0.07,-0.275 0.242,-0.524 0.462,-0.672 0.06,-0.04 0.04,-0.06 0.02,-0.09 -0.09,-0.198 -0.453,-0.08 -0.58,-0.226 0.224,-0.145 0.598,-0.298 0.791,-0.326 z" style="fill:#000000;stroke:#000000;stroke-width:0.004589px" id="path2697" /> +<circle r="0.0781378" cy="-36.5582" cx="-318.234" id="path2702" style="fill:#ffffff;stroke-width:0.027147;stroke-linecap:round" /> +<path d="m -318.518,-33.8072 c 0.06,0.02 0.113,0.05 0.17,0.07 -0.05,0.05 -0.211,0.05 -0.26,0.09 0.05,-0.01 0.203,0.03 0.249,0.02 0.03,0.04 -0.08,0.08 -0.03,0.108 0.05,0 0.199,-0.02 0.252,0 0.04,0 0.07,0.114 0.104,0.04 0.02,-0.07 0.05,-0.147 0.07,-0.22 0.08,0 0.16,0 0.24,-0.01 0.05,0.08 0.11,0.151 0.166,0.226 0.02,-0.03 0,-0.118 0.06,-0.112 0.06,0.01 0.126,0.03 0.189,0.04 -0.01,-0.05 -0.02,-0.09 -0.06,-0.165 0.1,0.04 0.164,0.08 0.348,0.06 -0.08,-0.07 -0.218,-0.09 -0.299,-0.165 0.04,-0.03 0.07,-0.06 0.111,-0.1 -0.07,0.04 -0.138,0.08 -0.212,0.109 -0.166,0.03 -0.33,0.06 -0.499,0.07 -0.09,0 -0.187,0.02 -0.278,0 -0.106,-0.02 -0.213,-0.04 -0.319,-0.06 z" style="fill:#000000;stroke:#000000;stroke-width:0.004589px" id="path2704" /> +</g> +</svg> diff --git a/assets/netbsd-OSX-catalina.svg b/assets/netbsd-OSX-catalina.svg new file mode 100644 index 0000000..3f5276c --- /dev/null +++ b/assets/netbsd-OSX-catalina.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle cx="1.908" cy="1.908" r="1.908" opacity=".12" fill="#f9f9f9"/><path d="M1.332 3.303c-.08-.02-.166-.08-.255-.174-.04-.05-.145-.19-.165-.23 0-.02 0-.04-.04-.04 0-.02-.103-.2-.125-.262l-.124-.408v-.402c0-.137 0-.134.04-.22a.9.9 0 0 1 .208-.322c.103-.1.213-.16.364-.2a.39.39 0 0 1 .214 0c.04 0 .04.02.103.03.04 0 .125.04.166.06s.124.04.145.04c.04 0 .08-.02.207-.06.04-.02.104-.04.125-.04.124-.04.124-.04.23-.04h.124c.186.04.325.116.435.25 0 .02.04.04 0 .04-.04.05-.124.12-.145.167-.07.07-.104.157-.145.265v.34c0 .03.07.116.07.157.07.104.166.2.276.25.04.02.04.02.04.03v.03c0 .03-.04.13-.08.197-.103.207-.277.433-.39.503-.125.08-.224.07-.43 0l-.145-.05c-.104-.02-.213-.02-.33.03-.187.07-.264.09-.338.07zM1.88.986h-.04v-.02c0-.05 0-.172.04-.25.04-.112.145-.25.253-.31.08-.06.186-.1.265-.107h.04v.25c0 .02-.04.08-.04.103 0 .04-.07.12-.125.163-.08.08-.145.112-.22.143-.04.02-.08.02-.145.02z"/></svg> diff --git a/assets/netbsd-iOS.svg b/assets/netbsd-iOS.svg new file mode 100644 index 0000000..2e283dd --- /dev/null +++ b/assets/netbsd-iOS.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle r="1.908" cy="1.908" cx="1.908" opacity=".42" fill="#fff"/><path d="M1.442 2.76c-.17-.03-.31-.12-.405-.262a1.15 1.15 0 0 1 0-1.132c.1-.148.257-.25.432-.277.07-.01.206 0 .268.01.225.06.39.24.457.502a1.47 1.47 0 0 1-.02.693.65.65 0 0 1-.517.463c-.07.01-.154.01-.215 0zm.2-.05a.58.58 0 0 0 .4-.259c.153-.225.19-.59.09-.892-.05-.18-.17-.316-.314-.382-.08-.04-.128-.05-.245-.05-.09 0-.116 0-.16.01-.25.07-.412.267-.47.583a1.35 1.35 0 0 0 0 .401c.06.335.24.54.507.58a.58.58 0 0 0 .186 0zm1.016.05c-.03-.01-.08-.01-.107-.02-.05-.02-.154-.06-.154-.07v-.03c0-.01 0-.02.02-.01 0 .01.1.05.145.07a.55.55 0 0 0 .335.01c.134-.04.24-.155.268-.28v-.09c0-.176-.09-.295-.303-.396l-.15-.07c-.21-.1-.294-.205-.294-.37 0-.112.03-.2.117-.28.04-.04.06-.06.104-.08.08-.04.125-.05.23-.05s.173.01.244.05l.04.02v.04l-.02-.01c-.09-.05-.28-.06-.38-.03-.127.04-.218.13-.256.258v.185c.04.105.1.158.332.272l.188.102c.07.05.14.115.166.166.07.135.05.314-.02.44-.07.11-.194.18-.348.196a.39.39 0 0 1-.152 0zM.59 2.16V1.58h.04v1.164H.59zm0-.91c-.03-.03 0-.08.03-.08s.04 0 .04.03c.02.05-.04.09-.07.06z" fill="#333"/></svg> \ No newline at end of file diff --git a/assets/passed.svg b/assets/passed.svg new file mode 100644 index 0000000..75ad325 --- /dev/null +++ b/assets/passed.svg @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="5.1275mm" height="5.1275mm" version="1.1" viewBox="0 0 5.1275 5.1275" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> +<metadata> +<rdf:RDF> +<cc:Work rdf:about=""> +<dc:format>image/svg+xml</dc:format> +<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> +<dc:title/> +</cc:Work> +</rdf:RDF> +</metadata> +<g transform="translate(-94.198 -73.032)"> +<circle cx="96.762" cy="75.595" r="2.5187" fill="#0a0" stroke="#000" stroke-linecap="round" stroke-width=".090153"/> +<path d="m95.484 75.123 2.464-0.809 0.493 0.812-2.01 2.209-1.352-1.855z" fill="#fff" stroke-width=".018022px"/> +<path d="m96.556 73.682c-0.377 0.06-0.718 0.185-1.016 0.439-0.192 0.165-0.354 0.366-0.468 0.591-0.56 1.111 0.09 2.495 1.319 2.75 0.257 0.05 0.521 0.05 0.778 0 0.264-0.05 0.512-0.166 0.732-0.321 0.162-0.116 0.304-0.256 0.422-0.414 0.665-0.89 0.403-2.179-0.525-2.771-0.186-0.116-0.401-0.206-0.617-0.247-0.2-0.04-0.423-0.06-0.625-0.03m-0.08 2.312c0.184-0.2059 0.379-0.401 0.564-0.607 0.104-0.104 0.201-0.226 0.308-0.328 0.104-0.104 0.207-0.279 0.344-0.343 0.163-0.08 0.328 0.06 0.307 0.232-0.01 0.147-0.218 0.283-0.311 0.387-0.258 0.286-0.53 0.558-0.787 0.845-0.103 0.1161-0.231 0.302-0.39 0.325-0.161 0.03-0.305-0.1699-0.411-0.266l-0.303-0.273c-0.06-0.06-0.135-0.117-0.135-0.218 1e-3 -0.151 0.152-0.256 0.293-0.203 0.08 0.03 0.145 0.103 0.207 0.158 0.104 0.09 0.228 0.183 0.314 0.291z" fill="#47e91b" stroke-width=".009018"/> +</g> +</svg> + diff --git a/assets/post-register-fail.html b/assets/post-register-fail.html new file mode 100644 index 0000000..063c3c5 --- /dev/null +++ b/assets/post-register-fail.html @@ -0,0 +1 @@ +Registration failed, sorry diff --git a/assets/post-register-ok.html b/assets/post-register-ok.html new file mode 100644 index 0000000..f5d70b8 --- /dev/null +++ b/assets/post-register-ok.html @@ -0,0 +1,22 @@ +<html> + <body> + <table> + <tr> + <td colspan=2 align=center> + <img src="sai.svg"> + </td> + </tr> + <tr> + <td> + Your registration is accepted:<br> + you will receive an email shortly with instructions<br> + to verify and enable the account for normal use.<br><br> + + The link is only valid for an hour, after that if it has<br> + not been verified your account will be deleted. + </td> + </tr> + </table> + </body> +</html> + diff --git a/assets/rebuild.png b/assets/rebuild.png new file mode 100644 index 0000000..7c5df4a Binary files /dev/null and b/assets/rebuild.png differ diff --git a/assets/sai-event.svg b/assets/sai-event.svg new file mode 100644 index 0000000..1dccc6c --- /dev/null +++ b/assets/sai-event.svg @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="8.7702mm" height="8.7702mm" version="1.1" viewBox="0 0 8.7702 8.7702" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> +<metadata> +<rdf:RDF> +<cc:Work rdf:about=""> +<dc:format>image/svg+xml</dc:format> +<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> +<dc:title/> +</cc:Work> +</rdf:RDF> +</metadata> +<g transform="translate(-47.776 -104.47)"> +<g transform="matrix(1.1547 0 0 1.1547 790.48 256.26)"> +<circle cx="-639.4" cy="-127.65" r="3.7976" fill="none" stroke-linecap="round" stroke-width=".0926"/> +<g transform="matrix(.22689 0 0 .22689 -537.47 -158.38)" fill="#fff" opacity=".10965" style="text-decoration-color:#000000" aria-label="裁"> +<path d="m-449.06 137.17 0.72 0.733q-0.36 0.36-0.626 0.6-0.267 0.24-0.52 0.427 0.2 0.186 0.413 0.36 0.227 0.16 0.467 0.346l-1.094 0.934q-0.213-0.2-0.413-0.4t-0.387-0.387l0.347 1.12q-1.04 0.36-1.987 0.64-0.946 0.28-1.973 0.587l-0.427-1.374q0.28-0.07 0.534-0.12 0.266-0.05 0.533-0.12v-0.493q0-0.133-0.01-0.267 0-0.133 0.01-0.266-0.187 0.16-0.387 0.32t-0.413 0.306l-0.8-1.226q0.733-0.467 1.173-0.867 0.454-0.4 0.787-0.853h-1.467v-1.374h2.187v-0.733h-2.213v-1.4h2.306v-0.84h-1.92v-1.347h1.92v-1.306h1.48v1.306h1.8v1.347h-1.8v0.84h2.454q-0.03-0.96-0.04-1.84 0-0.893-0.01-1.653h1.334q0 0.893 0.01 1.773t0.05 1.72h3.147v1.4h-3.054q0.05 0.64 0.12 1.227 0.08 0.586 0.187 1.146 0.307-0.533 0.56-1.093 0.253-0.573 0.48-1.2l1.053 0.733q-0.346 0.894-0.76 1.694-0.4 0.8-0.893 1.52 0.05 0.146 0.09 0.28 0.05 0.12 0.12 0.24 0.05 0.133 0.134 0.28 0.08 0.146 0.16 0.266 0.09 0.12 0.173 0.2 0.09 0.08 0.173 0.08 0.07 0 0.12-0.12t0.08-0.306q0.04-0.214 0.07-0.48 0.04-0.28 0.08-0.614l1.213 0.947q-0.04 0.4-0.16 0.8-0.106 0.413-0.28 0.733-0.173 0.334-0.413 0.547t-0.547 0.213q-0.493 0-0.893-0.28-0.387-0.28-0.707-0.733-0.24-0.32-0.453-0.76-1.067 1.12-2.493 1.907l-0.68-1.16q0.826-0.467 1.48-0.987 0.666-0.533 1.2-1.16-0.24-0.88-0.374-1.88-0.133-1-0.2-2.04h-2.64v0.733h2.467v1.374zm3.494-3.627q-0.147-0.333-0.267-0.6t-0.267-0.52q-0.133-0.267-0.306-0.533-0.16-0.267-0.4-0.614l1.12-0.626q0.266 0.346 0.44 0.613 0.186 0.267 0.32 0.52 0.146 0.253 0.253 0.507 0.12 0.253 0.253 0.573zm-5.014 6.147q-0.813-0.854-1.453-1.694-0.08 0.107-0.12 0.147v2.027q0.733-0.187 1.573-0.48zm0.254-1.6q0.213-0.174 0.413-0.387t0.467-0.533h-1.614q0.187 0.253 0.36 0.48 0.187 0.213 0.374 0.44z"/> +</g> +<path d="m-636.97-127.11 0.03-2.054" fill="none" stroke="#000" stroke-width=".049417px"/> +<g transform="matrix(.26144 0 0 .26144 -527.8 -72.385)"> +<path d="m-419.28-214.75 4.452-0.01 0.825 0.796-0.248 1.336-4.681 2.699-1.375-2.519z" fill="#fff" stroke-width=".26458px"/> +<path d="m-416.9-216.61c-0.819-0.06-1.587-0.01-2.343 0.346-0.489 0.233-0.935 0.559-1.296 0.963-1.774 1.986-1.186 5.211 1.206 6.416 0.5 0.252 1.048 0.394 1.605 0.432 0.576 0.04 1.153-0.06 1.693-0.257 0.396-0.144 0.77-0.362 1.101-0.623 1.865-1.47 2.04-4.282 0.45-6.019-0.319-0.348-0.716-0.646-1.14-0.854-0.391-0.192-0.838-0.369-1.276-0.404m-1.442 4.737c0.496-0.322 1.007-0.621 1.503-0.943 0.267-0.174 0.54-0.355 0.819-0.509s0.58-0.46 0.899-0.518c0.381-0.07 0.644 0.309 0.506 0.651-0.118 0.293-0.606 0.464-0.857 0.627-0.691 0.448-1.403 0.862-2.094 1.311-0.27 0.175-0.643 0.496-0.985 0.458-0.348-0.04-0.536-0.52-0.703-0.778l-0.476-0.733c-0.103-0.159-0.212-0.327-0.157-0.526 0.09-0.311 0.455-0.447 0.719-0.258 0.149 0.107 0.242 0.293 0.339 0.443 0.162 0.249 0.37 0.502 0.487 0.775z" fill="#47e91b" stroke-width=".019302"/> +</g> +<path d="m-641.87-127.11 0.03-2.054" fill="none" stroke="#000" stroke-width=".049417px"/> +<g transform="matrix(.24542 -.026228 .026228 .24542 -515.9 -89.323)"> +<path d="m-493.6-213.01-0.576 2.198 3.534 4.241 2.171-0.121 1.067-4.722z" fill="#fff" stroke-width=".26458px"/> +<g transform="rotate(-31.008 -469.83 -165.36)"> +<path d="m-464.42-215.27v1.78c0 0.28-0.04 0.599 0 0.875 0.02 0.118 0.154 0.182 0.227 0.268 0.133 0.158 0.219 0.377 0.182 0.584-0.136 0.776-1.235 0.904-1.492 0.133-0.09-0.261-0.02-0.536 0.162-0.741 0.106-0.122 0.271-0.169 0.279-0.348 0.03-0.574 0-1.154 0-1.727 0-0.255 0.06-0.601 0-0.846-0.06-0.212-0.367-0.312-0.442-0.563-0.05-0.171 0.05-0.381 0-0.531-0.114-0.302-0.59-0.622-0.818-0.85-0.07-0.07-0.183-0.234-0.292-0.234-0.09 0-0.18 0.121-0.239 0.181l-0.558 0.557-1.727 1.728c-0.239 0.238-0.612 0.499-0.679 0.85-0.101 0.534 0.535 0.946 0.865 1.275l3.029 3.029c0.361 0.361 0.814 1.066 1.408 0.826 0.263-0.107 0.468-0.39 0.664-0.586l3.455-3.455c0.262-0.262 0.694-0.562 0.659-0.983-0.04-0.513-0.719-0.963-1.058-1.302l-2.577-2.577c-0.329-0.329-0.731-0.915-1.169-1.086-0.53-0.208-0.893 0.304-1.223 0.634-0.102 0.102-0.327 0.255-0.362 0.399-0.04 0.138 0.25 0.339 0.336 0.425 0.216 0.216 0.468 0.582 0.744 0.716 0.178 0.09 0.41-0.02 0.611 0.08 0.238 0.12 0.415 0.372 0.443 0.638 0.02 0.145-0.06 0.271 0.04 0.398 0.192 0.24 0.442 0.448 0.659 0.665 0.104 0.103 0.214 0.255 0.346 0.324 0.129 0.07 0.327-0.02 0.478 0.03 0.449 0.14 0.725 0.653 0.491 1.083-0.25 0.461-0.862 0.571-1.232 0.185-0.139-0.144-0.215-0.331-0.215-0.53 0-0.09 0.05-0.207 0.02-0.292-0.04-0.124-0.194-0.23-0.283-0.319-0.23-0.23-0.452-0.504-0.718-0.691" fill="#ef5034" stroke-width=".026571"/> +</g> +</g> +<circle cx="-639.38" cy="-129.79" r=".26513" stroke-linecap="round" stroke-width=".15424"/> +<rect x="-639.65" y="-129.79" width=".52708" height="4.585" stroke-linecap="round" stroke-width=".31679"/> +<rect transform="rotate(90)" x="-125.28" y="637.92" width=".52708" height="3.0111" stroke-linecap="round" stroke-width=".25672"/> +<path d="m-639.55-129.41c-0.693-0.701-1.526-0.693-2.19-0.07-0.128 0.08-0.329 0.03-0.378-0.119" fill="none" stroke="#000" stroke-linecap="round" stroke-width=".18683"/> +<path d="m-639.28-129.4c0.694-0.701 1.527-0.694 2.19-0.07 0.128 0.08 0.33 0.03 0.379-0.119" fill="none" stroke="#000" stroke-linecap="round" stroke-width=".18677"/> +<path d="m-642.56-127.11 0.667-2.107" stroke="#000" stroke-width=".049417px"/> +<ellipse cx="-641.84" cy="-129.36" rx=".097435" ry=".19487" fill="none" stroke="#000" stroke-linecap="round" stroke-width=".056032"/> +<ellipse cx="-636.94" cy="-129.36" rx=".097435" ry=".19487" fill="none" stroke="#000" stroke-linecap="round" stroke-width=".056032"/> +<path d="m-641.11-127.09-0.666-2.107" fill="none" stroke="#000" stroke-width=".049417px"/> +<path d="m-637.67-127.11 0.667-2.107" fill="none" stroke="#000" stroke-width=".049417px"/> +<path d="m-636.22-127.12-0.666-2.107" fill="none" stroke="#000" stroke-width=".049417px"/> +<path d="m-642.7-127.08s0.03 0.118 0.06 0.17c0.02 0.03 0.101 0.08 0.101 0.08h1.441s0.07-0.05 0.09-0.08c0.03-0.05 0.04-0.167 0.04-0.167l0.08-0.02v-0.02h-1.89v0.02z" stroke-width=".043251px"/> +<path d="m-637.81-127.11s0.03 0.119 0.06 0.17c0.02 0.03 0.1 0.08 0.1 0.08h1.441s0.07-0.05 0.09-0.08c0.03-0.05 0.04-0.166 0.04-0.166l0.08-0.02v-0.02h-1.89v0.02z" stroke-width=".043251px"/> +<circle cx="-640.91" cy="-125.02" r=".26513" stroke-linecap="round" stroke-width=".15424"/> +<circle cx="-637.92" cy="-125.01" r=".26513" stroke-linecap="round" stroke-width=".15424"/> +</g> +</g> +</svg> + diff --git a/assets/sai-icon.svg b/assets/sai-icon.svg new file mode 100644 index 0000000..de7980c --- /dev/null +++ b/assets/sai-icon.svg @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="24.606mm" height="24.606mm" version="1.1" viewBox="0 0 24.606 24.606" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> +<metadata> +<rdf:RDF> +<cc:Work rdf:about=""> +<dc:format>image/svg+xml</dc:format> +<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> +<dc:title/> +</cc:Work> +</rdf:RDF> +</metadata> +<g transform="translate(204.63 -25.412)"> +<path d="m-180.03 37.715a12.303 12.303 0 0 1-12.303 12.303 12.303 12.303 0 0 1-12.303-12.303 12.303 12.303 0 0 1 12.303-12.303 12.303 12.303 0 0 1 12.303 12.303" fill="none" stroke-linecap="round" stroke-width=".3"/> +<g transform="matrix(.73506 0 0 .73506 137.91 -61.859)" fill="#fff" opacity=".10965" style="text-decoration-color:#000000" aria-label="裁"> +<path d="m-449.06 137.17 0.72 0.733q-0.36 0.36-0.626 0.6-0.267 0.24-0.52 0.427 0.2 0.186 0.413 0.36 0.227 0.16 0.467 0.346l-1.094 0.934q-0.213-0.2-0.413-0.4t-0.387-0.387l0.347 1.12q-1.04 0.36-1.987 0.64-0.946 0.28-1.973 0.587l-0.427-1.374q0.28-0.07 0.534-0.12 0.266-0.05 0.533-0.12v-0.493q0-0.133-0.01-0.267 0-0.133 0.01-0.266-0.187 0.16-0.387 0.32t-0.413 0.306l-0.8-1.226q0.733-0.467 1.173-0.867 0.454-0.4 0.787-0.853h-1.467v-1.374h2.187v-0.733h-2.213v-1.4h2.306v-0.84h-1.92v-1.347h1.92v-1.306h1.48v1.306h1.8v1.347h-1.8v0.84h2.454q-0.03-0.96-0.04-1.84 0-0.893-0.01-1.653h1.334q0 0.893 0.01 1.773t0.05 1.72h3.147v1.4h-3.054q0.05 0.64 0.12 1.227 0.08 0.586 0.187 1.146 0.307-0.533 0.56-1.093 0.253-0.573 0.48-1.2l1.053 0.733q-0.346 0.894-0.76 1.694-0.4 0.8-0.893 1.52 0.05 0.146 0.09 0.28 0.05 0.12 0.12 0.24 0.05 0.133 0.134 0.28 0.08 0.146 0.16 0.266 0.09 0.12 0.173 0.2 0.09 0.08 0.173 0.08 0.07 0 0.12-0.12t0.08-0.306q0.04-0.214 0.07-0.48 0.04-0.28 0.08-0.614l1.213 0.947q-0.04 0.4-0.16 0.8-0.106 0.413-0.28 0.733-0.173 0.334-0.413 0.547t-0.547 0.213q-0.493 0-0.893-0.28-0.387-0.28-0.707-0.733-0.24-0.32-0.453-0.76-1.067 1.12-2.493 1.907l-0.68-1.16q0.826-0.467 1.48-0.987 0.666-0.533 1.2-1.16-0.24-0.88-0.374-1.88-0.133-1-0.2-2.04h-2.64v0.733h2.467v1.374zm3.494-3.627q-0.147-0.333-0.267-0.6t-0.267-0.52q-0.133-0.267-0.306-0.533-0.16-0.267-0.4-0.614l1.12-0.626q0.266 0.346 0.44 0.613 0.186 0.267 0.32 0.52 0.146 0.253 0.253 0.507 0.12 0.253 0.253 0.573zm-5.014 6.147q-0.813-0.854-1.453-1.694-0.08 0.107-0.12 0.147v2.027q0.733-0.187 1.573-0.48zm0.254-1.6q0.213-0.174 0.413-0.387t0.467-0.533h-1.614q0.187 0.253 0.36 0.48 0.187 0.213 0.374 0.44z"/> +</g> +<path d="m-184.46 39.465 0.09-6.653" fill="none" stroke="#000" stroke-width=".1601px"/> +<g transform="matrix(.847 0 0 .847 169.21 216.75)"> +<path d="m-419.28-214.75 4.452-0.01 0.825 0.796-0.248 1.336-4.681 2.699-1.375-2.519z" fill="#fff" stroke-width=".26458px"/> +<path d="m-416.9-216.61c-0.819-0.06-1.587-0.01-2.343 0.346-0.489 0.233-0.935 0.559-1.296 0.963-1.774 1.986-1.186 5.211 1.206 6.416 0.5 0.252 1.048 0.394 1.605 0.432 0.576 0.04 1.153-0.06 1.693-0.257 0.396-0.144 0.77-0.362 1.101-0.623 1.865-1.47 2.04-4.282 0.45-6.019-0.319-0.348-0.716-0.646-1.14-0.854-0.391-0.192-0.838-0.369-1.276-0.404m-1.442 4.737c0.496-0.322 1.007-0.621 1.503-0.943 0.267-0.174 0.54-0.355 0.819-0.509s0.58-0.46 0.899-0.518c0.381-0.07 0.644 0.309 0.506 0.651-0.118 0.293-0.606 0.464-0.857 0.627-0.691 0.448-1.403 0.862-2.094 1.311-0.27 0.175-0.643 0.496-0.985 0.458-0.348-0.04-0.536-0.52-0.703-0.778l-0.476-0.733c-0.103-0.159-0.212-0.327-0.157-0.526 0.09-0.311 0.455-0.447 0.719-0.258 0.149 0.107 0.242 0.293 0.339 0.443 0.162 0.249 0.37 0.502 0.487 0.775z" fill="#47e91b" stroke-width=".019302"/> +</g> +<path d="m-200.32 39.462 0.09-6.654" fill="none" stroke="#000" stroke-width=".1601px"/> +<g transform="matrix(.79511 -.084971 .084971 .79511 207.8 161.88)"> +<path d="m-493.6-213.01-0.576 2.198 3.534 4.241 2.171-0.121 1.067-4.722z" fill="#fff" stroke-width=".26458px"/> +<g transform="rotate(-31.008 -469.83 -165.36)"> +<path d="m-464.42-215.27v1.78c0 0.28-0.04 0.599 0 0.875 0.02 0.118 0.154 0.182 0.227 0.268 0.133 0.158 0.219 0.377 0.182 0.584-0.136 0.776-1.235 0.904-1.492 0.133-0.09-0.261-0.02-0.536 0.162-0.741 0.106-0.122 0.271-0.169 0.279-0.348 0.03-0.574 0-1.154 0-1.727 0-0.255 0.06-0.601 0-0.846-0.06-0.212-0.367-0.312-0.442-0.563-0.05-0.171 0.05-0.381 0-0.531-0.114-0.302-0.59-0.622-0.818-0.85-0.07-0.07-0.183-0.234-0.292-0.234-0.09 0-0.18 0.121-0.239 0.181l-0.558 0.557-1.727 1.728c-0.239 0.238-0.612 0.499-0.679 0.85-0.101 0.534 0.535 0.946 0.865 1.275l3.029 3.029c0.361 0.361 0.814 1.066 1.408 0.826 0.263-0.107 0.468-0.39 0.664-0.586l3.455-3.455c0.262-0.262 0.694-0.562 0.659-0.983-0.04-0.513-0.719-0.963-1.058-1.302l-2.577-2.577c-0.329-0.329-0.731-0.915-1.169-1.086-0.53-0.208-0.893 0.304-1.223 0.634-0.102 0.102-0.327 0.255-0.362 0.399-0.04 0.138 0.25 0.339 0.336 0.425 0.216 0.216 0.468 0.582 0.744 0.716 0.178 0.09 0.41-0.02 0.611 0.08 0.238 0.12 0.415 0.372 0.443 0.638 0.02 0.145-0.06 0.271 0.04 0.398 0.192 0.24 0.442 0.448 0.659 0.665 0.104 0.103 0.214 0.255 0.346 0.324 0.129 0.07 0.327-0.02 0.478 0.03 0.449 0.14 0.725 0.653 0.491 1.083-0.25 0.461-0.862 0.571-1.232 0.185-0.139-0.144-0.215-0.331-0.215-0.53 0-0.09 0.05-0.207 0.02-0.292-0.04-0.124-0.194-0.23-0.283-0.319-0.23-0.23-0.452-0.504-0.718-0.691" fill="#ef5034" stroke-width=".026571"/> +</g> +</g> +<path d="m-191.42 30.775a0.85894 0.85894 0 0 1-0.859 0.8589 0.85894 0.85894 0 0 1-0.859-0.8589 0.85894 0.85894 0 0 1 0.859-0.8589 0.85894 0.85894 0 0 1 0.859 0.8589" stroke-linecap="round" stroke-width=".49969"/> +<path d="m-193.13 30.78h1.708v14.854h-1.708z" stroke-linecap="round" stroke-width="1.0263"/> +<path transform="rotate(90)" d="m45.398 187.53h1.7076v9.755h-1.7076z" stroke-linecap="round" stroke-width=".83169"/> +<path d="m-192.81 32.011c-2.247-2.269-4.946-2.245-7.095-0.235-0.415 0.248-1.066 0.106-1.226-0.386" fill="none" stroke="#000" stroke-linecap="round" stroke-width=".60529"/> +<path d="m-191.94 32.023c2.247-2.269 4.946-2.246 7.095-0.236 0.414 0.249 1.066 0.106 1.226-0.385" fill="none" stroke="#000" stroke-linecap="round" stroke-width=".60509"/> +<path d="m-202.56 39.453 2.159-6.827" stroke="#000" stroke-width=".1601px"/> +<path d="m-199.91 32.158a0.31566 0.63133 0 0 1-0.316 0.6313 0.31566 0.63133 0 0 1-0.316-0.6313 0.31566 0.63133 0 0 1 0.316-0.6313 0.31566 0.63133 0 0 1 0.316 0.6313" fill="none" stroke="#000" stroke-linecap="round" stroke-width=".18153"/> +<path d="m-184.04 32.162a0.31566 0.63133 0 0 1-0.316 0.6313 0.31566 0.63133 0 0 1-0.316-0.6313 0.31566 0.63133 0 0 1 0.316-0.6313 0.31566 0.63133 0 0 1 0.316 0.6313" fill="none" stroke="#000" stroke-linecap="round" stroke-width=".18153"/> +<path d="m-197.87 39.512-2.158-6.827" fill="none" stroke="#000" stroke-width=".1601px"/> +<path d="m-186.72 39.451 2.159-6.826" fill="none" stroke="#000" stroke-width=".1601px"/> +<path d="m-182 39.444-2.158-6.826" fill="none" stroke="#000" stroke-width=".1601px"/> +<path d="m-203.01 39.565s0.09 0.385 0.198 0.551c0.07 0.107 0.325 0.269 0.325 0.269h4.669s0.231-0.152 0.302-0.266c0.1-0.158 0.141-0.538 0.141-0.538l0.244-0.07v-0.06h-6.122v0.06z" stroke-width=".14012px"/> +<path d="m-187.17 39.466s0.09 0.386 0.198 0.552c0.07 0.107 0.325 0.269 0.325 0.269h4.668s0.232-0.152 0.302-0.266c0.1-0.158 0.141-0.538 0.141-0.538l0.245-0.07v-0.06h-6.123v0.06z" stroke-width=".14012px"/> +<path d="m-196.36 46.246a0.85894 0.85894 0 0 1-0.859 0.8589 0.85894 0.85894 0 0 1-0.859-0.8589 0.85894 0.85894 0 0 1 0.859-0.8589 0.85894 0.85894 0 0 1 0.859 0.8589" stroke-linecap="round" stroke-width=".49969"/> +<path d="m-186.67 46.252a0.85894 0.85894 0 0 1-0.859 0.8589 0.85894 0.85894 0 0 1-0.859-0.8589 0.85894 0.85894 0 0 1 0.859-0.8589 0.85894 0.85894 0 0 1 0.859 0.8589" stroke-linecap="round" stroke-width=".49969"/> +</g> +</svg> diff --git a/assets/sai.css b/assets/sai.css index c0be30b..f505808 100644 --- a/assets/sai.css +++ b/assets/sai.css @@ -1,122 +1,50 @@ -span.title { - font-size:18pt; - font-family: Arial; - font-weight:normal; - text-align:center; - color:#000000; -} -span.mount { - font-size:10pt; - font-family: Arial; - font-weight:normal; - text-align:center; - color:#000000; -} -span.mountname { - font-size:14pt; - font-family: Arial; - font-weight:bold; - text-align:center; - color:#404010; +@font-face { + font-family: 'OpenSans'; + src: url('OpenSans-Light.ttf') format('truetype'); } -span.n { - font-size:12pt; - font-family: Arial; - font-weight:normal; - text-align:center; - color:#808020; -} -span.sn { - font-size:12pt; - font-family: Arial; - font-weight:bold; - text-align:center; - color:#808020; -} -span.v { - font-size:12pt; - font-family: Arial; - font-weight:bold; - text-align:center; - color:#202020; + +@font-face { + font-family: 'Iosevka'; + font-style: normal; + font-weight: 400; + font-display: block; + src: url('/iosevka-term-ss01-regular.ttf') format('truetype'); } -span.m1 { - font-size:12pt; - font-family: Arial; - font-weight:bold; - text-align:center; - color:#202020; + + +body { + font-family: 'OpenSans', Fallback, sans-serif; } -span.m2 { - font-size:12pt; - font-family: Arial; - font-weight:normal; - text-align:center; - color:#202020; + + +.content { vertical-align:top; text-align:center; background:#fffff0; padding:12px; -webkit-border-radius:10px; border-radius:10px; } +.canvas { vertical-align:top; text-align:center; background:#efefd0; padding:12px; -webkit-border-radius:10px; border-radius:10px; } + + +div.zi { + z-index: 1000 } -.browser { font-size:12pt; font-family: Arial; font-weight:normal; text-align:center; color:#ffff00; vertical-align:middle; text-align:center; background:#d0b070; padding:12px; -webkit-border-radius:10px; border-radius:10px;} -.group2 { vertical-align:middle; - text-align:center; - background:#f0f0e0; - padding:12px; - -webkit-border-radius:10px; - border-radius:10px; } -.explain { vertical-align:middle; - text-align:center; - background:#f0f0c0; padding:12px; - -webkit-border-radius:10px; - border-radius:10px; - color:#404000; - padding:3px; +td.al { + text-align: left } -td.wsstatus { vertical-align:middle; width:200px; height:50px; - text-align:center; - background:#f0f0c0; padding:6px; - -webkit-border-radius:8px; - border-radius:8px; - color:#404000; } -.tdform { vertical-align:middle; width:200px; height:50px; - text-align:center; - background:#f0f0d0; padding:6px; - -webkit-border-radius:8px; - margin:10px; - border-radius:8px; - border: 1px solid black; - border-collapse: collapse;font-size:18pt; font-family: Arial; font-weight:normal; text-align:center; color:#000000; - color:#404000; } - -td.l { vertical-align:middle; - text-align:center; - background:#d0d0b0; - padding:3px; - -webkit-border-radius:3px; - border-radius:3px; } - -td.bigger { font-size:120%; } -div.bgw { background:white } -div.conninfo { - border: solid 2px #e0d040; - padding: 4px; - width: 500px; - height:350px; - overflow: auto; +img.tread1 { + margin-left: -6px; + z-index: 10 } -span.f12 { font-size:12pt } - -.content { vertical-align:top; text-align:center; background:#fffff0; padding:12px; -webkit-border-radius:10px; border-radius:10px; } -.canvas { vertical-align:top; text-align:center; background:#efefd0; padding:12px; -webkit-border-radius:10px; border-radius:10px; } -.tabs { - position: relative; - min-height: 750px; /* This part sucks */ - clear: both; - margin: 25px 0; + +img.tread2 { + margin-left: -6px; + z-index: 9 } -.tab { - float: left; + +img.zup { + position: relative; + z-index: 1001 } + .tab label { background: #eee; padding: 10px; @@ -147,70 +75,530 @@ span.f12 { font-size:12pt } z-index: 1; } -td.wsstatus { vertical-align:middle; width:200px; height:50px; - text-align:center; - background:#f0f0c0; padding:6px; - -webkit-border-radius:8px; - border-radius:8px; - color:#404000; } -td.l { vertical-align:middle; - text-align:center; - background:#d0d0b0; - padding:3px; - -webkit-border-radius:3px; - border-radius:3px; } -td.dl { vertical-align:middle; - text-align:center; - background:#c0c0c0; - padding:3px; - -webkit-border-radius:3px; - border-radius:3px; } -td.c { vertical-align:middle; - text-align:center; - background:#c0c0a0; - padding:3px; - -webkit-border-radius:3px; - border-radius:3px; } -td.c0 { vertical-align:middle; - text-align:center; - background:#b0b090; - padding:3px; - -webkit-border-radius:3px; - border-radius:3px; } -td.dc0 { vertical-align:middle; - text-align:center; - background:#a0a0a0; - padding:3px; - -webkit-border-radius:3px; - border-radius:3px; } -td.c1 { vertical-align:middle; +td.waiting { + vertical-align:top; + text-align:left; + color:#000000; + padding:1px; + font-size: 9pt; + font-weight: bold; + border-radius:5px; +} + +table.comp { + border-radius:7px; +} + +table.comp_pass { + background:#01ff70; +} + +table.comp_fail { + background:#ff4136; + color:#fff; +} + +div.ov_good { + background:#2ecc40; +} + +div.ov_bad { + background: #ff4136; + color:#fff; +} + +span.ov_bad { + color:#fff +} + +div.ov_dunno { + background:#d0d0d0; +} + +img.deemph { + opacity: 0.6; +} + +td.tasks { + vertical-align:top; text-align:center; - background:#c0c0c0; - padding:3px; - -webkit-border-radius:3px; - border-radius:3px; } -td.t { vertical-align:middle; + background:#f0f0f0; + color:#000000; + padding:0px; + font-weight: normal; + border-radius:6px; +} + +td.ti { + vertical-align: top; + margin-left: 4px; +} + +td.atop { + vertical-align: top; +} + +td.keepline { + display: inline-flex; +} + +td.jumble { + position: relative; + vertical-align: top; + white-space: nowrap; +} + +span.group2 { + vertical-align:middle; text-align:center; - background:#e0e0c0; - padding:3px; + background:#f0f0f0; + color:#000000; + padding:3px; + font-weight: normal; -webkit-border-radius:3px; border-radius:3px; } +span.stderr { + color: #44f; +} +span.saibuild { + color: #68a; +} +span.tty0 { + color: #884; +} +span.tty1 { + color: #848; +} + + +span.noscript { + text-size:200%; +} + +span.nowrap { + white-space: nowrap; +} + +div.taskinfo { + vertical-align: top; +} + +div.taskstate { + font-weight: normal; + font-size: 7pt; + padding:1px; + margin-right:1px; + border-radius:3px; + white-space: nowrap; +} + +div.nailed { + position:fixed; + background: #fff; + width:100%; + z-index:1001; + margin-top:-12px; +} + +div.undernailed { + position:relative; + top: 180px; +} + + +div.evr { + position: absolute; + top: 16px; + left: 16px; +} + + +.taskstate0 { + color:#000000; + opacity:0.5; +} + +.taskstate1 { + + color:#000000; + opacity:0.6; +} + +img.ip { + width: 16px; + height: 16px; + vertical-align:middle; +} + +img.ip0 { + width: 16px; + height: 16px; + vertical-align:middle; +} + +img.ip1 { + width: 18px; + height: 18px; + vertical-align:top; +} + +img.ip2 { + width: 19px; + height: 19px; + vertical-align:text-bottom; +} + +img.rebuild { + width: 16px; + height: 16px; + vertical-align:middle; + cursor: pointer; +} + +img.bico { + width: 12px; + height: 12px; + vertical-align:middle; +} + +@keyframes stretch { + 0%, 100% { + opacity: 0.8; + } + 0% { + opacity: 0.3; + } +} + +.taskstate1 { + background:#a0a0a0; + color:#000000; + opacity:0.75; + animation-name: stretch; + animation-duration: 2s; + animation-timing-function: ease-out; + animation-delay: 0s; + animation-direction: alternate; + animation-iteration-count: infinite; + animation-fill-mode: none; + animation-play-state: running; +} + +.taskstate2 { + background:#a0a0a0; + color:#000000; + opacity:0.75; + animation-name: stretch; + animation-duration: 1s; + animation-timing-function: ease-out; + animation-delay: 0s; + animation-direction: alternate; + animation-iteration-count: infinite; + animation-fill-mode: none; + animation-play-state: running; +} + +.taskstate3 { + background:#01ff70; + color:#003000; + opacity:1; + /* + animation-name: fadeInOpacity; + animation-iteration-count: 1; + animation-timing-function: ease-in; + animation-duration: 2s; */ +} + +@keyframes fadeInOpacity { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} +} + +.taskstate4 { + background:#ff851b; + color:#300000; + opacity:1; +} + +.taskstate5 { + background:#808080; + color:#202020; + opacity:1; +} + +.taskstate6 { + background:#df3030; + color:#300000; + opacity:1; +} + + +div.dlogs { + vertical-align: top; + font-size: 11px; + font-family: "Iosevka"; + +-webkit-text-size-adjust: none; + -moz-text-size-adjust: none; + -ms-text-size-adjust: none; + text-size-adjust: none; +} +div.dlogsn { + vertical-align: top; + font-size: 11px; + font-family: "Iosevka"; + text-align:right; + width: 5em; + margin-top: 0px; + margin-right: 3px; +-webkit-text-size-adjust: none; + -moz-text-size-adjust: none; + -ms-text-size-adjust: none; + text-size-adjust: none; + +} +div.dlogst { + vertical-align: top; + font-size: 11px; + color:#808080; + font-family: "Iosevka"; + text-align:right; + width: 5em; + margin-top: 0px; + margin-right: 3px; +-webkit-text-size-adjust: none; + -moz-text-size-adjust: none; + -ms-text-size-adjust: none; + text-size-adjust: none; + +} + +div.sai_arts { + display: inline-flex; + vertical-align: middle; + font-size: 7pt; + background-color:#ddd; + color:#3d9970; + text-align:left; + margin: 3px 2px; + padding: 2px 3px; + border-radius:5px; +} + +span.ti1 { + font-weight: normal; + font-size: 9pt; + background:#d0c0b0; + color:#000000; + padding:3px; + border-radius:3px; + white-space: nowrap; +} + +span.ti2 { + font-weight: normal; + font-size: 7pt; +} + +span.ti3 { + font-weight: normal; + font-size: 7pt; +} + +span.ti4 { + font-weight: normal; + font-size: 7pt; +} +span.ti5 { + font-weight: normal; + font-size: 7pt; +} + +span.e1 { + font-weight: bold; + font-size: 11pt; +} + +span.e2 { + font-weight: normal; + font-size: 10pt; +} + +span.e3 { + font-weight: normal; + font-size: 8pt; +} + +td.e4 { + font-weight: normal; + font-size: 8pt; + text-align: right; +} + table.summary { width: 100%; + padding: 1px; + margin: 1px; +} + +table.builders { + height: 100%; + margin-left: 32px; } + +img.bsvg { + height: 50px; + width: auto; + padding:3px; +} + +td.builder { + width: 99%; +} + + td.logo { - margin: 16px; - margin-right: 32px; - padding: 16px; + margin: 3px; + margin-right: 4px; + padding: 2px; + vertical-align: top; + z-index: 1000; } td.summary { background: #f8f8f8; width: 100%; - margin: 16px; - padding: 16px; + margin: 4px; + padding: 4px; +} + +td.bn { + font-weight: normal; + font-size: 7pt; + padding: 1px; +} + +td.bins { + font-weight: normal; + font-size: 9pt; + text-align: center; +} + +td.tn { + font-weight: normal; + font-size: 7pt; + padding-left:0px; +} + +table.nomar { + padding: 0px; + margin: -3px; +} + +td.nomar { + padding: 0px; + margin: 0px; +} + +div.ib { + display: inline-block; + vertical-align:middle; + text-align:left; + line-height: 50%; + float:left; + margin: 0.1em; + font-weight: normal; + font-size: 7pt; + padding: 1.1px 1.5px 1.1px 1.5px; + border-radius:3px; +} + +div.ibuil { + display: inline-block; + vertical-align:middle; + text-align:left; + line-height: 50%; + float:left; + margin: 0.1em; + font-weight: normal; + font-size: 7pt; + background:#c0c0e0; + color:#000000; + padding:3px; + border-radius:10px; +} + +div.ibuilctr { + font-weight: normal; + font-size: 9pt; + background:#e0e0ff; +} + +div.ibuilctrname { + display: inline-block; + vertical-align:middle; + text-align:left; + line-height: 50%; + float:left; + margin: 0.1em; + font-weight: normal; + font-size: 9pt; + color:#000000; + padding:3px; + +} + +div.plat { + display: inline-block; + vertical-align:middle; + text-align:left; + line-height: 50%; + float:left; + margin: 0.1em; + font-weight: normal; + font-size: 7pt; + color:#000000; + padding:3px; + border-radius:3px; +} + +div.pass { + display: inline-block; + background:#40e040; +} + +div.fail { + display: inline-block; + background:#40e040; +} + +div.awaiting { + display: inline-block; +} + +div.bdr { + display: inline-block; + opacity: 1; +} + + + +img.branch { + display: inline-block; + float:left; + background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyNi43NDkgMjcuMjIyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImIiIHg9Ii0uMDcwOCIgeT0iLS4wNzA4IiB3aWR0aD0iMS4xNDE2IiBoZWlnaHQ9IjEuMTQxNiIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMC4yNTQyNzk1MyIvPgo8L2ZpbHRlcj4KPGZpbHRlciBpZD0iYSIgeD0iLS4wOTA1NjgiIHk9Ii0uMDQ2MjE0IiB3aWR0aD0iMS4xODExIiBoZWlnaHQ9IjEuMDkyNCIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMC40Nzk4NDA4OSIvPgo8L2ZpbHRlcj4KPC9kZWZzPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNzAuNzQ3IC02OS40NTQpIj4KPGNpcmNsZSBjeD0iODQuMTIxIiBjeT0iODMuMDIyIiByPSIxMy4zNzQiIGZpbGw9Im5vbmUiLz4KPHBhdGggZD0ibTgzLjc3MyA3MC42MDZjLTEuMTg2MiAwLjAxNTQtMi4zNDE4IDAuODQ2ODgtMi40Nzc0IDEuOTk4OGwtMC4wMTI0IDAuMDQ5MWMtMC4xNDk5IDUuNjExMi0wLjA0NzQgMTEuMjUyLTAuMTExNjIgMTYuODc0IDAuMDU4MSAxLjg3NzctMC40NDMzNyAzLjg0NDEgMC44NjgxNyA1LjIwMTIgMS43NDYyIDEuNzQ0NSA0LjIzMTcgMC40OTM5MyA0LjE0MjQtMi41MTU2IDAuMDQ1NS0wLjkyODQyIDAuMDExNy0xLjQwMiAwLjExMDA3LTMuMjM0OSAxLjUwNTIgMS4xMzQ4IDIuOTYwNCAyLjk5NjYgNC44MTk5IDMuNTIzOCAyLjAzNzEgMC4zMTUyOCAzLjQ3NDgtMi4yMDU1IDIuMzEyLTMuODU1MS0yLjI5OTItMi4yMDI1LTQuODIwMi00LjA1Ny03LjE2Ni02LjIyODYgMC4xMzg5LTMuMyAwLjAxNzYtNi4xNDIgMC4wNDY1LTkuNjA2Ny0wLjQxNDMtMS41OTgtMS40ODQ5LTIuMjE5Ni0yLjUzMTYtMi4yMDYxem0wLjE4NDQ5IDExLjg4M2EyLjA3ODkgMi4wNzg5IDAgMCAxIDIuMDc4OSAyLjA3ODkgMi4wNzg5IDIuMDc4OSAwIDAgMS0yLjA3ODkgMi4wNzkgMi4wNzg5IDIuMDc4OSAwIDAgMS0yLjA3ODQtMi4wNzkgMi4wNzg5IDIuMDc4OSAwIDAgMSAyLjA3ODQtMi4wNzg5eiIgZmlsdGVyPSJ1cmwoI2EpIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iLjI2NDU4cHgiLz4KPHBhdGggZD0ibTgzLjUxOCA3MC4yODNjLTEuMTg2MiAwLjAxNTQtMi4zNDE4IDAuODQ2ODgtMi40Nzc0IDEuOTk4OGwtMC4wMTI0IDAuMDQ5MWMtMC4xNDk5IDUuNjExMi0wLjA0NzQgMTEuMjUyLTAuMTExNjIgMTYuODc0IDAuMDU4MSAxLjg3NzctMC40NDMzNyAzLjg0NDEgMC44NjgxNyA1LjIwMTIgMS43NDYyIDEuNzQ0NSA0LjIzMTcgMC40OTM5MyA0LjE0MjQtMi41MTU2IDAuMDQ1NS0wLjkyODQyIDAuMDExNy0xLjQwMiAwLjExMDA3LTMuMjM0OSAxLjUwNTIgMS4xMzQ4IDIuOTYwNCAyLjk5NjYgNC44MTk5IDMuNTIzOCAyLjAzNzEgMC4zMTUyOCAzLjQ3NDgtMi4yMDU1IDIuMzEyLTMuODU1MS0yLjI5OTItMi4yMDI1LTQuODIwMi00LjA1Ny03LjE2Ni02LjIyODYgMC4xMzg5LTMuMyAwLjAxNzYtNi4xNDIgMC4wNDY1LTkuNjA2Ny0wLjQxNDMtMS41OTgtMS40ODQ5LTIuMjE5Ni0yLjUzMTYtMi4yMDYxem0wLjE4NDQ5IDExLjg4M2EyLjA3ODkgMi4wNzg5IDAgMCAxIDIuMDc4OSAyLjA3ODkgMi4wNzg5IDIuMDc4OSAwIDAgMS0yLjA3ODkgMi4wNzkgMi4wNzg5IDIuMDc4OSAwIDAgMS0yLjA3ODQtMi4wNzkgMi4wNzg5IDIuMDc4OSAwIDAgMSAyLjA3ODQtMi4wNzg5eiIgZmlsbD0iI2ZmZiIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9Ii4yNjQ1OHB4Ii8+CjxwYXRoIGQ9Im04My43MTYgODAuMThhNC4zMDk3IDQuMzA5NyAwIDAgMC00LjMwOTggNC4zMDk4IDQuMzA5NyA0LjMwOTcgMCAwIDAgNC4zMDk4IDQuMzA5OCA0LjMwOTcgNC4zMDk3IDAgMCAwIDQuMzA5OC00LjMwOTggNC4zMDk3IDQuMzA5NyAwIDAgMC00LjMwOTgtNC4zMDk4em0wLjAzMzYgMi4xMDQ4YTIuMTcxNiAyLjE3MTYgMCAwIDEgMi4xNzE1IDIuMTcxNSAyLjE3MTYgMi4xNzE2IDAgMCAxLTIuMTcxNSAyLjE3MTQgMi4xNzE2IDIuMTcxNiAwIDAgMS0yLjE3MTQtMi4xNzE0IDIuMTcxNiAyLjE3MTYgMCAwIDEgMi4xNzE0LTIuMTcxNXoiIGZpbGw9IiNmZmYiIGZpbHRlcj0idXJsKCNiKSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9Ii41Ii8+CjxwYXRoIGQ9Im04My42NjkgODAuMDYyYTQuMzA5NyA0LjMwOTcgMCAwIDAtNC4zMDk4IDQuMzA5OCA0LjMwOTcgNC4zMDk3IDAgMCAwIDQuMzA5OCA0LjMwOTggNC4zMDk3IDQuMzA5NyAwIDAgMCA0LjMwOTgtNC4zMDk4IDQuMzA5NyA0LjMwOTcgMCAwIDAtNC4zMDk4LTQuMzA5OHptMC4wMzM2IDIuMTA0OGEyLjE3MTYgMi4xNzE2IDAgMCAxIDIuMTcxNSAyLjE3MTQgMi4xNzE2IDIuMTcxNiAwIDAgMS0yLjE3MTUgMi4xNzE0IDIuMTcxNiAyLjE3MTYgMCAwIDEtMi4xNzE0LTIuMTcxNCAyLjE3MTYgMi4xNzE2IDAgMCAxIDIuMTcxNC0yLjE3MTR6IiBmaWxsPSIjZmZmIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iLjYiLz4KPC9nPgo8L3N2Zz4K"); + width:0px; + height:0px; + cursor:pointer; + padding: 0.6em; + background-repeat: no-repeat; + vertical-align:middle; + color: rgba(0, 0, 0, 0); } - - \ No newline at end of file diff --git a/assets/sai.js b/assets/sai.js index 59477d8..2f523cd 100644 --- a/assets/sai.js +++ b/assets/sai.js @@ -5,6 +5,395 @@ * that has HTML stuff in it */ +/* http://i18njs.com/ this from http://i18njs.com/js/i18n.js */ +(function() { + var Translator, i18n, translator, + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + Translator = (function() { + function Translator() { + this.translate = __bind(this.translate, this); this.data = { + values: {}, + contexts: [] + }; + this.globalContext = {}; + } + + Translator.prototype.translate = function(text, defaultNumOrFormatting, + numOrFormattingOrContext, formattingOrContext, context) { + var defaultText, formatting, isObject, num; + + if (context == null) { + context = this.globalContext; + } + isObject = function(obj) { + var type; + + type = typeof obj; + return type === "function" || type === "object" && !!obj; + }; + if (isObject(defaultNumOrFormatting)) { + defaultText = null; + num = null; + formatting = defaultNumOrFormatting; + context = numOrFormattingOrContext || this.globalContext; + } else { + if (typeof defaultNumOrFormatting === "number") { + defaultText = null; + num = defaultNumOrFormatting; + formatting = numOrFormattingOrContext; + context = formattingOrContext || this.globalContext; + } else { + defaultText = defaultNumOrFormatting; + if (typeof numOrFormattingOrContext === "number") { + num = numOrFormattingOrContext; + formatting = formattingOrContext; + context = context; + } else { + num = null; + formatting = numOrFormattingOrContext; + context = formattingOrContext || this.globalContext; + } + } + } + if (isObject(text)) { + if (isObject(text['i18n'])) { + text = text['i18n']; + } + return this.translateHash(text, context); + } else { + return this.translateText(text, num, formatting, context, defaultText); + } + }; + + Translator.prototype.add = function(d) { + var c, v, _i, _len, _ref, _ref1, _results; + + if ((d.values != null)) { + _ref = d.values; + var k; + for (k in _ref) { + if ({}.hasOwnProperty.call(_ref, k)) { + v = _ref[k]; + this.data.values[k] = v; + } + } + } + if ((d.contexts != null)) { + _ref1 = d.contexts; + _results = []; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + c = _ref1[_i]; + _results.push(this.data.contexts.push(c)); + } + return _results; + } + }; + + Translator.prototype.setContext = function(key, value) { + return this.globalContext[key] = value; + }; + + Translator.prototype.clearContext = function(key) { + return this.lobalContext[key] = null; + }; + + Translator.prototype.reset = function() { + this.data = { + values: {}, + contexts: [] + }; + return this.globalContext = {}; + }; + + Translator.prototype.resetData = function() { + return this.data = { + values: {}, + contexts: [] + }; + }; + + Translator.prototype.resetContext = function() { + return this.globalContext = {}; + }; + + Translator.prototype.translateHash = function(hash, context) { + var k, v; + + for (k in hash) { + if ({}.hasOwnProperty.call(hash, k)) { + v = hash[k]; + if (typeof v === "string") { + hash[k] = this.translateText(v, null, null, context); + } + } + } + return hash; + }; + + Translator.prototype.translateText = function(text, num, formatting, + context, defaultText) { + var contextData, result; + + if (context == null) { + context = this.globalContext; + } + if (this.data == null) { + return this.useOriginalText(defaultText || text, num, formatting); + } + contextData = this.getContextData(this.data, context); + if (contextData != null) { + result = this.findTranslation(text, num, formatting, contextData.values, + defaultText); + } + if (result == null) { + result = this.findTranslation(text, num, formatting, this.data.values, + defaultText); + } + if (result == null) { + return this.useOriginalText(defaultText || text, num, formatting); + } + return result; + }; + + Translator.prototype.findTranslation = function(text, num, formatting, data) { + var result, triple, value, _i, _len; + + value = data[text]; + if (value == null) { + return null; + } + if (num == null) { + if (typeof value === "string") { + return this.applyFormatting(value, num, formatting); + } + } else { + if (value instanceof Array || value.length) { + for (_i = 0, _len = value.length; _i < _len; _i++) { + triple = value[_i]; + if ((num >= triple[0] || triple[0] === null) && + (num <= triple[1] || triple[1] === null)) { + result = this.applyFormatting(triple[2].replace("-%n", + String(-num)), num, formatting); + return this.applyFormatting(result.replace("%n", + String(num)), num, formatting); + } + } + } + } + return null; + }; + + Translator.prototype.getContextData = function(data, context) { + var c, equal, key, value, _i, _len, _ref, _ref1; + + if (data.contexts == null) { + return null; + } + _ref = data.contexts; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + c = _ref[_i]; + equal = true; + _ref1 = c.matches; + for (key in _ref1) { + if ({}.hasOwnProperty.call(_ref1, key)) { + value = _ref1[key]; + equal = equal && value === context[key]; + } + } + if (equal) { + return c; + } + } + return null; + }; + + Translator.prototype.useOriginalText = function(text, num, formatting) { + if (num == null) { + return this.applyFormatting(text, num, formatting); + } + return this.applyFormatting(text.replace("%n", String(num)), + num, formatting); + }; + + Translator.prototype.applyFormatting = function(text, num, formatting) { + var ind, regex; + + for (ind in formatting) { + if ({}.hasOwnProperty.call(formatting, ind)) { + regex = new RegExp("%{" + ind + "}", "g"); + text = text.replace(regex, formatting[ind]); + } + } + return text; + }; + + return Translator; + + })(); + + translator = new Translator(); + + i18n = translator.translate; + + i18n.translator = translator; + + i18n.create = function(data) { + var trans; + + trans = new Translator(); + if (data != null) { + trans.add(data); + } + trans.translate.create = i18n.create; + return trans.translate; + }; + + (typeof module !== "undefined" && module !== null ? module.exports = i18n : void 0) || + (this.i18n = i18n); + +}.call(this)); + +var lang_ja = "{" + + "\"values\":{" + + "\"Summary\": \"概要\"," + + "\"Log\": \"ログ\"," + + "\"Tree\": \"木構造\"," + + "\"Blame\": \"責任\"," + + "\"Copy Lines\": \"コピーライン\"," + + "\"Copy Link\": \"リンクをコピーする\"," + + "\"View Blame\": \"責任がある\"," + + "\"Remove Blame\": \"責任を取り除く\"," + + "\"Mode\": \"モード\"," + + "\"Size\": \"サイズ\"," + + "\"Name\": \"名\"," + + "\"s\": \"秒\"," + + "\"m\": \"分\"," + + "\"h\": \"時間\"," + + "\" days\": \"日々\"," + + "\" weeks\": \"週\"," + + "\" months\": \"数ヶ月\"," + + "\" years\": \"年\"," + + "\"Branch Snapshot\": \"ブランチスナップショット\"," + + "\"Tag Snapshot\": \"タグスナップショット\"," + + "\"Commit Snapshot\": \"スナップショットをコミットする\"," + + "\"Description\": \"説明\"," + + "\"Owner\": \"オーナー\"," + + "\"Branch\": \"ブランチ\"," + + "\"Tag\": \"タグ\"," + + "\"Author\": \"著者\"," + + "\"Age\": \"年齢\"," + + "\"Page fetched\": \"ページを取得した\"," + + "\"creation time\": \"作成時間\"," + + "\"created\": \"作成した\"," + + "\"ago\": \"前\"," + + "\"Message\": \"メッセージ\"," + + "\"Download\": \"ダウンロード\"," + + "\"root\": \"ルート\"," + + "\"Committer\": \"コミッター\"," + + "\"Raw Patch\": \"生パッチ\"," + + "\"Page fetched %{pf} ago, creation time: %{ct}ms " + + "(vhost etag hits: %{ve}%, cache hits: %{ch}%)\": " + + "\"%{pf}間前に取得されたページ, 作成にかかった時間: %{ct}ms " + + "(vhost etag キャッシュヒット: %{ve}%, キャッシュヒット: %{ch}%)\"," + + "\"Created %{pf} ago, creation time: %{ct}ms \":" + + "\"%{pf}間前に作成されました, 作成にかかった時間: %{ct}ms\"" + + "}}"; + +var lang_zht = "{" + +"\"values\":{" + + "\"Summary\": \"概要\"," + + "\"Log\": \"日誌\"," + + "\"Tree\": \"樹\"," + + "\"Blame\": \"責怪\"," + + "\"Copy Lines\": \"複製線\"," + + "\"Copy Link\": \"複製鏈接\"," + + "\"View Blame\": \"看責怪\"," + + "\"Remove Blame\": \"刪除責怪\"," + + "\"Mode\": \"模式\"," + + "\"Size\": \"尺寸\"," + + "\"Name\": \"名稱\"," + + "\"s\": \"秒\"," + + "\"m\": \"分鐘\"," + + "\"h\": \"小時\"," + + "\" days\": \"天\"," + + "\" weeks\": \"週\"," + + "\" months\": \"個月\"," + + "\" years\": \"年份\"," + + "\"Branch Snapshot\": \"科快照\"," + + "\"Tag Snapshot\": \"标签快照\"," + + "\"Commit Snapshot\": \"提交快照\"," + + "\"Description\": \"描述\"," + + "\"Owner\": \"所有者\"," + + "\"Branch\": \"科\"," + + "\"Tag\": \"標籤\"," + + "\"Author\": \"作者\"," + + "\"Age\": \"年齡\"," + + "\"Page fetched\": \"頁面已獲取\"," + + "\"creation time\": \"創作時間\"," + + "\"created\": \"創建\"," + + "\"ago\": \"前\"," + + "\"Message\": \"信息\"," + + "\"Download\": \"下載\"," + + "\"root\": \"根源\"," + + "\"Committer\": \"提交者\"," + + "\"Raw Patch\": \"原始補丁\"," + + "\"Page fetched %{pf} ago, creation time: %{ct}ms " + + "(vhost etag hits: %{ve}%, cache hits: %{ch}%)\": " + + "\"頁面%{pf}前獲取, 創作時間: %{ct}ms " + + "(vhost etag 緩存命中: %{ve}%, 緩存命中: %{ch}%)\"," + + "\"Created %{pf} ago, creation time: %{ct}ms \":" + + "\"%{pf}前創建, 創作時間: %{ct}ms \"" + +"}}"; + +var lang_zhs = "{" + +"\"values\":{" + + "\"Summary\": \"概要\"," + + "\"Log\": \"日志\"," + + "\"Tree\": \"木\"," + + "\"Blame\": \"归咎\"," + + "\"Copy Lines\": \"复制线\"," + + "\"Copy Link\": \"复制链接\"," + + "\"View Blame\": \"看责备\"," + + "\"Remove Blame\": \"删除责备\"," + + "\"Mode\": \"模式\"," + + "\"Size\": \"尺寸\"," + + "\"Name\": \"名称\"," + + "\"s\": \"秒\"," + + "\"m\": \"分钟\"," + + "\"h\": \"小时\"," + + "\" days\": \"天\"," + + "\" weeks\": \"周\"," + + "\" months\": \"个月\"," + + "\" years\": \"年份\"," + + "\"Branch Snapshot\": \"科快照\"," + + "\"Tag Snapshot\": \"标签快照\"," + + "\"Commit Snapshot\": \"提交快照\"," + + "\"Description\": \"描述\"," + + "\"Owner\": \"所有者\"," + + "\"Branch\": \"科\"," + + "\"Tag\": \"标签\"," + + "\"Author\": \"作者\"," + + "\"Age\": \"年龄\"," + + "\"Page fetched\": \"页面已获取\"," + + "\"creation time\": \"创作时间\"," + + "\"created\": \"创建\"," + + "\"ago\": \"前\"," + + "\"Message\": \"信息\"," + + "\"Download\": \"下载\"," + + "\"root\": \"根源\"," + + "\"Committer\": \"提交者\"," + + "\"Raw Patch\": \"原始补丁\"," + + "\"Page fetched %{pf} ago, creation time: %{ct}ms " + + "(vhost etag hits: %{ve}%, cache hits: %{ch}%)\": " + + "\"页面%{pf}前获取, 创作时间: %{ct}ms " + + "(vhost etag 缓存命中: %{ve}%, 缓存命中: %{ch}%)\"," + + "\"Created %{pf} ago, creation time: %{ct}ms \":" + + "\"%{pf}前创建, 创作时间: %{ct}ms \"" + +"}}"; + +var logs = "", redpend = 0, gitohashi_integ = 0; + function san(s) { if (s.search("<") !== -1) @@ -29,7 +418,23 @@ function humanize(s) return s; } - var pos = 0; +function hsanitize(s) +{ + var table = { + '<': 'lt', + '>': 'gt', + '"': 'quot', + '\'': 'apos', + '&': 'amp' + }; + + return s.toString().replace(/[<>"'&]/g, function(chr) { + return '&' + table[chr] + ';'; + }).replace(/\n/g, '<br>'); +} + +var pos = 0, lli = 1, lines = "", times = "", locked = 1, tfirst = 0, + cont = [ 0, 0, 0, 0, 0]; function get_appropriate_ws_url() { @@ -55,182 +460,701 @@ function get_appropriate_ws_url() return pcol + u[0]; } +var age_names = [ "s", "m", "h", " days", " weeks", " months", " years" ]; +var age_div = [ 1, 60, 3600, 86400, 604800, 2419200, 31536000 ]; +var age_limit = [ 120, 7200, 172800, 1209600, 4838400, 63072000, 0 ]; +var age_upd = [ 5, 10, 300, 1800, 3600, 12 * 3600, 12 * 3600 ]; + +function agify(now, secs) +{ + var d = now - secs, n; + + if (!secs) + return ""; + + for (n = 0; n < age_names.length; n++) + if (d < age_limit[n] || age_limit[n] === 0) + return "<span class='age-" + n + "' ut='" + secs + + "'>" + Math.ceil(d / age_div[n]) + + i18n(age_names[n]) + "</span>"; +} + +function aging() +{ + var n, next = 24 * 3600, + now_ut = Math.round((new Date().getTime() / 1000)); + + for (n = 0; n < age_names.length; n++) { + var m, elems = document.getElementsByClassName( + "age-" + n), ne = elems.length, + a = new Array(), ee = new Array(); + + if (elems.length && age_upd[n] < next) + next = age_upd[n]; + + for (m = 0; m < ne; m++) { + var e = elems[m], secs = elems[m].getAttribute("ut"); + + a.push(agify(now_ut, secs)); + ee.push(e); + } + + for (m = 0; m < ee.length; m++) { + ee[m].innerHTML = a[m]; + } + } + + /* + * We only need to come back when the age might have changed. + * Eg, if everything is counted in hours already, once per + * 5 minutes is accurate enough. + */ + window.setTimeout(aging, next * 1000); +} +var sai, jso, s, sai_arts = ""; + +function sai_plat_icon(plat, size) +{ + var s, s1 = ""; + + s = plat.split('/'); + if (s[0]) { + console.log("plat " + plat + " plat[0] " + s[0]); + s1 = "<img class=\"ip" + size + " zup\" src=\"/sai/" + san(s[0]) + + ".svg\">"; + + if (s[1]) + s1 += "<img class=\"ip" + size + " tread1\" src=\"/sai/arch-" + san(s[1]) + ".svg\">"; + } + + if (s[2]) { + s1 += "<img class=\"ip" + size + " tread2\" src=\"/sai/tc-" + san(s[2]) + ".svg\">"; + } + return s1; +} + +function sai_stateful_taskname(state, nm, sf) +{ + var tp = ""; + + if (sf) + return "<span id=\"taskstate\" class=\"ti2 taskstate" + + state + "\">&nbsp;" + san(nm) + "&nbsp;&nbsp;</span>"; + + if (state == 4 || state == 6) + tp = " ov_bad"; + + return "<span id=\"taskstate\" class=\"ti2 " + tp + "\">" + san(nm) + "</span>"; +} + +function sai_taskinfo_render(t, now_ut) +{ + var now_ut = Math.round((new Date().getTime() / 1000)); + var s = ""; + + s = "<div class=\"taskinfo\"><table><tr class=\"nomar\"><td class=\"atop\"><table>" + + sai_event_render(t, now_ut, 0) + "</table></td><td class=\"ti\">" + + "<span class=\"ti1\">" + sai_plat_icon(t.t.platform, 2) + + san(t.t.platform) + "</span>&nbsp;"; + if (t.t.state != 0 && t.t.state != 3 && t.t.state != 4 && t.t.state != 5) + s += "<img class=\"rebuild\" alt=\"stop build\" src=\"stop.svg\" " + + "id=\"stop-" + san(t.t.uuid) + "\">&nbsp;"; + s += "<img class=\"rebuild\" alt=\"rebuild\" src=\"rebuild.png\" " + + "id=\"rebuild-" + san(t.t.uuid) + "\">&nbsp;" + + sai_stateful_taskname(t.t.state, t.t.taskname, 1); + + if (t.t.builder_name) { + var now_ut = Math.round((new Date().getTime() / 1000)); + + s += "&nbsp;&nbsp;<span class=\"ti5\"><img class=\"bico\" src=\"/sai/builder-instance.png\">&nbsp;" + + san(t.t.builder_name) + "</span>"; + if (t.t.started) + /* started is a unix time, in seconds */ + s += ", <span class=\"ti5\"> " + + agify(now_ut, t.t.started) + " ago, Dur: " + + (t.t.duration ? t.t.duration / 1000000 : + now_ut - t.t.started).toFixed(1) + + "s</span><div id=\"sai_arts\"></div>"; + sai_arts = ""; + } + + s += "</td></tr>"; + + s += "</td></tr></table></table></div>"; + + return s; +} + +function sai_event_summary_render(o, now_ut, reset_all_icon) +{ + var s, q, ctn = "", wai, s1 = "", n, e = o.e; + + s = "<tr><td class=\"waiting\"><table class=\"comp"; + if (e.state == 3) + s += " comp_pass"; + if (e.state == 4 || e.state == 6) + s += " comp_fail"; + + s += "\"><tr><td class=\"jumble\"><a href=\"/sai/?event=" + san(e.uuid) + + "\"><img src=\"/sai/sai-event.svg\""; + if (e.state == 3 || e.state == 4) + s += " class=\"deemph\""; + s += ">"; + if (e.state == 3) + s += "<div class=\"evr\"><img src=\"/sai/passed.svg\"></div>"; + if (e.state == 4) + s += "<div class=\"evr\"><img src=\"/sai/failed.svg\"></div>"; + + s += "</a>"; + if (reset_all_icon && !gitohashi_integ) { + s += "<br><img class=\"rebuild\" alt=\"rebuild all\" src=\"/sai/rebuild.png\" " + + "id=\"rebuild-ev-" + san(e.uuid) + "\">&nbsp;"; + s += "<img class=\"rebuild\" alt=\"delete event\" src=\"/sai/delete.png\" " + + "id=\"delete-ev-" + san(e.uuid) + "\">"; + } + s += "</td>" + + "<td><table class=\"nomar\">" + + "<tr><td class=\"nomar\" colspan=2><span class=\"e1\">" + + san(e.repo_name) + + "</span></td></tr><tr><td class=\"nomar\" colspan=2><span class=\"e2\">"; + + if (e.ref.substr(0, 11) === "refs/heads/") { + s += "<img class=\"branch\">" + + san(e.ref.substr(11)); + } else + if (e.ref.substr(0, 10) === "refs/tags/") { + s += "<img class=\"tag\">" + + san(e.ref.substr(10)); + } else + s += san(e.ref); + + s += "</span></td></tr><tr><td class=\"nomar\"><span class=\"e3\">" + + san(e.hash.substr(0, 8)) + + "</span></td><td class=\"e4\" class=\"nomar\">" + + agify(now_ut, e.created) + "</td></tr>"; + if (o.t.length > 1) + s += "<tr><td class=\"nomar\"><span class=\"e3\">" + o.t.length + " builds</span></td><tr>"; + s += "</table>" + + "</td></tr></table></td>"; + + return s; +} + +function sai_event_render(o, now_ut, reset_all_icon) +{ + var s, q, ctn = "", wai, s1 = "", n, e = o.e; + + s = sai_event_summary_render(o, now_ut, reset_all_icon); -var sai, jso, s; + if (o.t.length) { + var all_good, has_bad, all_done; + + /* + * SAIES_WAITING, + * SAIES_PASSED_TO_BUILDER, + * SAIES_BEING_BUILT, + * SAIES_SUCCESS, done --> + * SAIES_FAIL, + * SAIES_CANCELLED, + * SAIES_BEING_BUILT_HAS_FAILURES + */ + + s += "<td class=\"tasks\"><table><tr><td class=\"atop\">"; -function ws_open_server_status() + for (q = 0; q < o.t.length; q++) { + var t = o.t[q]; + + if (t.taskname !== ctn) { + if (ctn !== "") { + var st = 0; + /* + * We defer issuing the task overview + * until we know the class element to + * use for the container + */ + + s += "<div class=\""; + if (wai) + s += " awaiting"; + if (all_good === 1) { + s += " ov_good"; + st = 3; + } else { + if (has_bad === 1) { + s += " ov_bad"; + st = 4; + } else + if (all_done === 0) + s += " ov_dunno"; + } + + s += " ib\"><table class=\"nomar\">" + + "<tr><td class=\"keepline\">" + s1; + s += "</td><td class=\"tn\">" + + sai_stateful_taskname(st, ctn, 0) + + "</td></tr></table></div>"; + s1 = ""; + } + all_good = 1; + has_bad = 0; + all_done = 1; + ctn = t.taskname; + wai = 0; + } + + if (t.state < 3) + all_done = 0; + if (t.state !== 3) + all_good = 0; + if (t.state === 4 || t.state == 6) + has_bad = 1; + + s1 += "<div class=\"taskstate taskstate" + t.state + "\">"; + if (t.state === 0) + wai = 1; + + s1 += "<a href=\"/sai/index.html?task=" + t.uuid + "\">" + + sai_plat_icon(t.platform, 0) + "</a>"; + + s1 += "</div>"; + } + + if (ctn !== "") { + var st = 0; + + s += "<div class=\""; + if (wai) + s += " awaiting"; + if (all_good === 1) { + s += " ov_good"; + st = 3; + } else { + if (has_bad === 1) { + s += " ov_bad"; + st = 4; + } else + if (all_done === 0) + s += " ov_dunno"; + } + s += " ib\"><table class=\"nomar\">" + + "<tr><td class=\"keepline\">" + s1; + s += "</td><td class=\"tn\">" + + sai_stateful_taskname(st, ctn, 0) + + "</td></tr></table></div>"; + } + + s += "</td></tr></table></td>"; + } + + s += "</tr>"; + + return s; +} + +function render_builders(jso) +{ + var s, n, conts = [], nc = 0; + + s = "<table class=\"builders\"><tr><td>"; + +// s = "<table class=\"builders\"><tr><td><img class=\"ip bsvg\" " + +// "src=\"/sai/builder.png\"></td><td>"; + + for (n = 0; n < jso.builders.length; n++) { + var e = jso.builders[n], host; + + host = e.name.split('.')[0]; + if (host.split('-')[1]) { + var m; + + for (m = 0; m < conts.length; m++) + if (host.split('-')[1] === conts[m]) + m = 999; + if (m < 999 || !conts.length) + conts.push(host.split('-')[1]); + } + } + + for (nc = 0; nc <= conts.length; nc++) { + var samplat = ""; + + /* + * nc == conts.length means those not + * in a container + */ + + if (nc < conts.length) + s += "<div class=\"ibuil ibuilctr bdr\"><div class=\"ibuilctrname bdr\">" + conts[nc] + "</div>"; + + for (n = 0; n < jso.builders.length; n++) { + var e = jso.builders[n], nn, host, plat, cia, sen; + + sen = e.name; + host = sen.split('.')[0]; + cia = host.split('-')[1]; + + console.log("host " + host + ", cia " + cia); + + if ((nc == conts.length && !cia) || + (cia && cia === conts[nc])) { + var did = 0, arc; + + if (!n || + host !== jso.builders[n - 1].name.split('.')[0] || + e.platform != samplat) { + s += "<div class=\"ibuil bdr\" title=\"" + + san(e.platform) + "@" + san(host) + + "\"><table class=\"nomar\"><tr><td class=\"bn\">" + + sai_plat_icon(e.platform, 1); + + samplat = e.platform; + did = 1; + } + + if (n + 1 == jso.builders.length || did) + s += "</td></tr></table></div>"; + } + } + if (nc < conts.length) + s += "</div>"; + } + + s += "</td></tr></table></td></tr>"; + s = s + "</table>"; + + return s; +} + +function ws_open_sai() { - sai = new WebSocket(get_appropriate_ws_url() + "/browse", "com-warmcat-sai"); + var s = "", q, qa, qi, q5, q5s; - console.log(get_appropriate_ws_url() + "/browse"); + q = window.location.href; + console.log(q); + qi = q.indexOf("/git/"); + if (qi !== -1) { + /* it has the /git/... does it have the project? */ + s += "/specific"; + q5 = q.substring(qi + 5); + console.log("q5 = " + q5); + q5s = q5.indexOf("/"); + if (q5s !== -1) + s += "/" + q5.substring(0, q5s); + else + s += "/" + q5; + + /* + * gitohashi has ?h=branch and ?id=hash possible + */ + qa = q.split("?"); + if (qa[1]) + s += "?" + qa[1]; + console.log(s); + gitohashi_integ = 1; + } + + sai = new WebSocket(get_appropriate_ws_url() + "/sai/browse" + s, + "com-warmcat-sai"); try { sai.onopen = function() { - // document.getElementById("title").innerHTML = "Server Status (Active)"; - lws_gray_out(false); + var par = new URLSearchParams(window.location.search), + tid, eid; + tid = par.get('task'); + eid = par.get('event'); + + + if (tid) { + /* + * We're being the page monitoring / reporting + * on what happened with a specific task... ask + * about the specific task on the ws link + */ + + console.log("tid " + tid); + + sai.send("{\"schema\":" + + "\"com.warmcat.sai.taskinfo\"," + + "\"logs\": 1," + + "\"task_hash\":" + + JSON.stringify(tid) + "}"); + + return; + } + + if (eid) { + /* + * We're being the page monitoring / reporting + * on what happened with a specific event... ask + * about the specific event on the ws link + */ + + console.log("eid " + eid); + + sai.send("{\"schema\":" + + "\"com.warmcat.sai.eventinfo\"," + + "\"event_hash\":" + + JSON.stringify(eid) + "}"); + + return; + } + + /* + * request the overview schema + */ + + sai.send("{\"schema\":" + + "\"com.warmcat.sai.taskinfo\"}"); }; sai.onmessage =function got_packet(msg) { var u, ci, n; + var now_ut = Math.round((new Date().getTime() / 1000)); + console.log(msg.data); - if (msg.data.length < 100) - return; + // if (msg.data.length < 10) + // return; jso = JSON.parse(msg.data); - u = parseInt(san(jso.i.uptime), 10); - - if (parseInt(jso.i.contexts[0].deprecated, 10) === 0) - s = "<table><tr><td></td><td class=\"c0\">"; - else - s = "<table><tr><td></td><td class=\"dc0\">"; - s += - "Server</td><td>" + - "<span class=\"sn\">Server Version:</span> <span class=\"v\">" + - san(jso.i.version) + "</span><br>" + - "<span class=\"sn\">Host Uptime:</span> <span class=\"v\">" + - ((u / (24 * 3600)) | 0) + "d " + - (((u % (24 * 3600)) / 3600) | 0) + "h " + - (((u % 3600) / 60) | 0) + "m</span>"; - if (jso.i.l1) - s = s + ", <span class=\"sn\">Host Load:</span> <span class=\"v\">" + san(jso.i.l1) + " "; - if (jso.i.l2) - s = s + san(jso.i.l2) + " "; - if (jso.i.l3) - s = s + san(jso.i.l3); - if (jso.i.l1) - s =s + "</span>"; - - if (jso.i.statm) { - var sm = jso.i.statm.split(" "); - s += ", <span class=\"sn\">Virt stack + heap Usage:</span> <span class=\"v\">" + - humanize(parseInt(sm[0], 10) * 4096) + "B</span>"; + console.log(jso.schema); + + if (jso.alang) { + var a = jso.alang.split(","), n; + + for (n = 0; n < a.length; n++) { + var b = a[n].split(";"); + switch (b[0]) { + case "ja": + i18n.translator.add(JSON.parse(lang_ja)); + n = a.length; + break; + case "zh_TW": + case "zh_HK": + case "zh_SG": + case "zh_HANT": + case "zh-TW": + case "zh-HK": + case "zh-SG": + case "zh-HANT": + i18n.translator.add(JSON.parse(lang_zht)); + n = a.length; + break; + case "zh": + case "zh_CN": + case "zh_HANS": + case "zh-CN": + case "zh-HANS": + i18n.translator.add(JSON.parse(lang_zhs)); + n = a.length; + break; + case "en": + case "en_US": + case "en-US": + n = a.length; + break; + } + } } + + if (jso.schema == "com.warmcat.sai.builders") { + + s = render_builders(jso); - for (n = 0; n < jso.files.length; n++) { - s += "<br><span class=n>" + san(jso.files[n].path) + ":</span><br> " + san(jso.files[n].val); + if (document.getElementById("sai_builders")) + document.getElementById("sai_builders").innerHTML = s; } - s += "</td></tr>"; - - for (ci = 0; ci < jso.i.contexts.length; ci++) { - - if (parseInt(jso.i.contexts[ci].deprecated, 10) === 0) - s += "<tr><td></td><td class=\"c\">" + - "Active Context</td><td>"; - else - s += "<tr><td></td><td class=\"c1\">" + - "Deprecated Context " + ci + "</td><td>"; - - u = parseInt(san(jso.i.contexts[ci].context_uptime), 10); - s += "<span class=n>Server Uptime:</span> <span class=v>" + - ((u / (24 * 3600)) | 0) + "d " + - (((u % (24 * 3600)) / 3600) | 0) + "h " + - (((u % 3600) / 60) | 0) + "m</span>"; - - s = s + - "<br>" + - "<span class=n>Listening wsi:</span> <span class=v>" + san(jso.i.contexts[ci].listen_wsi) + "</span>, " + - "<span class=n>Current wsi alive:</span> <span class=v>" + (parseInt(san(jso.i.contexts[ci].wsi_alive), 10) - - parseInt(san(jso.i.contexts[ci].listen_wsi), 10)) + "</span><br>" + - "<span class=n>Total Rx:</span> <span class=v>" + humanize(san(jso.i.contexts[ci].rx)) +"B</span>, " + - "<span class=n>Total Tx:</span> <span class=v>" + humanize(san(jso.i.contexts[ci].tx)) +"B</span><br>" + - - "<span class=n>CONNECTIONS: HTTP/1.x:</span> <span class=v>" + san(jso.i.contexts[ci].h1_conn) +"</span>, " + - "<span class=n>Websocket:</span> <span class=v>" + san(jso.i.contexts[ci].ws_upg) +"</span>, " + - "<span class=n>H2 upgrade:</span> <span class=v>" + san(jso.i.contexts[ci].h2_upg) +"</span>, " + - "<span class=n>H2 ALPN:</span> <span class=v>" + san(jso.i.contexts[ci].h2_alpn) +"</span>, " + - "<span class=n>Rejected:</span> <span class=v>" + san(jso.i.contexts[ci].rejected) +"</span><br>" + - - "<span class=n>TRANSACTIONS: HTTP/1.x:</span> <span class=v>" + san(jso.i.contexts[ci].h1_trans) + "</span>, " + - "<span class=n>H2:</span> <span class=v>" + san(jso.i.contexts[ci].h2_trans) +"</span>, " + - "<span class=n>Total H2 substreams:</span> <span class=v>" + san(jso.i.contexts[ci].h2_subs) +"</span><br>" + - - "<span class=n>CGI: alive:</span> <span class=v>" + san(jso.i.contexts[ci].cgi_alive) + "</span>, " + - "<span class=n>spawned:</span> <span class=v>" + san(jso.i.contexts[ci].cgi_spawned) + - "</span><table>"; - - for (n = 0; n < jso.i.contexts[ci].pt.length; n++) { - - if (parseInt(jso.i.contexts[ci].deprecated, 10) === 0) - s += "<tr><td>&nbsp;&nbsp;</td><td class=\"l\">service thread " + (n + 1); - else - s += "<tr><td>&nbsp;&nbsp;</td><td class=\"dl\">service thread " + (n + 1); - s += "</td><td>" + - "<span class=n>fds:</span> <span class=v>" + san(jso.i.contexts[ci].pt[n].fds_count) + " / " + - san(jso.i.contexts[ci].pt_fd_max) + "</span>, "; - s = s + "<span class=n>ah pool:</span> <span class=v>" + san(jso.i.contexts[ci].pt[n].ah_pool_inuse) + " / " + - san(jso.i.contexts[ci].ah_pool_max) + "</span>, " + - "<span class=n>ah waiting list:</span> <span class=v>" + san(jso.i.contexts[ci].pt[n].ah_wait_list); - - s = s + "</span></td></tr>"; - + + if (jso.schema == "sai.warmcat.com.overview") { + s = "<table>"; + + if (jso.overview.length) + for (n = jso.overview.length - 1; n >= 0; n--) + s += sai_event_render(jso.overview[n], now_ut, 1); + + s = s + "</table>"; + + if (document.getElementById("sai_sticky")) + document.getElementById("sai_sticky").innerHTML = s; + aging(); + + if (jso.overview.length) + for (n = jso.overview.length - 1; n >= 0; n--) { + if (document.getElementById("rebuild-ev-" + san(jso.overview[n].e.uuid))) + document.getElementById("rebuild-ev-" + san(jso.overview[n].e.uuid)). + addEventListener("click", function(e) { + console.log(e); + var rs= "{\"schema\":" + + "\"com.warmcat.sai.eventreset\"," + + "\"uuid\": " + + JSON.stringify(san(e.srcElement.id.substring(11))) + "}"; + + console.log(rs); + sai.send(rs); + }); + if (document.getElementById("delete-ev-" + san(jso.overview[n].e.uuid))) + document.getElementById("delete-ev-" + san(jso.overview[n].e.uuid)). + addEventListener("click", function(e) { + console.log(e); + var rs= "{\"schema\":" + + "\"com.warmcat.sai.eventdelete\"," + + "\"uuid\": " + + JSON.stringify(san(e.srcElement.id.substring(10))) + "}"; + + console.log(rs); + sai.send(rs); + }); + } + } + + if (jso.schema == "com.warmcat.sai.taskinfo") { + s = sai_taskinfo_render(jso); + if (document.getElementById("sai_sticky")) + document.getElementById("sai_sticky").innerHTML = s; + + + s = "<table><td colspan=\"3\"><pre><table><tr>" + + "<td class=\"atop\">" + + "<div id=\"dlogsn\" class=\"dlogsn\">" + lines + "</div></td>" + + "<td class=\"atop\">" + + "<div id=\"dlogst\" class=\"dlogst\">" + times + "</div></td>" + + "<td class=\"atop\"><div id=\"dlogs\" class=\"dlogs\">" + + "<span id=\"logs\" class=\"nowrap\">" + logs + + "</span>"+ + "</div></td></tr></table></pre>"; + + if (document.getElementById("sai_overview")) + document.getElementById("sai_overview").innerHTML = s; + + if (document.getElementById("rebuild-" + san(jso.t.uuid))) { + document.getElementById("rebuild-" + san(jso.t.uuid)). + addEventListener("click", function(e) { + var rs= "{\"schema\":" + + "\"com.warmcat.sai.taskreset\"," + + "\"uuid\": " + + JSON.stringify(san(e.srcElement.id.substring(8))) + "}"; + + console.log(rs); + sai.send(rs); + document.getElementById("dlogsn").innerHTML = ""; + document.getElementById("dlogst").innerHTML = ""; + document.getElementById("logs").innerHTML = ""; + lines = times = logs = ""; + tfirst = 0; + lli = 1; + }); } - for (n = 0; n < jso.i.contexts[ci].vhosts.length; n++) { - if (parseInt(jso.i.contexts[ci].deprecated, 10) === 0) - s += "<tr><td>&nbsp;&nbsp;</td><td class=\"l\">vhost " + (n + 1); - else - s += "<tr><td>&nbsp;&nbsp;</td><td class=\"dl\">vhost " + (n + 1); - s += "</td><td><span class=\"mountname\">"; - if (jso.i.contexts[ci].vhosts[n].use_ssl === "1") - s = s + "https://"; - else - s = s + "http://"; - s = s + san(jso.i.contexts[ci].vhosts[n].name) + ":" + - san(jso.i.contexts[ci].vhosts[n].port) + "</span>"; - if (jso.i.contexts[ci].vhosts[n].sts === "1") - s = s + " (STS)"; - s = s +"<br>" + - - "<span class=n>Total Rx:</span> <span class=v>" + humanize(san(jso.i.contexts[ci].vhosts[n].rx)) +"B</span>, " + - "<span class=n>Total Tx:</span> <span class=v>" + humanize(san(jso.i.contexts[ci].vhosts[n].tx)) +"B</span><br>" + - - "<span class=n>CONNECTIONS: HTTP/1.x:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].h1_conn) +"</span>, " + - "<span class=n>Websocket:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].ws_upg) +"</span>, " + - "<span class=n>H2 upgrade:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].h2_upg) +"</span>, " + - "<span class=n>H2 ALPN:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].h2_alpn) +"</span>, " + - "<span class=n>Rejected:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].rejected) +"</span><br>" + + + if (document.getElementById("stop-" + san(jso.t.uuid))) { + document.getElementById("stop-" + san(jso.t.uuid)). + addEventListener("click", function(e) { + var rs= "{\"schema\":" + + "\"com.warmcat.sai.taskcan\"," + + "\"task_uuid\": " + + JSON.stringify(san(e.srcElement.id.substring(5))) + "}"; + console.log(rs); + sai.send(rs); + }); + } + + aging(); + } + + + if (jso.schema == "com-warmcat-sai-artifact") { + console.log(jso); + + sai_arts += "<div class=\"sai_arts\"><img src=\"artifact.svg\">&nbsp;<a href=\"artifacts/" + + san(jso.task_uuid) + "/" + + san(jso.artifact_down_nonce) + "/" + + san(jso.blob_filename) + "\">" + + san(jso.blob_filename) + "</a>&nbsp;" + + humanize(jso.len) + "B </div>"; + + if (document.getElementById("sai_arts")) + document.getElementById("sai_arts").innerHTML = sai_arts; + + } + + if (jso.schema == "com-warmcat-sai-logs") { + var s1 = atob(jso.log), s = hsanitize(s1), li, + en = "", yo, dh, ce, tn = ""; - "<span class=n>TRANSACTIONS: HTTP/1.x:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].h1_trans) + "</span>, " + - "<span class=n>H2:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].h2_trans) +"</span>, " + - "<span class=n>Total H2 substreams:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].h2_subs) +"</span><br>"; + if (!tfirst) + tfirst = jso.timestamp; + + li = (s1.match(/\n/g)||[]).length; + + switch (jso.channel) { + case 1: + logs += s; + break; + case 2: + logs += "<span class=\"stderr\">" + s + + "</span>"; + break; + case 3: + logs += "<span class=\"saibuild\">\u{25a0} " + s + + "</span>"; + break; + case 4: + logs += "<span class=\"tty0\">" + s + + "</span>"; + break; + default: + logs += "<span class=\"tty1\">" + s + + "</span>"; + + + } + + if (!cont[jso.channel] && jso.len) + tn = ((jso.timestamp - tfirst) / 1000000).toFixed(4); + + cont[jso.channel] = (li == 0); - if (jso.i.contexts[ci].vhosts[n].mounts) { - s = s + "<table><tr><td class=t>Mountpoint</td><td class=t>Origin</td><td class=t>Cache Policy</td></tr>"; - - var m; - for (m = 0; m < jso.i.contexts[ci].vhosts[n].mounts.length; m++) { - s = s + "<tr><td>"; - s = s + "<span class=\"m1\">" + san(jso.i.contexts[ci].vhosts[n].mounts[m].mountpoint) + - "</span></td><td><span class=\"m2\">" + - san(jso.i.contexts[ci].vhosts[n].mounts[m].origin) + - "</span></td><td>"; - if (parseInt(san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_max_age), 10)) - s = s + "<span class=n>max-age:</span> <span class=v>" + - san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_max_age) + - "</span>, <span class=n>reuse:</span> <span class=v>" + - san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_reuse) + - "</span>, <span class=n>reval:</span> <span class=v>" + - san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_revalidate) + - "</span>, <span class=n>inter:</span> <span class=v>" + - san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_intermediaries); - s = s + "</span></td></tr>"; - } - s = s + "</table>"; - } - s = s + "</td></tr>"; + while (li--) { + en += "<a id=\"#sn" + lli + + "\" href=\"#sn" + lli + "\">" + + lli + "</a><br>"; + tn += "<br>" + lli++; } + + lines += en; + times += tn; + + if (!redpend) { + redpend = 1; + setTimeout(function() { + redpend = 0; + locked = document.body.scrollHeight - + document.body.clientHeight <= + document.body.scrollTop + 1; + + if (document.getElementById("logs")) { + document.getElementById("logs").innerHTML = logs; - s += "</table></td></tr>"; + if (document.getElementById("dlogsn")) + document.getElementById("dlogsn").innerHTML = lines; - } // context - s = s + "</table>"; - - document.getElementById("conninfo").innerHTML = s; + if (document.getElementById("dlogst")) + document.getElementById("dlogst").innerHTML = times; + } + + if (locked) + document.body.scrollTop = + document.body.scrollHeight - + document.body.clientHeight; + }, 500); + } + } }; sai.onclose = function(){ -// document.getElementById("title").innerHTML = "Server Status (Disconnected)"; - lws_gray_out(true,{"zindex":"499"}); +// document.getElementById("title").innerHTML = +// "Server Status (Disconnected)"; +// lws_gray_out(true,{"zindex":"499"}); - myVar = setTimeout(ws_open_server_status, 3000); + myVar = setTimeout(ws_open_sai, 10000); }; } catch(exception) { alert("<p>Error" + exception); @@ -241,9 +1165,22 @@ function ws_open_server_status() window.addEventListener("load", function() { - lws_gray_out(true,{"zindex":"499"}); + if (document.getElementById("noscript")) + document.getElementById("noscript").display = "none"; + ws_open_sai(); + aging(); + - ws_open_server_status(); + setInterval(function() { + + var locked = document.body.scrollHeight - + document.body.clientHeight <= document.body.scrollTop + 1; + + if (locked) + document.body.scrollTop = document.body.scrollHeight - + document.body.clientHeight; + + }, 500) }, false); diff --git a/assets/sai.svg b/assets/sai.svg index bf913b2..cdc6d1b 100644 --- a/assets/sai.svg +++ b/assets/sai.svg @@ -1,84 +1,52 @@ <?xml version="1.0" encoding="UTF-8"?> -<svg width="78.646mm" height="38.001mm" version="1.1" viewBox="0 0 78.646 38.001" xmlns="http://www.w3.org/2000/svg"> - <g transform="translate(195.5 -47.682)"> - <g> - <g transform="matrix(.70522 0 0 .70522 -57.63 14.056)" dominant-baseline="auto" stroke-width="4.2629" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="Sai"> - <path d="m-122.89 79.46q0 5.6894-2.8864 8.7146-2.8586 3.0251-8.2705 3.0251-5.3009 0-9.0199-2.0815v-7.4657q4.5516 2.9696 8.3538 2.9696 2.1648 0 3.2472-1.1934 1.1101-1.1934 1.1101-3.2471 0-2.026-1.3044-3.6912-1.3044-1.693-4.3018-3.7467-2.6088-1.804-3.9687-3.4414-1.3322-1.6652-2.0815-3.719-0.72159-2.0538-0.72159-4.7736 0-5.2176 2.8864-8.2705 2.9141-3.0529 7.7987-3.0529 4.6626 0 8.9644 2.6088l-2.4978 6.0502q-1.3322-0.7771-2.8031-1.3877-1.4432-0.61058-3.1084-0.61058-1.8317 0-2.8308 1.2212-0.97136 1.1934-0.97136 3.2749 0 2.0538 1.1656 3.5802 1.1934 1.5264 4.1353 3.4692 3.9687 2.6643 5.5229 5.4397 1.582 2.7753 1.582 6.3278z"/> - <path d="m-103.13 90.645-1.4709-4.2185h-0.16652q-1.6374 2.7198-3.3859 3.7467-1.7485 1.0269-4.4128 1.0269-3.4137 0-5.3842-2.5533-1.9427-2.5533-1.9427-7.1049 0-4.8846 2.6643-7.2436 2.6921-2.3868 8.0207-2.6366l4.1075-0.16652v-2.2203q0-4.3295-3.7467-4.3295-2.7753 0-6.3833 2.1648l-2.5533-5.1066q4.6071-2.9141 10.019-2.9141 4.9124 0 7.4657 2.8308 2.5811 2.8031 2.5811 7.9652v20.76zm-6.411-5.2176q2.026 0 3.2194-1.804 1.2212-1.8317 1.2212-4.8568v-2.6088l-2.2758 0.11101q-2.5256 0.13877-3.719 1.4432-1.1656 1.3044-1.1656 3.8855 0 3.83 2.7198 3.83z"/> - <path d="m-84.312 90.645h-7.3824v-30.917h7.3824zm-7.6877-38.994q0-1.8317 1.0546-2.8864 1.0824-1.0824 2.9974-1.0824 1.804 0 2.8864 1.0824 1.0824 1.0546 1.0824 2.8864 0 1.8872-1.1101 2.9419-1.1101 1.0269-2.8586 1.0269-1.8317 0-2.9419-1.0269-1.1101-1.0546-1.1101-2.9419z"/> - </g> - <path d="m-165.8 63.781a14.486 14.486 0 0 1-14.486 14.486 14.486 14.486 0 0 1-14.486-14.486 14.486 14.486 0 0 1 14.486-14.486 14.486 14.486 0 0 1 14.486 14.486z" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.4552"/> - <g transform="matrix(1.6615 0 0 1.6615 566.58 -162.55)" dominant-baseline="auto" fill="#fff" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="裁"> - <path d="m-449.06 137.17 0.72 0.73333q-0.36 0.36-0.62667 0.6-0.26666 0.24-0.52 0.42667 0.2 0.18666 0.41334 0.36 0.22666 0.16 0.46666 0.34666l-1.0933 0.93334q-0.21333-0.2-0.41333-0.4t-0.38667-0.38667l0.34667 1.12q-1.04 0.36-1.9867 0.64t-1.9733 0.58667l-0.42667-1.3733q0.28-0.0667 0.53333-0.12 0.26667-0.0533 0.53334-0.12v-0.49333q0-0.13333-0.0133-0.26667 0-0.13333 0.0133-0.26666-0.18667 0.16-0.38667 0.32t-0.41333 0.30666l-0.8-1.2267q0.73333-0.46667 1.1733-0.86667 0.45333-0.4 0.78667-0.85333h-1.4667v-1.3733h2.1867v-0.73333h-2.2133v-1.4h2.3067v-0.84h-1.92v-1.3467h1.92v-1.3067h1.48v1.3067h1.8v1.3467h-1.8v0.84h2.4533q-0.0267-0.96-0.04-1.84 0-0.89333-0.0133-1.6533h1.3333q0 0.89333 0.0133 1.7733t0.0533 1.72h3.1467v1.4h-3.0533q0.0533 0.64 0.12 1.2267 0.08 0.58666 0.18667 1.1467 0.30667-0.53333 0.56-1.0933 0.25333-0.57333 0.48-1.2l1.0533 0.73333q-0.34666 0.89334-0.76 1.6933-0.4 0.8-0.89333 1.52 0.0533 0.14666 0.0933 0.28 0.0533 0.12 0.12 0.24 0.0533 0.13333 0.13334 0.28 0.08 0.14666 0.16 0.26666 0.0933 0.12 0.17333 0.2 0.0933 0.08 0.17333 0.08 0.0667 0 0.12-0.12t0.08-0.30666q0.04-0.21334 0.0667-0.48 0.04-0.28 0.08-0.61334l1.2133 0.94667q-0.04 0.4-0.16 0.8-0.10666 0.41333-0.28 0.73333-0.17333 0.33334-0.41333 0.54667t-0.54667 0.21333q-0.49333 0-0.89333-0.28-0.38667-0.28-0.70667-0.73333-0.24-0.32-0.45333-0.76-1.0667 1.12-2.4933 1.9067l-0.68-1.16q0.82666-0.46667 1.48-0.98667 0.66666-0.53333 1.2-1.16-0.24-0.88-0.37334-1.88-0.13333-1-0.2-2.04h-2.64v0.73333h2.4667v1.3733zm3.4933-3.6267q-0.14666-0.33333-0.26666-0.6t-0.26667-0.52q-0.13333-0.26667-0.30667-0.53333-0.16-0.26667-0.4-0.61334l1.12-0.62666q0.26667 0.34666 0.44 0.61333 0.18667 0.26667 0.32 0.52 0.14667 0.25333 0.25334 0.50667 0.12 0.25333 0.25333 0.57333zm-5.0133 6.1467q-0.81333-0.85334-1.4533-1.6933-0.08 0.10667-0.12 0.14667v2.0267q0.73333-0.18667 1.5733-0.48zm0.25333-1.6q0.21334-0.17334 0.41334-0.38667t0.46666-0.53333h-1.6133q0.18667 0.25333 0.36 0.48 0.18667 0.21333 0.37333 0.44z" fill="#fff"/> - </g> - </g> - <g transform="matrix(.70522 0 0 .70522 -174.25 113.4)"> - <path d="m-4.5685-49.308 0.56358-0.22543 0.037614-1.5405 1.0144-0.41329-0.52601-1.6156 1.315-3.2312-0.45087-1.6907 0.45087-1.8786 0.71387-0.56358 0.4133-0.07515-0.52601-2.367 0.37572-3.1936 1.0896-1.1272 0.63873-0.90173 0.90173-2.4798 1.5405-1.6532 3.2688-1.578 2.7803-1.9538 1.578-0.78902v-1.2023l0.26301-1.6907 0.33815-0.45086-0.63873-1.3526 0.15028-0.86416-2.1792-2.2168-0.33815-0.71387-0.86417-0.48844-0.22543-0.86416-0.30057-0.33815 0.56358-0.41329 0.63873 0.15029 0.075149-0.6763 0.60116-0.263 0.18786-0.71387 0.56359-0.52601 0.22543-0.60115 0.71387 0.07515 0.26301-0.78902 0.45087-0.26301 0.63872 0.18786 0.48844-0.11272 0.22543-0.63873 0.4133-0.30057 0.37572 0.263 0.48844 0.03753 0.56358-0.22544 0.18786-0.30057 0.90173 0.03754 0.45086 0.22543 0.60115-0.18786 0.78902 0.03754 0.07515 0.263 0.18786 0.22544 0.37572-0.03753 0.63873-0.11272 0.48844 0.71387 0.60116 0.07515 0.37572-0.15029 0.22543 0.48844 0.37572 0.18786 0.4133 0.11272 0.18786 1.0144 0.26301 1.5405-0.11272 2.4046-0.78902 1.0144v0.86416l-0.6763 1.7659 0.56359 1.315 0.22543 1.7659 0.07515 1.6532-0.22543 1.0144 0.9393 1.315 1.315 0.78902 1.9913 1.6907 1.578 2.8179 0.78902 1.9162 0.56359 1.5029 2.1792 6.237 2.7428 2.104 1.315 1.052 1.2399 0.56358 1.7659 1.9913 1.2399 0.90173-0.52601 1.1272-3.1185 1.841 0.07515 0.75144 0.63873 0.03757 7.4393 8.5289-32.876 0.9393-2.0289-1.6907-5.4855-6.3873-6.9884-1.578" fill="#fff"/> - <g> - <g stroke="#000" stroke-width=".2104px"> - <path d="m10.362-76.843c-0.6376 0.593-1.2667 1.2086-2.0611 1.5899-0.85296 0.55731-1.7577 1.026-2.7144 1.3777-1.1515 0.44703-2.249 1.0205-3.3133 1.6421-0.44533 0.53673-0.99887 1.0336-1.2932 1.6752-0.27915 0.68845-0.62957 1.3669-0.64093 2.1259-0.1947 1.4012-0.14246 2.8215-0.082576 4.2303 0.21842 0.95772-0.75295 0.07728-0.58564-0.23317-0.052087 0.6363-0.11151 1.2852 0.11339 1.8983 0.059086 0.7035 0.39732 1.3156 0.76201 1.9026 0.25979 0.48237 0.42621-0.49485 0.049861-0.64401-0.42246-0.60127-0.55769-1.3367-0.75556-2.0298-0.28092-0.84004 0.83193 0.51232 0.83761-0.33999-0.14071-1.1203-0.16543-2.2511-0.21885-3.3779 0.21525-0.83166 0.14769-1.7681 0.62576-2.506 0.50937-0.79208 0.81385-1.7764 1.6779-2.2591 0.6644-0.5814 1.4435-0.98426 2.245-1.3397 1.5925-0.75491 3.1215-1.6375 4.5613-2.6539 0.30997-0.30064 0.52139-0.71619 0.79279-1.0584z"/> - <path d="m0.18632-67.864c-0.81026 0.84595-1.4436 1.8796-1.6735 3.0386-0.51902 1.9602 0.011849 4.0897 1.1234 5.7458 0.31657 0.3763 0.14313 1.6027-0.34404 0.82917-0.2199-0.75017 0.13205-1.7018-0.51972-2.2924-0.62031-0.5222-1.0577 0.40297-1.1589 0.91316-0.19707 0.6876-0.41149 1.4066-0.21395 2.1225 0.20041 0.84804 0.5089 1.6733 0.97565 2.4113 0.26194 0.48086 0.48044 1.0205 0.42332 1.579-0.50432-0.21529-0.23982-1.2098-0.58861-1.3279-0.27578 0.69922-0.29345 1.4961-0.18148 2.2345 0.12681 0.54578 0.38604 1.3016 1.05 1.329 0.76984-0.02128 1.1958-0.78769 1.5335-1.3778 0.72437-1.0482 1.2687-2.2169 1.6088-3.4431 0.10005-0.36142 0.69729-0.98271 0.51575-1.1804-0.7836 0.46497-0.70296 1.4961-1.0702 2.2268-0.42334 1.2014-1.1749 2.2721-2.0357 3.195-0.58201 0.42709-1.3724-0.18352-1.3453-0.83802-0.087069-0.26578-0.22817-1.2234 0.1359-1.1088 0.063778 0.36351 0.36992 1.1657 0.80336 0.65461 0.05495-0.56818-0.36219-1.0758-0.51997-1.6121-0.38228-0.98299-1.0321-1.8824-1.0801-2.9702-0.079364-0.85336 0.080071-1.7484 0.55252-2.4735 0.63943-0.35808 0.95492 0.51504 0.89488 1.0204-0.18909 0.69113 0.085575 1.3851 0.64934 1.8235 0.44087 0.44464 0.37855-0.703 0.34219-1.0142-0.091515-0.8379-0.70685-1.473-0.94405-2.2637-0.43246-1.196-0.64799-2.5185-0.39039-3.7755 0.30779-1.2122 0.74608-2.4085 1.4575-3.4458z"/> - <path d="m-2.311-56.386c-0.60146 1.04-1.1795 2.1313-1.3514 3.3358 0.12534 0.5234 0.20219 1.0726 0.52979 1.5188 0.1333 0.25903 0.53914 0.91641 0.49559 0.91695-0.31121-0.31471-0.5362-1.3919-1.0943-1.0112-0.37426 0.36987-0.53485 0.84441-0.40114 1.356 0.094346 0.37018-0.31806 1.3119 0.38552 1.0114 0.088955-0.48009-0.036024-0.99492 0.05654-1.49-0.050657-0.42712 0.3909-0.80055 0.59223-0.26536 0.23558 0.27655 0.60697 0.891 0.74693 0.9615-0.04167-0.49354-0.23208-0.94639-0.50273-1.3562-0.36782-0.51652-0.4449-1.1581-0.41411-1.7739 0.1508-1.1097 0.55654-2.1644 0.95708-3.2037z"/> - <path d="m1.5678-60.557c1.1022-1.2256 3.0919-1.932 4.6303-1.1525 1.3659 0.85271-0.54293 1.4916-0.93294 2.2761-0.90662 1.1268-0.91908 2.6208-1.1632 3.9833-0.20937 1.3352-0.33617 2.6847-0.10331 4.0274 0.33043 0.925-0.43678 1.8083-1.2909 1.1009 0.42668-0.39292 1.4893-0.25174 1.1496-1.2612-0.38344-2.3073 0.12918-4.6362 0.56823-6.8992 0.26637-0.77922 1.5935-2.0385 1.5693-2.182-0.994 0.53559-1.5711 1.6135-2.0908 2.579-1.0689 2.5699-1.3742 5.3949-1.3554 8.1585 0.48739 0.11492 2.3884-0.06136 1.4636 0.67869-0.62676-0.1629-2.1187 0.24074-2.1601-0.21389 0.59769-1.3403 0.15561-2.8717 0.56796-4.2699 0.26798-1.8623 0.92779-3.6612 1.7769-5.3334 0.31033-0.98244 1.6057-1.3924 1.8238-2.2665-1.0433-0.73893-2.4494-0.27516-3.5043 0.22784-0.3286 0.15951-0.64567 0.34267-0.94855 0.54681z"/> - <path d="m21.228-74.931c1.2521 1.6422 3.8565 1.934 4.3543 4.2342 0.86907 2.0943 2.0102 4.0694 2.5779 6.2768 0.52362 1.4919 0.90865 3.0742 1.6626 4.4573 1.3659 1.0954 2.4067 2.4233 3.8965 3.4146 1.668 0.5471 2.8014 1.833 3.9259 3.1178 1.1368 0.357 1.9138 2.1614 0.11136 1.6355-3.0916-0.11293-6.3276-1.0599-9.3341 0.03857-1.7099 0.46646-1.9685 2.3293-3.0544 3.4025-1.1952-0.66559-2.4806-2.5143-2.0756-4.079-0.15126-1.0739 2.0214-3.3815 2.1548-1.6891-1.8428 0.16934-2.2261 2.6364-1.2496 3.9094 0.58909 1.409 1.5587 0.6725 1.7415-0.45225 2.0893-2.1539 5.3451-2.2401 8.1382-1.8891 1.0773-0.05969 3.4863 0.65921 3.7067-0.08377-1.2467-1.1783-2.1159-2.8625-3.9209-3.3078-0.71054-0.42339-4.7117-1.4212-3.563-1.1937 0.98762 0.21534 1.9752 0.43069 2.9629 0.64604-1.3695-1.3789-3.3164-2.147-4.6951-3.3235-0.34356-0.70868-1.3731-3.5053-0.49569-1.4653 0.07237 0.72896 1.5021 2.7399 1.3868 1.0072-0.80616-2.2107-1.573-4.4398-2.3597-6.6595-0.7712 1.0314 0.15839 2.5745-0.2694 3.6001 0.91192-1.3463 0.31563 1.7258 0.19603 2.3335-0.91726 2.2674-2.9704 3.8162-4.0867 5.9716-1.0233-1.3416 0.25936-4.0824 0.78813-5.8017 0.89423-3.2018 0.51224-6.5781 0.12216-9.8295 0.75147 3.1652 0.99733 6.5285 0.29291 9.7213-0.59913 1.6594-1.6277 3.2972-1.4012 5.1287 1.2469-1.0146 2.4551-3.0605 3.5133-4.5868 1.0181-1.2156 0.55477-2.2629-0.24606-1.296 0.947-2.5886 0.78068-5.4999-0.41629-7.9768-0.55244-1.6308-1.6098-3.0554-3.1387-3.8854-0.45166-0.38892-1.2634-0.65753-1.2256-1.3758z"/> - <path d="m10.601-58.007c-1.0804 1.5941-2.1608 3.1881-3.2412 4.7822 0.82316 0.11042 0.94795-1.3626 1.5673-1.7999 0.44547-0.56234 1.0952-1.8444 1.4136-1.959 0.0528 1.8435-0.0233 3.6987 0.26735 5.5259 0.09811 0.66218-0.38044 2.4467-1.1878 1.7401-0.06839-0.97395 0.65888-1.8342 0.43531-2.8262-0.034911-1.1303-0.069746-2.2605-0.10467-3.3908-0.47609 0.7032-0.14728 1.5981-0.27093 2.3858 0.016143 1.0212 0.022426 2.0563-0.31352 3.034-1.0497-0.2733-0.026958-1.8734-0.31852-2.7034-0.046361-0.48496 0.034911-1.7945-0.35679-0.74935-0.28965 0.54435 0.18643 1.926-0.38783 1.968-0.38554-0.87873-1.094-1.6589-1.3904-2.542 0.22764-1.7503 0.20538-3.5241 0.1787-5.2834 0.043896-0.75864-0.55411-1.0391-0.98571-0.34173-0.56455 0.34821 0.59085 0.81491 0.15105 1.4404-0.074672 0.50785-0.069742 1.7049-0.13389 1.749-0.24849-0.67948 0.50507-2.1155-0.5117-2.0955-0.68319 0.04539-0.9726 1.0898-0.025367 0.76567 0.69374-0.72623-0.10487 0.73149-0.50592 0.02356-0.6542-0.06704-0.25865 1.264-0.52775 1.7579-0.18648 1.4679-0.44832 2.9449-0.46706 4.4217 0.15632 0.89019 0.012247 1.9469 0.52706 2.7057 0.026402-1.3009-0.26216-2.6042-0.19485-3.907 0.14418-1.1775 0.28837-2.355 0.43255-3.5325 0.12729-0.54847 1.2649-1.817 0.87689-0.55609-0.44511 0.91935-0.2545 2.0398-0.81568 2.8725 0.13014 1.7872 0.26027 3.5744 0.39041 5.3616 0.026561-1.718 0.053122-3.4361 0.079705-5.1541 0.20476 1.3182 0.58814 2.609 0.8716 3.897-0.14381 0.41744 0.13809 1.8904 0.2029 0.80265-0.029741-0.95058-0.00795-1.9267-0.42193-2.8082-0.52522-1.1977-0.19336-2.5198-0.23672-3.7794-0.11788-1.0363 0.85498 1.4745 0.6937 0.0344 0.037535-1.171 0.075069-2.3421 0.1126-3.5131 0.68349 0.13125 0.16234 1.4747 0.31586 2.0782-0.043817 1.0619-0.11555 2.1243-0.27293 3.176 0.48068 1.1214 1.3335 2.0487 1.8073 3.1737 0.41138 0.60886-0.18048 2.0516 0.907 1.9433 0.73407 0.18613 1.6904 0.16659 1.5724-0.8561 0.37634-1.2828-0.05892-2.5932-0.06975-3.8933-0.09059-1.3143-0.03443-2.632-0.06227-3.948z"/> - </g> - <path d="m31.855-55.589c0.46866 0.27651 1.405 0.53761 1.4244 1.0557-0.6002 0.21825-1.1888-0.20853-1.7968-0.18897-0.63351-0.23262-1.615 0.40019-0.90335 0.99996 0.65986 0.61161 1.6384 0.42649 2.4601 0.49316 0.86268 0.01401 1.7255-4.21e-4 2.5882 0.0047 0.29536 0.25905 1.1507 0.01039 1.1243-0.07158-1.2914-0.30985-2.6271-0.27913-3.9442-0.25498-0.64188-0.04423-1.2943-0.2016-1.8605-0.50911-0.31977-0.58514 0.77638-0.273 1.0328-0.07476 0.55992 0.15922 1.7815 0.33235 1.8147-0.51458-0.05249-0.53306-0.65843-0.58766-1.0415-0.8013-0.2688-0.08516-0.52712-0.34771-0.81174-0.16477l-0.08634 0.02657z"/> - <g stroke="#000" stroke-width=".2104px"> - <path d="m5.2076-71.61 0.47821 2.0988 0.71733 1.966 1.1158 1.5675 0.053122 0.02657 0.31881-0.21254-1.6206-2.4708-0.63762-1.9926z"/> - <path d="m10.92-62.789 1.2487 0.77046 0.95644 0.39851 0.31881-0.39851-1.2487-0.53135-1.4081-0.87673z"/> - <path d="m22.237-65.472c-0.36018-0.1089-0.74695-0.3089-1.0764-0.03301-0.38657 0.09546-0.43059 0.32102-0.22246 0.66153 0.23539 0.826 0.50666 1.6406 0.86622 2.4212 0.34945 0.93258 0.64118 1.9217 1.2766 2.7066 0.29974 0.1007-0.15872-0.65782-0.20282-0.89787-0.23578-0.64567-0.60087-1.252-0.78697-1.9086-0.10792-0.71082-0.30274-1.4333-0.1154-2.15 0.04716-0.27706 0.11657-0.55597 0.26121-0.79983z"/> - <path d="m2.1523-47.593 7.0935 7.9968 0.47821 0.18597 33.156-1.0627-7.9171-8.6876-1.1424 0.05314 1.0096 0.10627 7.4123 8.2625-32.28 0.85016 0.66419-0.29224 0.10627-0.55792-0.58448-0.66419-0.53135-0.50478-5.6589-6.2434-0.95643-0.23911-0.50479 0.26568-0.18597 0.45165 0.45165-0.45165 0.77046 0.02657 0.34538 0.26567 6.084 6.7216-0.31881-0.05314-0.63762 0.31881 0.053122 0.66419z"/> - <path d="m3.9589-48.15 20.776-0.37195 0.42508 0.45165-20.723 0.39851z"/> - <path d="m7.0939-69.909 0.37195-0.26568 0.42507 0.13284 0.25079 0.74428-0.76265 0.33856 0.19104 0.47651 0.74479-0.3324 0.39963 1.2173-0.10627 0.34538 0.34538 0.58449-0.45165 0.05313-0.42507-0.05313-1.0361-2.9224z"/> - <path d="m9.1927-63.985 1.3815 3.8789 0.31881-0.02657 0.2391-0.18597-1.169-3.6397-0.55792-0.58449 0.10627 0.74389z"/> - <path d="m6.2171-61.753c-0.16891-0.40905-0.47081-0.7817-0.46122-1.2421-0.094576-0.4899 0.47562-0.775 0.44275-1.2485 0.044055-0.43146 0.40462-0.75373 0.78891-0.91004 0.11172-0.54615 0.58351-0.84258 1.0029-1.0926 0.018211-0.47036 0.36852-0.39281 0.72065-0.31217 0.2579 0.06633 0.43404 0.07125 0.23247-0.20756-0.067037-0.17818-0.40219-0.79636-0.10295-0.37361 0.24537 0.2948 0.43435 0.75249 0.87672 0.47822 0.31972-0.04036 0.21434 0.57947 0.45727 0.77789 0.24102 0.28096 0.25767 0.64678 0.30782 0.99516 0.07944 0.48383 0.107 0.97337 0.14484 1.4615-0.2966 0.09917-0.43825-0.10236-0.54889-0.33071-0.1447-0.23514-0.28941-0.47028-0.43411-0.70542-0.5092-0.38986 0.16707 0.84354-0.35204 0.43172-0.15728 0.08386 0.126 0.62972-0.00477 0.90512-0.030616 0.43413-0.106 0.85805-0.39831 1.1988-0.11628 0.05612-0.18203-0.50362-0.19311-0.70058-0.021471-0.14297-0.016938-0.73582-0.069105-0.31507-0.21695 0.41641 0.14658 1.2185-0.39734 1.304-0.43335-0.35406-0.099284-0.94736-0.32176-1.4001-0.020994-0.28655-0.27905-0.34096-0.16567-7e-3 0.081598 0.3718 0.18597 0.84263-0.13001 1.1365-0.12723 0.15268-0.31938 0.46828-0.22612 0.09387-0.03992-0.25422 0.24064-0.90945-0.23058-0.7675-0.18094 0.01085-0.74234-0.16117-0.72832-0.08871 0.25637 0.2076 0.77543 0.24222 0.86713 0.51922-0.022585 0.24836-0.045169 0.49671-0.067754 0.74506 0.29451 0.33343 0.49621-0.70642 0.76138-0.33911 0.13586 0.15731 0.27172 0.31462 0.40759 0.47194 0.097415-0.14169 0.19483-0.28339 0.29225-0.42508 0.62882 0.22426 0.73622-0.70506 0.76187-1.1606 0.030696-0.38077 0.078966-1.0835 0.35728-1.2006 0.2366 0.28522 0.25611 0.87164 0.68743 0.88669 0.44359 0.165 0.14984-0.52652 0.22704-0.78991-0.02465-0.43907 0.0509-0.89745-0.14265-1.3081-0.13901-0.45492-0.25732-0.92128-0.66094-1.2162-0.23804-0.30818-0.55645-0.50839-0.91425-0.64994-0.46057-0.39149-0.10372 0.65395-0.61139 0.4074-0.48459-0.17814-0.5764 0.2572-0.714 0.62101-0.40062 0.01938-0.61193 0.31243-0.74739 0.65741-0.11886 0.412-0.62051 0.51851-0.78162 0.92353-0.13213 0.38896-0.22491 0.80471-0.42049 1.181-0.16681 0.32014-0.13268 0.74092 0.031014 1.0463 0.15245 0.18292 0.30487 0.36588 0.45734 0.54877z"/> - </g> - </g> - <path d="m10.627-60 0.29225 0.82359 0.3985 0.39851 0.13284-0.37195v-0.66419l-0.26568-0.47822-0.31881 0.1594z" fill="#fff" stroke="#000" stroke-width=".2104px"/> - <g> - <g stroke="#000" stroke-width=".2104px"> - <path d="m17.083-49.771c-0.13668-1.7541-0.27353-3.5173-0.63951-5.2407-0.08891-0.46665-0.38607-1.0545-0.33217-1.4494 0.64456-0.22536 1.4739-0.46639 2.0434 0.05621 0.51258-0.38656 1.3693-0.55055 1.8565-0.0417 0.23098 0.43194 0.39869 0.90586 0.60058 1.3561 0.31178 0.71927 0.26766 1.5162 0.35508 2.2799 0.07293 1.0398 0.08926 2.0845 0.02441 3.1252 0.47742 0.28387 1.131-0.02722 0.82712-0.61098-0.04246-1.2555 0.03221-2.5319-0.28326-3.7604-0.07078-0.55154-0.22496-1.097-0.56018-1.5478-0.19038-0.32258-0.38075-0.64516-0.57112-0.96774 0.70847 0.12398 1.4169 0.24797 2.1254 0.37195 0.34623-0.59303-0.57857-0.51371-0.93059-0.5367-0.47236-0.102-0.95558-0.16277-1.4423-0.18131-1.1025-0.05162-2.2089-0.01738-3.3102 0.01378-1.4715 0.30648-2.7975 1.0484-4.1403 1.693-0.6247 0.31286-1.2425 0.64075-1.8397 1.0038-0.06735 0.47169 0.38642 0.55702 0.642 0.22404 0.40962-0.14878 0.7668-0.75219 1.2136-0.61674-0.04286 1.7072-0.08578 3.4145-0.12868 5.1217 0.54945 0.18768 0.77742-0.43499 0.67-0.86616-0.09309-0.60134 0.0089-1.199-0.0064-1.8022 0.0091-0.91715 0.01813-1.8343 0.02727-2.7514 0.26568-0.16826 0.53135-0.33652 0.79702-0.50478-0.09885 0.92828-0.1773 1.8706-0.0055 2.7954 0.10716 1.0423 0.21011 2.085 0.29765 3.1291 0.659 0.07775 0.36318-0.74785 0.28599-1.1155-0.05693-0.40997-0.04493-0.82619-0.11544-1.238-0.11339-0.99939-0.21841-2.0093-0.11994-3.0147-0.15161-0.27514 0.15822-1.2715 0.41897-0.67473 0.31027 1.7573 0.64872 3.5136 0.80291 5.2934 0.05877 0.20557-0.01399 0.92823 0.2642 0.67235 0.15426-0.6802-0.12952-1.3616-0.09606-2.0466-0.07165-1.4451-0.37977-2.8608-0.6436-4.279 0.28153-0.10878 0.81117-0.44765 0.72963 0.12761 0.25229 1.571 0.64894 3.1292 0.67022 4.7283 0.12969 0.52898-0.12806 1.1573 0.21827 1.6224 1.3469-0.0429 2.6938-0.08579 4.0408-0.12869-0.07061-0.50968-0.856-0.10666-1.2224-0.23911-0.8412-2e-6 -1.6824-2e-6 -2.5236 0z"/> - <path d="m10.574-76.87c0.1674 0.45265 0.45143 0.88074 0.38364 1.3829-0.0064 0.39417 0.02712 0.78411 0.12114 1.1676 0.32877-0.0713 0.67904-0.09664 0.91401-0.36847 0.19625-0.18544 0.53339-0.4778 0.58456-0.04097 0.04954 0.43758 0.09907 0.87516 0.14861 1.3127 0.2766 0.18483 0.52538 0.44735 0.81839 0.58633 0.34173 0.01148 0.69876 0.08826 0.97975-0.15906 0.55338-0.25388 1.0235-0.66348 1.5619-0.93184 0.49753-0.11775 0.9802-0.37243 1.5024-0.26252 0.35074-0.15626 0.54747 0.34314 0.15468 0.34167-0.30162 0.04769-0.64015 0.01809-0.82513 0.30998-0.3206 0.31224-0.70817 0.5752-0.84156 1.0252-0.126 0.31014-0.27614 0.60025-0.589 0.75533-0.34229 0.15913-0.42642 0.36327-0.13085 0.6336 0.19865 0.26262 0.61071 0.24378 0.90044 0.3863 0.51512 0.16422 1.0619 0.28367 1.5894 0.35678 0.51846-0.17587 1.079-0.26844 1.5712-0.4963 0.20854-0.19383 0.59505-0.30365 0.47074-0.65458-0.02258-0.60426-0.03825-1.2089-0.09541-1.8112-0.31074-0.18326-0.60068-0.44842-0.92451-0.58014-0.36421 0.21212-0.31508-0.49922 0.02195-0.31863 0.38941 0.0074 0.77883 0.0147 1.1682 0.02204 0.10627-0.27453 0.21254-0.54906 0.31881-0.82359 0.12688 0.74355 0.24096 1.4904 0.53443 2.1907 0.27952 0.9189 0.67532 1.7928 1.0596 2.6711-0.40182 1.1236-0.76147 2.2662-1.1897 3.3779-0.62857 1.1321-1.2972 2.2497-1.9009 3.3909-0.11886 0.75029-0.31732 1.4945-0.38541 2.2487 0.04278 1.4133 0.08565 2.8265 0.12848 4.2398-0.21084 0.1666-0.34087 0.85748-0.6289 0.52471-0.86053-0.43373-0.99358-1.2828-1.0288-2.1565-0.29242-1.7275-0.46769-3.4827-0.97255-5.1658-0.33357-1.3359-0.72305-2.6554-1.1689-3.9582-0.15604-0.39895-0.56315-0.64973-0.82358-0.98707 0.02656-0.54021 0.05312-1.0804 0.0797-1.6206 0.34538-0.16826 0.69075-0.33652 1.0361-0.50478-0.03952-0.38747-0.18982-0.53358-0.52634-0.27394-0.7484 0.34944-1.5034 0.68268-2.2898 0.93812-0.46936-0.21254-0.93872-0.42508-1.4081-0.63762-0.21014-0.68584-0.54604-1.3452-0.53725-2.0771-0.05129-0.60213-0.15768-1.2063 0.0071-1.8005 0.08719-0.74145 0.28574-1.4835 0.21131-2.2329z"/> - <path d="m7.1736-57.662c0.65998 0.03763 1.3861-0.04285 1.9193-0.46141 0.42444-0.4283 0.68989-0.982 1.1299-1.3973 0.24412-0.24896 0.21384-0.39302-0.06393-0.52544-0.09282-0.20005-0.27843-0.7759-0.33014-0.23052-0.14106 0.60903-0.60483 1.0559-0.84222 1.6202-0.22716 0.54596-0.99114 0.48428-1.317 0.07077-0.36365-0.23603-0.44801-0.7621-0.73655-0.99225 0.027993 0.65604 0.034831 1.3127 0.054711 1.969 0.061869-0.01769 0.12434-0.03546 0.18588-0.05312z"/> - <path d="m16.895-72.067c0.28542-0.01096 0.59182 0.02001 0.83628-0.15946 0.16899-0.10844 0.44311-0.24426 0.59557-0.04133 0.14574 0.28507 0.51518 0.09185 0.7273 0.26356 0.19762 0.1496 0.31056 0.28146 0.01153 0.36074-0.23112 0.1282-0.45371 0.27237-0.66178 0.43551-0.09485 0.23912-0.40923 0.29348-0.62596 0.21509-0.09909-0.29206-0.35018-0.41336-0.5926-0.58069-0.10752-0.15645-0.19136-0.33066-0.29033-0.49342z"/> - <path d="m18.05-74.81c-0.08454-0.09393-0.16908-0.18786-0.25361-0.28179-0.10959-0.0407-0.21917-0.0814-0.32876-0.12211-0.09079 0.05949-0.1816 0.11898-0.2724 0.17847-0.026 0.08488-0.09657 0.16368-0.07483 0.25756 0.02736 0.05084 0.0369 0.11289 0.07721 0.15617 0.04254 0.03488 0.08957-0.01633 0.13486-0.0201 0.15839-0.04981 0.31962-0.09021 0.48306-0.11954 0.07818-0.01622 0.15632-0.03243 0.23446-0.04865z"/> - <path d="m19.346-74.679c-0.047-0.07827-0.09393-0.15655-0.1409-0.23483 0.03244-0.0449 0.04406-0.12847 0.08983-0.14853 0.0734 0.0088 0.14676 0.01762 0.22014 0.02643 0.04246 0.03123 0.11128 0.05531 0.13738 0.091-0.03643 0.06673-0.07277 0.13346-0.10919 0.20019-0.06576 0.02191-0.13151 0.04383-0.19726 0.06575z"/> - </g> - <path d="m14.922-77.816c0.33268-0.24547 0.70482-0.38378 1.1181-0.39454 0.31969-0.05739 0.63863 0.03392 0.95772 0.05638 0.08141 0.10645 0.16282 0.21291 0.24422 0.31936 0.37017 0.03992 0.85554-0.1887 1.1427 0.08209 0.0055 0.4091 0.49512 0.55738 0.37213 1.0029 0.02107 0.29898 0.04215 0.59797 0.06314 0.89695 0.13461-0.40975 0.01678-1.0268 0.25268-1.3143 0.36246-0.05011 0.72292-0.11709 1.053-0.28251-0.33489 0.04004-0.69951 0.22555-1.023 0.11399-0.32436-0.11342-0.60294-0.65288-0.28788-0.90861 0.29968-0.19958 0.83088-0.34808 1.1333-0.11061 0.1425 0.12294 0.26097 0.58686 0.23943 0.12396-7.96e-4 -0.2648 0.14685-0.70033-0.27441-0.555-0.42608-0.05763-0.82142-0.11623-1.1701 0.21098-0.2653 0.29258-0.59998 0.55329-1.008 0.57986-0.40832 0.15033-0.60139-0.16184-0.83691-0.41542-0.32238-0.23904-0.757-0.2827-1.1437-0.22147-0.49101 0.10106-0.99275 0.05388-1.49 0.06455 0.37572 0.05636 0.75144 0.11272 1.1272 0.16907-0.31035 0.19139-0.67633 0.1697-1.0263 0.17286-0.26161 0.04994-0.64607-0.19337-0.76593-0.19484 0.17784 0.36702 0.661 0.32278 0.92907 0.39158-0.12843 0.10486-0.43749 0.23752-0.06529 0.22544 0.26286 0.27246 0.53298 0.54894 0.85139 0.7541 0.35748 0.01504 0.70391 0.11556 1.0534 0.17467 0.40494-0.04561 0.80539-0.24986 0.89617-0.67353 0.07054-0.19232-0.30035-0.48775-0.18767-0.10445-0.15901 0.30493-0.55493 0.58344-0.90947 0.45075-0.45921-0.02413-0.86536-0.20277-1.2036-0.51497l-0.02068-0.04961-0.02068-0.04959z"/> - <g stroke="#000" stroke-width=".2104px"> - <path d="m16.162-77.938-0.12211 0.18786 0.047 0.15029 0.18786 0.07515 0.30058-0.02818 0.12211-0.19725-0.14089-0.25361-0.19726-0.0094z"/> - <path d="m19.309-78.182-0.18786 0.18786 0.02815 0.15029 0.17846 0.14089h0.17846c0.03753 0 0.16908-0.10333 0.16908-0.10333l0.03753-0.22543-0.19726-0.18786z"/> - <path d="m14.001-80.343-0.16907 0.45087-0.0094 0.38511 0.56358 0.19725 0.69508-0.06575 0.4133 0.07515 0.57298 0.12211 0.48844 0.03757 0.42269-0.06575 0.24422-0.38511-0.03762-0.21604-0.44147 0.263-0.55419 0.03757-1.146-0.36633-0.6763-0.47904-0.34754-0.18786z"/> - <path d="m19.055-79.432 0.23483 0.39451 0.26301-0.31936 0.5354-0.24422 0.25361 0.23483 0.21605 0.15029 0.46025-0.50722 0.13151-0.29118-0.76084-0.69508-0.49783-0.47904 0.05638 0.58237-0.03762 0.42269-0.2724 0.2724 0.03753 0.21604-0.1409 0.25361-0.20665-0.28179-0.46025-0.44147z"/> - <path d="m22.22-71.194 0.263-2.0571-1.3432-1.4559-0.21604 0.15029 0.13151 0.49783 0.3945 0.263 0.12211 0.43208-0.25361 0.12211 0.14089 0.49783 0.33816 0.04697-0.05638 0.72326 0.15968 0.58237 0.28179 0.5354z"/> - </g> - <path d="m11.174-79.497c-0.2498-0.57825-0.54942-1.1467-0.76862-1.731-0.0092-0.30427 0.06776-0.64357 0.0064-0.92679-0.72293-0.68585-1.4458-1.3717-2.1688-2.0575-0.040875-0.22659 0.12007-0.56225-0.16835-0.65509-0.22347-0.13334-0.44792-0.26576-0.53553-0.52662-0.143-0.27888-0.38734-0.53272-0.3002-0.86846 0.12542-0.3348-0.23409-0.3102-0.45803-0.38609-0.03157-0.19501-0.10062-0.53129 0.17331-0.61763 0.20789-0.24508 0.54732-0.14316 0.80033-0.02702-0.2053-0.22848-0.14709-0.44643 0.047078-0.65896 0.10946-0.20948 0.56422-0.07133 0.472-0.40612 0.052008-0.21744 0.054552-0.56305 0.30588-0.63124 0.16664-0.13145 0.59458-0.09614 0.52124-0.38075-0.10231-0.29427 0.12509-0.43183 0.34188-0.57443 0.17823-0.26176 0.43741 0.03221 0.65974 0.0055 0.3338-0.03618 0.32752-0.26291 0.32522-0.52381 0.16413-0.15782 0.2328-0.49165 0.51482-0.43206 0.2548-0.08762 0.45866 0.06267 0.68972 0.07825 0.19511-0.06553 0.64153 0.08885 0.52762-0.24708-0.06092-0.25234 0.20499-0.45357 0.38679-0.57504 0.2392-0.07579 0.47919-0.20837 0.71508-0.05066 0.16392 0.21473 0.36386 0.22243 0.61785 0.12997 0.35081 0.07913 0.26883-0.30754 0.45386-0.40952 0.28523-0.05766 0.57388-0.17563 0.84669-0.01296 0.2095 0.13334 0.42262 0.1773 0.68183 0.15713 0.25897 0.047 0.35982-0.36286 0.64167-0.25897 0.29289-0.0338 0.64617 0.12933 0.69174 0.4338-0.08836 0.26536 0.40427 0.10645 0.57248 0.18444 0.19454-0.14269 0.40921-0.23428 0.66245-0.19957 0.47391-0.04047 0.24854 0.78199 0.75889 0.7085 0.32373 0.10162 0.65635-0.28953 0.95376 0.07149 0.27377 0.21945-0.03945 0.82256 0.44249 0.68877 0.31118-0.08327 0.42051 0.15559 0.52498 0.40221 0.07978 0.6912 0.09822 1.388 0.26976 2.067 0.22894 0.76173 0.08507 1.5421 0.01789 2.316 0.02736 0.37824-0.35185 0.58876-0.53737 0.87918-0.28566 0.22447 0.11762 0.56197 0.0079 0.85207-0.0097 0.6801-0.23312 1.3043-0.58087 1.8842-0.05837 0.46933-0.40983 0.44132-0.77199 0.39445-0.92666-0.037-1.8574-0.08729-2.7824-0.09585-0.78481 0.21567-1.5883 0.38356-2.3614 0.6292-0.39591 0.21273-0.79183 0.42546-1.1878 0.63819-0.20969 0.59987-0.48374 1.1825-0.54479 1.8222-0.03436-0.26491 0.01567-0.5756-0.07022-0.81254-0.22575-0.14887-0.4211-0.38329-0.71827-0.33118-0.40947 0.03246-0.33854-0.08054-0.03619-0.26359 0.36077-0.29748 0.74349-0.55822 1.1847-0.72105 0.70137-0.30015 1.3662-0.70281 2.1245-0.84307 0.57714-0.13328 1.1475-0.33214 1.7288-0.42484 0.97512 0.04875 1.9502 0.09752 2.9254 0.14627 0.14308-0.47736 0.33132-0.94991 0.4468-1.4302-0.02187-0.36636-0.10634-0.74508-0.08966-1.1038 0.2184-0.39588 0.38558-0.82976 0.75465-1.1139 0.25666-0.13816 0.2046-0.40028 0.19362-0.64592-0.01558-0.7166-0.03118-1.4332-0.04676-2.1498-0.08767 0.37572-0.17534 0.75145-0.26301 1.1272-0.25809-0.13586-0.35586-0.32592-0.21571-0.59641 0.09695-0.40235 0.19391-0.8047 0.29086-1.207-0.17805-0.07889-0.43074-0.46405-0.55932-0.34001-0.13288 0.29214-0.3085 0.575-0.24847 0.90893 0.04318 0.37437-0.06935 0.72848-0.16907 1.0842-0.35634 0.07894-0.19079-0.32666-0.23011-0.54205 0.0024-0.66467-0.03284-1.33 0.0048-1.9941-0.15432-0.11563-0.51065-0.3241-0.54353-0.38392 0.25178 0.10096 0.60621 0.06068 0.71346 0.3605 0.11767 0.22775 0.25707 0.42334 0.54034 0.40593 0.18623 0.06878 0.56602-0.17426 0.48972-0.36499-4e-3 -0.296-0.31346-0.38351-0.50202-0.16966-0.25176 0.01209-0.47261-0.07515-0.6604-0.24238 0.14266-0.16769 0.17152-0.56176-0.0427-0.61421-0.26935-0.05702-0.34488 0.35387-0.62711 0.17725-0.23085 0.0079-0.47687-0.1523-0.53905-0.35283 0.14955-0.27095 0.11957-0.46609-0.20473-0.53695-0.56491-0.08081-0.533 0.63672-0.07046 0.7256-0.28348 0.05304-0.49062-0.02362-0.57045-0.31668-0.06314-0.29734-0.40246-0.24418-0.48366 0.01447-0.1564 0.19959-0.48064 0.11379-0.71031 0.1888-0.16145 0.08648-0.53915-0.35085-0.32143-0.38233 0.2175-0.17098 0.48324 0.12627 0.68375-0.09047 0.27625-0.01121 0.09152-0.54312-0.1274-0.39786-0.2972-0.03085-0.37794 0.181-0.5333 0.36918-0.13602 0.08016-0.25721-0.31314-0.55202-0.19142-0.34746-0.15029-0.1445 0.31009-0.27502 0.45155-0.20287 0.17765-0.44544 0.21421-0.70633 0.18565-0.29205-0.2225-0.08348-0.26214 0.17552-0.29979 0.23829-0.06227 0.45812-0.2423 0.2667-0.47073-0.1671-0.12277-0.55486-0.2111-0.61035 0.05432-0.03204 0.306-0.33606 0.30223-0.57054 0.37764-0.3077-0.0083-0.34122 0.28091-0.44655 0.50414-0.02656 0.29411-0.35492 0.28251-0.56797 0.26204-0.35908-0.15644-0.24056-0.27674 0.07355-0.38224 0.29868-0.01957 0.27087-0.38048 0.09398-0.51349-0.18034-0.13822-0.55805-0.02314-0.52923 0.23371-0.03491 0.24499-0.07989 0.51503-0.36465 0.56703-0.22372 0.11392-0.51726 0.17846-0.45924 0.49173 0.08717 0.5164-1.1853 0.22046-0.83549 0.04294 0.23996-0.07284 0.38705-0.26227 0.53229-0.454-0.12946-0.22902-0.4582-0.25982-0.59001-0.02911-0.13762 0.24324-0.35723 0.48303-0.27417 0.78054-0.27037 0.23671 0.08855 0.89505-0.47971 0.87375-0.20765 0.08828-0.51343-0.05773-0.67726 0.04788-0.10248 0.31148-0.17309 0.62937-0.19561 0.95696-0.37792 0.08427-0.080215 0.33648 0.029344 0.5419 0.351 0.50563 0.69376 1.0288 1.0854 1.4999 0.38952 0.3209 0.77066 0.6549 1.2492 0.83615 0.67259 0.35059 1.3817 0.64068 2.0307 1.0303 0.45753 0.35739 0.97048 0.6561 1.3928 1.0508 0.20669 0.23904 0.34672 0.41419 4e-3 0.12662-0.81796-0.63615-1.7258-1.1448-2.597-1.702-0.72713-0.32628-1.4643-0.63333-2.1296-1.0779-0.22688-0.14829-0.51885-0.23525-0.59353-0.52695-0.22872-0.41569-0.42133-0.8615-0.67226-1.2586-0.25161-0.1747-0.46469-0.41327-0.75719-0.52015-0.18372 0.22646-0.093328 0.44228 0.057893 0.65575 0.23774 0.41244 0.46642 0.84423 0.75475 1.2188 0.25341 0.19071 0.47963 0.45959 0.74922 0.60375 0.24936-0.0088 0.52361-0.08102 0.66624 0.18194 0.01185 0.12356-0.4122 0.14855-0.58223 0.20965-0.4287 0.11072-0.86782 0.15414-1.3078 0.19226-0.041908-0.28015-0.057416-0.53569 0.30314-0.44078 0.35439 0.04755 0.42609-0.04503 0.16167-0.30421-0.34623-0.46097-0.66009-0.95858-1.0261-1.3971-0.24686-0.165-0.46958-0.28934-0.37796 0.11586 0.042226 0.47538-0.09006 0.0106-0.1734-0.19049-0.14499-0.22604-0.51206 0.09176-0.31599 0.21907 0.30075 0.03387 0.34917 0.29362 0.40853 0.54358 0.092446 0.25321 0.081583 0.57791 0.21217 0.79272 0.22901 0.13502 0.58857 0.13748 0.71976 0.38184-0.00556 0.26869 0.11605 0.48319 0.299 0.67147 0.50046 0.65614 1.1143 1.2145 1.6679 1.8248 0.74032-0.28406 1.4508-0.65931 2.2355-0.8078 0.04287-0.29274 0.0905-0.56644 0.30058-0.18786 0.24627 0.0074 0.54525-0.0526 0.53037 0.2891-0.08038 0.16332-0.43481 0.19495-0.61878 0.30848-0.69066 0.28298-1.3813 0.56596-2.072 0.84894 0.05925 0.41544-0.0071 0.86206 0.14054 1.2555 0.11461 0.30256 0.30617 0.60038 0.37375 0.90582-0.03992 0.13119-0.07984 0.26238-0.11976 0.39357z"/> - <g stroke="#000" stroke-width=".2104px"> - <path d="m10.141-88.796 0.63873 0.6763 0.45086 0.82658-0.09393 1.5592-0.3945 0.16908 0.4133 0.41329 0.78902 0.05636 0.15028 0.35694 1.2023 0.82659 0.95809 0.46965 0.54479-0.39451 1.0144 0.11272 0.07515-0.39451-0.50723-1.672v-0.43208l1.5968-0.92052 1.4653 0.75144s0 0.86416-0.03753 0.9393c-0.03762 0.07513-0.43208 1.409-0.43208 1.409l1.409 0.01879 0.35693 0.05636 0.13151-0.60116-1.1647-0.03757 0.20665-0.48844 0.48844 0.05636 0.24422 0.37572 0.16906-0.30058-0.45086-0.24422 0.11272-0.35694h0.8078v-0.35694l-0.88294-0.05636-0.07515-0.24422 0.77023-0.01879-0.1315-0.30058-0.77023-0.11272s0.05638-0.35694-0.07515-0.35694c-0.1315 0-0.67629-0.09393-0.67629-0.09393l-0.33816-0.5448-0.31936 0.07515-0.46965-0.07515-0.263-0.28179-0.45087 0.30057-1.4841 0.77023-0.30058-0.03757-0.37572 0.75144 0.52601 1.841-0.33816 0.33815-0.41329 0.31936s-0.73266-0.56358-0.82659-0.63873c-0.09392-0.07515-0.99566-0.45086-0.99566-0.45086l0.03762-0.35694 0.1315-0.56358-0.92052-0.01879-0.05638-1.8974-0.65751-0.99566z"/> - <path d="m16.885-72.997 0.73265 0.18786 0.69508 0.07514 0.56359 0.07515 0.4133-0.20665 0.15029-0.35693 0.03753-0.03757-0.18786 0.1315-0.24421 0.22543-0.37573 0.05636-0.99566-0.30058-0.60115-0.20665-0.35694 0.30058z"/> - <path d="m14.95-90.149-0.48844 0.35693-0.28179 0.4133-0.78901-0.05638-0.2818 0.45087s-0.263 0.43208-0.263 0.54479c0 0.11272 0 0.58237 0.01876 0.75144 0.01877 0.16907-0.03762 0.45086-0.03762 0.45086l-0.28179 0.43208 0.22543 0.52601v0.1315l-0.24422 0.41329-0.16907 1.0144 1.1459 0.52601 0.63873 0.50722 0.58237-0.39451-0.43208 0.11272s-0.60115-0.18786-0.48844-0.28179c0.11272-0.09393 0.526-0.45086 0.526-0.45086l0.05638-0.30058-0.41329 0.31936-0.39451 0.18786-0.52601-0.52601-0.28179-0.48844-0.07515-0.35694 0.56358-0.31936 0.35694 0.16908 0.45086-0.05636 0.07515-0.39451 0.45087-0.39451 0.16907-0.63873-0.43208-0.33815 0.18786 0.45087-0.11272 0.41329-0.37572 0.37572-0.92052-0.35694 0.263-0.58237 0.24421-0.16908-0.26301-0.22543-0.01876-0.46965 0.28179-0.07515 0.50723 0.13151-0.35694-0.28179-0.22543-0.33816h0.37572c0.11272 0 0.35694-0.01876 0.35694-0.01876l0.46965-0.45086 0.58236-0.20665 0.48844 0.37572 0.43207 0.45087s0.13151 0.18786-0.1315 0.43208c-0.26301 0.24422-0.48844 0.22543-0.48844 0.22543l0.01877-0.43208 0.31936-0.16907-0.95809 0.01877 0.05638 0.31936 0.45087 0.54479 0.24422 0.07515 0.60115-0.33816 0.15029-0.11271 0.97687 0.16907 0.22543-0.37572 0.07515-0.41329-0.5448-0.263-0.46965-0.07515 0.18786 0.45086s0.37572-0.07515 0.37572 0.03753c0 0.11272 0.11272 0.30058 0 0.31936-0.11272 0.01876-0.69508-0.03762-0.69508-0.03762v-0.48844l0.18786-0.3945 0.56359-0.07515 0.84538-0.31936 0.28179-0.07515 0.45086 0.41329 0.48844-0.05638-0.71388-0.5448-0.78902-0.05638-0.8078 0.45087-0.48844 0.1315s-0.48844 0.07515-0.60115-0.1315c-0.11272-0.20665-0.71387-0.50723-0.71387-0.50723z"/> - <path d="m16.007-85.743 0.89001-0.50478 0.96972 0.46493-0.0797 0.54464-0.39851 0.09299-0.27896 0.06641 0.5712 0.06641 0.0797 0.39851-0.62433 0.14612-0.0797-0.62434-0.62433-0.02657 0.05312 0.9033-0.19926 0.10627-0.0797-0.94315-0.14612-0.43836-0.09298 0.33209 0.17269 1.0361-0.29225-0.94315 0.01328-0.86344 0.95643-0.55792 0.34538-0.13284 0.99629 0.47822-0.02656 0.39851-0.17269 0.51807-0.1594 0.11955 0.13284 0.17269 0.03985 0.70404-0.18598 0.25239h-1.1823l0.09298-0.25239 0.22582-0.13284 0.02656-0.46493-0.29224-0.01328v0.62434l-0.05312 0.14612-0.53135 0.0797-0.06973-1.7169z"/> - <path d="m11.08-79.573-0.17846 0.12211-0.17847 0.38512-0.16907 0.5354-0.09392 0.43208-0.047 0.5354-0.0094 0.45087 0.01877 0.33815 0.29119 0.43208-0.12211-0.70448-0.03753-0.89234 0.1315-0.78902 0.18786-0.51662z"/> - <path d="m11.963-77.713c-0.30565 5.68e-4 -0.4811-0.29263-0.48818-0.56892-0.08238-0.22488-0.0198-0.5817-0.26433-0.69053-0.06306 0.3027-0.17982 0.60644-0.13145 0.92272 0.05916 0.33285-0.10759 0.74997 0.16452 1.0071 0.21524 0.17767 0.3168 0.44045 0.22469 0.70778 0.03054 0.19542 0.24179 0.51901 0.4599 0.4392 0.33943-0.05929 0.26544-0.39818 0.28895-0.65428-0.05384-0.23561-0.2368-0.42299-0.34802-0.63701 0.04859-0.11102 0.19567-0.6216 0.1634-0.27226-0.05702 0.22284 0.03976 0.42604 0.16637 0.60356 0.1949 0.22129 0.06521 0.52484 0.03674 0.78881-0.08469 0.1887-0.39908 0.37342-0.57268 0.19454-0.15816-0.1525-0.33996-0.3751-0.23294-0.60943 0.07697-0.28351-0.23924-0.47535-0.3462-0.71493-0.14076-0.19556-0.04183-0.4656-0.07436-0.69476 0.01877-0.2331-0.05082-0.48278 0.04016-0.70201 0.04907-0.16686 0.15421-0.65133 0.32075-0.29918 0.14876 0.28168 0.16499 0.60935 0.2558 0.91137 0.06847 0.11391 0.23394 0.17618 0.33692 0.26828z"/> - <path d="m10.614-54.713 1.8597-0.90329 2.2184-1.0627 2.2715-0.4915-0.10627-0.30552-1.5808 0.39851-1.6339 0.51807 1.4612-0.78374 1.4479-0.39851 0.30553-0.10627-0.1594-0.33209-1.4081 0.4915-1.5011 0.6509-1.5409 0.89001s-1.6339 0.94315-1.6738 1.0096c-0.03985 0.06641 0.0797 0.4915 0.0797 0.4915z"/> - <path d="m18.85-57.755 0.02656 0.34538 2.4708 0.19926 1.3018 0.21254 0.10627-0.4915-1.9926-0.21254-0.03984 0.34538-0.38522-0.03985-0.22582-0.35866z"/> - <path d="m7.3595-74.319 0.26568 1.4878s0.45165 1.4346 0.5712 1.5808c0.11955 0.14612 0.75718 1.8597 0.75718 1.8597l1.4479 2.6568 1.6339 1.9926 1.2221 1.3549 1.0229 0.79702-1.2221 0.99628-0.82359 0.87673-0.82359 1.1823-0.87673 1.5542 3.2545-1.7269 2.5903-0.74389-0.78374-3.9054-0.53135-2.0988-0.54464-0.06641-0.4915 1.6073-0.43836 1.5409-0.35866-0.29224 0.78374-2.9756-1.2885-0.59777-0.87673 1.7003-0.27896-0.22582 0.82359-1.5675-1.7136-1.5542-1.0229-1.3682-1.2752-2.8959-0.079698-1.78z"/> - <path d="m6.7751-64.038c0.14577 0.03466 0.32081 0.01259 0.43104 0.13353 0.10872 0.08833 0.21742 0.17666 0.32614 0.26499v0.1594c0.064811-0.09455 0.20744-0.18639 0.22591-0.28256-0.1063-0.13607-0.2126-0.27213-0.31889-0.4082-0.13563-0.04495-0.27786-0.20596-0.40897-0.1735-0.14196 0.05915-0.24088 0.14451-0.25522 0.30634z"/> - <path d="m7.5588-65.034 0.51807 0.21254 0.31881 0.54463 0.1594 0.74389-0.039841 0.37195-0.25239-0.87673-0.54464-0.4118-0.50479-0.26568-0.14612-0.23911z"/> - <path d="m11.106-70.055 0.51807 0.77046 0.75717 0.75717 1.0361 0.77046 0.79703 0.19926 0.15941-0.22582-0.99628-0.86344-0.95643-1.0494-1.1823-0.43836z"/> - <path d="m25.376-48.031c-0.32563 0.25048-0.65125 0.50096-0.97688 0.75144 0.01249 0.22543 0.02505 0.45086 0.03754 0.6763 0.40077 0.20039 0.80154 0.40077 1.2023 0.60116 0.44361-0.12388 0.8745-0.30463 1.3274-0.38823 0.25458 0.0223 0.58962-0.18248 0.76261 0.07127 0.37505 0.39282 0.76436 0.77214 1.2042 1.0929 0.30848 0.32332 0.75042 0.39709 1.1563 0.53541 0.27039 0.12559 0.5671 0.0587 0.85345 0.06664 0.42373-0.0084 0.84746-0.01694 1.2712-0.02542-0.40077-0.15029-0.80154-0.30058-1.2023-0.45087-0.12389-0.26498-0.03332-0.73814-0.30711-0.85644-0.33589-0.12095-0.21232 0.4519-0.32244 0.66028 6.28e-4 0.10996 0.18241 0.14348 0.26087 0.2206 0.12289 0.07953 0.24579 0.15904 0.36868 0.23856-0.34222 0.23853-0.60806-0.0826-0.9406-0.12303-0.29504 0.0076-0.58638-0.02152-0.794-0.25731-0.30543-0.20227-0.57178-0.45092-0.80065-0.73624-0.27608-0.3203-0.5771-0.62465-0.83044-0.9613-0.14303-0.32182-0.28606-0.64364-0.4291-0.96546 0.08768-0.15029 0.17534-0.30057 0.263-0.45086-0.27552 0.12524-0.55105 0.25048-0.82658 0.37572-0.19008 0.34778-0.41482 0.68331-0.49614 1.0777-0.05066 0.21366-0.08681 0.44913-0.34498 0.27762-0.2317-0.0804-0.50216-0.10054-0.67427-0.29256-0.12107-0.10377-0.24214-0.20755-0.3632-0.31132 0.14753-0.2806 0.64736-0.18047 0.56682-0.58628 0.01145-0.0801 0.0229-0.16021 0.03435-0.24031z"/> - <path d="m30.421-45.889-0.76084-0.22543h-0.57298l0.47904-0.24422 0.65751-0.35694 1.0614-0.08454 0.71387 0.11272 0.85477 0.64812 0.35694 0.32876 0.25362-0.12211 0.07515 0.37572 0.08453 0.23483 0.43209 0.36633 0.45086-0.06575 0.09393-0.16907-0.07515 0.41329-0.30058 0.0094-0.65751-0.45087-0.24422-0.44147-0.25361-0.16907-0.89234-0.57298-1.0144-0.24422-0.89234 0.02818-0.34754 0.21604z"/> - <path d="m31.994-47.631 0.57298 0.04227 0.29588 0.23952 0.88764 0.91582 0.14559 0.2771 0.62464 1.0004 0.33345 0.16907"/> - <path d="m32.008-47.693 0.89703-0.05166s0.52131 0.45556 0.5354 0.47435c0.01408 0.01879 1.0238 1.3949 1.0238 1.3949l0.17846 0.52131 0.14559 0.32406-0.52131-0.57298-0.92052-1.3103-0.71387-0.61994z"/> - <path d="m34.262-48.519 0.68568 0.23483 0.59176 0.93461 0.61994 0.9487 0.32876 0.63403s-0.56828-0.69039-0.56828-0.71857c0-0.02818-0.97687-1.362-0.97687-1.362l-0.56828-0.32406-0.52131-0.19256z"/> - <path d="m33.018-48.092 0.76083 0.22543 0.75614 1.052 0.81719 1.1976 0.0517 0.89234-0.19726-0.92052-1.4794-2.1181z"/> - <path d="m25.658-48.463c0.29722 0.15242 0.55473 0.59925 0.9121 0.30475 0.93392-0.29272 1.7992-0.75484 2.6644-1.1906 0.66209-0.11767 1.3599-0.11978 2.0222-6e-3 0.61402 0.18622 1.2212 0.50604 1.8828 0.3287 0.99568-0.0558 1.8413-0.67506 2.6981-1.129 1.1309-0.68332 2.3931-1.1038 3.5905-1.6519-2.0739-0.11704-4.1513-0.2704-6.223-0.36478-0.56824 0.14664-1.1365 0.29329-1.7047 0.43993 0.76397 0.16908 1.5279 0.33815 2.2919 0.50722-0.3037 0.28805-0.60742 0.57611-0.91112 0.86416-0.57924-0.38664-1.709-0.10546-1.87-0.89947-0.17968-0.77364-1.225 0.08483-0.42835 0.42099 0.37512 0.0058 1.5375 0.46653 0.82318 0.80302-0.13922-0.21113-1.0059-0.68763-0.85606-0.27539 0.87687 0.43401-0.83716 0.19082-1.1436 0.26472-1.0048 0.05136 0.33877-0.96328-0.09098-1.0309-0.34618 0.51538-1.4502 0.25317-1.516 0.75379 0.34917 0.09273 0.85096 0.38505 0.21699 0.51827-0.57491-0.02517-0.9618 0.42786-1.4948 0.42194-0.12369 0.38184-0.60482 0.60835-0.86363 0.92047z"/> - <path d="m18.952-60.918c0.70134-0.03757 1.4027-0.07515 2.104-0.11272 0.5284 0.17486 1.0835 0.30163 1.5477 0.62414 0.16039 0.09253 0.32077 0.18506 0.48116 0.27759-0.8034-0.23062-1.6228-0.39648-2.4509-0.50722-0.51153-0.10593-1.0342-0.15848-1.5543-0.09617-0.16483-0.0077-0.23134-0.01656-0.1277-0.18562z"/> - <path d="m15.645-82.822c0.72634 0.09814 1.4585 0.24257 2.1924 0.13267 0.68988-0.01284 1.4242-0.32374 1.7574-0.95608 0.04684-0.11464 0.42371-0.49459 0.10642-0.3209-0.4272 0.27602-0.74606 0.69806-1.2118 0.92191-0.89929 0.40347-1.9033 0.09445-2.8443 0.2224z"/> - </g> - </g> - <path d="m-4.8315-49.646 28.592-0.45087 0.71388 0.82659-29.344 0.15029z" fill="#1a1a1a" stroke="#000" stroke-width=".2104px"/> - </g> - </g> +<svg width="69.046mm" height="23.991mm" version="1.1" viewBox="0 0 69.046 23.991" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> +<metadata> +<rdf:RDF> +<cc:Work rdf:about=""> +<dc:format>image/svg+xml</dc:format> +<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> +<dc:title/> +</cc:Work> +</rdf:RDF> +</metadata> +<g transform="translate(370.23 44.86)"> +<path d="m-370.23-44.86h69.046v23.991h-69.046z" fill="none" stroke-linecap="round" stroke-width="28.17"/> +<g transform="matrix(.73506 0 0 .73506 -24.436 -133.1)" fill="#fff" opacity=".10965" style="text-decoration-color:#000000" aria-label="裁"> +<path d="m-449.06 137.17 0.72 0.733q-0.36 0.36-0.626 0.6-0.267 0.24-0.52 0.427 0.2 0.186 0.413 0.36 0.227 0.16 0.467 0.346l-1.094 0.934q-0.213-0.2-0.413-0.4t-0.387-0.387l0.347 1.12q-1.04 0.36-1.987 0.64-0.946 0.28-1.973 0.587l-0.427-1.374q0.28-0.07 0.534-0.12 0.266-0.05 0.533-0.12v-0.493q0-0.133-0.01-0.267 0-0.133 0.01-0.266-0.187 0.16-0.387 0.32t-0.413 0.306l-0.8-1.226q0.733-0.467 1.173-0.867 0.454-0.4 0.787-0.853h-1.467v-1.374h2.187v-0.733h-2.213v-1.4h2.306v-0.84h-1.92v-1.347h1.92v-1.306h1.48v1.306h1.8v1.347h-1.8v0.84h2.454q-0.03-0.96-0.04-1.84 0-0.893-0.01-1.653h1.334q0 0.893 0.01 1.773t0.05 1.72h3.147v1.4h-3.054q0.05 0.64 0.12 1.227 0.08 0.586 0.187 1.146 0.307-0.533 0.56-1.093 0.253-0.573 0.48-1.2l1.053 0.733q-0.346 0.894-0.76 1.694-0.4 0.8-0.893 1.52 0.05 0.146 0.09 0.28 0.05 0.12 0.12 0.24 0.05 0.133 0.134 0.28 0.08 0.146 0.16 0.266 0.09 0.12 0.173 0.2 0.09 0.08 0.173 0.08 0.07 0 0.12-0.12t0.08-0.306q0.04-0.214 0.07-0.48 0.04-0.28 0.08-0.614l1.213 0.947q-0.04 0.4-0.16 0.8-0.106 0.413-0.28 0.733-0.173 0.334-0.413 0.547t-0.547 0.213q-0.493 0-0.893-0.28-0.387-0.28-0.707-0.733-0.24-0.32-0.453-0.76-1.067 1.12-2.493 1.907l-0.68-1.16q0.826-0.467 1.48-0.987 0.666-0.533 1.2-1.16-0.24-0.88-0.374-1.88-0.133-1-0.2-2.04h-2.64v0.733h2.467v1.374zm3.494-3.627q-0.147-0.333-0.267-0.6t-0.267-0.52q-0.133-0.267-0.306-0.533-0.16-0.267-0.4-0.614l1.12-0.626q0.266 0.346 0.44 0.613 0.186 0.267 0.32 0.52 0.146 0.253 0.253 0.507 0.12 0.253 0.253 0.573zm-5.014 6.147q-0.813-0.854-1.453-1.694-0.08 0.107-0.12 0.147v2.027q0.733-0.187 1.573-0.48zm0.254-1.6q0.213-0.174 0.413-0.387t0.467-0.533h-1.614q0.187 0.253 0.36 0.48 0.187 0.213 0.374 0.44z"/> +</g> +<path d="m-346.8-31.772 0.09-6.653" fill="none" stroke="#000" stroke-width=".1601px"/> +<g transform="matrix(.847 0 0 .847 6.8722 145.52)"> +<path d="m-419.28-214.75 4.452-0.01 0.825 0.796-0.248 1.336-4.681 2.699-1.375-2.519z" fill="#fff" stroke-width=".26458px"/> +<path d="m-416.9-216.61c-0.819-0.06-1.587-0.01-2.343 0.346-0.489 0.233-0.935 0.559-1.296 0.963-1.774 1.986-1.186 5.211 1.206 6.416 0.5 0.252 1.048 0.394 1.605 0.432 0.576 0.04 1.153-0.06 1.693-0.257 0.396-0.144 0.77-0.362 1.101-0.623 1.865-1.47 2.04-4.282 0.45-6.019-0.319-0.348-0.716-0.646-1.14-0.854-0.391-0.192-0.838-0.369-1.276-0.404m-1.442 4.737c0.496-0.322 1.007-0.621 1.503-0.943 0.267-0.174 0.54-0.355 0.819-0.509s0.58-0.46 0.899-0.518c0.381-0.07 0.644 0.309 0.506 0.651-0.118 0.293-0.606 0.464-0.857 0.627-0.691 0.448-1.403 0.862-2.094 1.311-0.27 0.175-0.643 0.496-0.985 0.458-0.348-0.04-0.536-0.52-0.703-0.778l-0.476-0.733c-0.103-0.159-0.212-0.327-0.157-0.526 0.09-0.311 0.455-0.447 0.719-0.258 0.149 0.107 0.242 0.293 0.339 0.443 0.162 0.249 0.37 0.502 0.487 0.775z" fill="#47e91b" stroke-width=".019302"/> +</g> +<path d="m-362.67-31.774 0.09-6.654" fill="none" stroke="#000" stroke-width=".1601px"/> +<g transform="matrix(.79511 -.084971 .084971 .79511 45.451 90.643)"> +<path d="m-493.6-213.01-0.576 2.198 3.534 4.241 2.171-0.121 1.067-4.722z" fill="#fff" stroke-width=".26458px"/> +<g transform="rotate(-31.008 -469.83 -165.36)"> +<path d="m-464.42-215.27v1.78c0 0.28-0.04 0.599 0 0.875 0.02 0.118 0.154 0.182 0.227 0.268 0.133 0.158 0.219 0.377 0.182 0.584-0.136 0.776-1.235 0.904-1.492 0.133-0.09-0.261-0.02-0.536 0.162-0.741 0.106-0.122 0.271-0.169 0.279-0.348 0.03-0.574 0-1.154 0-1.727 0-0.255 0.06-0.601 0-0.846-0.06-0.212-0.367-0.312-0.442-0.563-0.05-0.171 0.05-0.381 0-0.531-0.114-0.302-0.59-0.622-0.818-0.85-0.07-0.07-0.183-0.234-0.292-0.234-0.09 0-0.18 0.121-0.239 0.181l-0.558 0.557-1.727 1.728c-0.239 0.238-0.612 0.499-0.679 0.85-0.101 0.534 0.535 0.946 0.865 1.275l3.029 3.029c0.361 0.361 0.814 1.066 1.408 0.826 0.263-0.107 0.468-0.39 0.664-0.586l3.455-3.455c0.262-0.262 0.694-0.562 0.659-0.983-0.04-0.513-0.719-0.963-1.058-1.302l-2.577-2.577c-0.329-0.329-0.731-0.915-1.169-1.086-0.53-0.208-0.893 0.304-1.223 0.634-0.102 0.102-0.327 0.255-0.362 0.399-0.04 0.138 0.25 0.339 0.336 0.425 0.216 0.216 0.468 0.582 0.744 0.716 0.178 0.09 0.41-0.02 0.611 0.08 0.238 0.12 0.415 0.372 0.443 0.638 0.02 0.145-0.06 0.271 0.04 0.398 0.192 0.24 0.442 0.448 0.659 0.665 0.104 0.103 0.214 0.255 0.346 0.324 0.129 0.07 0.327-0.02 0.478 0.03 0.449 0.14 0.725 0.653 0.491 1.083-0.25 0.461-0.862 0.571-1.232 0.185-0.139-0.144-0.215-0.331-0.215-0.53 0-0.09 0.05-0.207 0.02-0.292-0.04-0.124-0.194-0.23-0.283-0.319-0.23-0.23-0.452-0.504-0.718-0.691" fill="#ef5034" stroke-width=".026571"/> +</g> +</g> +<path d="m-353.76-40.462a0.85894 0.85894 0 0 1-0.859 0.8589 0.85894 0.85894 0 0 1-0.859-0.8589 0.85894 0.85894 0 0 1 0.859-0.8589 0.85894 0.85894 0 0 1 0.859 0.8589" stroke-linecap="round" stroke-width=".49969"/> +<path d="m-355.47-40.456h1.708v14.854h-1.708z" stroke-linecap="round" stroke-width="1.0263"/> +<path transform="rotate(90)" d="m-25.838 349.87h1.7076v9.755h-1.7076z" stroke-linecap="round" stroke-width=".83169"/> +<path d="m-355.15-39.226c-2.247-2.269-4.946-2.245-7.095-0.235-0.415 0.248-1.066 0.106-1.226-0.386" fill="none" stroke="#000" stroke-linecap="round" stroke-width=".60529"/> +<path d="m-354.28-39.214c2.247-2.269 4.946-2.246 7.095-0.236 0.414 0.249 1.066 0.106 1.226-0.385" fill="none" stroke="#000" stroke-linecap="round" stroke-width=".60509"/> +<path d="m-364.91-31.784 2.159-6.827" stroke="#000" stroke-width=".1601px"/> +<path d="m-362.25-39.078a0.31566 0.63133 0 0 1-0.316 0.6313 0.31566 0.63133 0 0 1-0.316-0.6313 0.31566 0.63133 0 0 1 0.316-0.6313 0.31566 0.63133 0 0 1 0.316 0.6313" fill="none" stroke="#000" stroke-linecap="round" stroke-width=".18153"/> +<path d="m-346.38-39.074a0.31566 0.63133 0 0 1-0.316 0.6313 0.31566 0.63133 0 0 1-0.316-0.6313 0.31566 0.63133 0 0 1 0.316-0.6313 0.31566 0.63133 0 0 1 0.316 0.6313" fill="none" stroke="#000" stroke-linecap="round" stroke-width=".18153"/> +<path d="m-360.22-31.724-2.158-6.827" fill="none" stroke="#000" stroke-width=".1601px"/> +<path d="m-349.06-31.786 2.159-6.826" fill="none" stroke="#000" stroke-width=".1601px"/> +<path d="m-344.35-31.792-2.158-6.826" fill="none" stroke="#000" stroke-width=".1601px"/> +<path d="m-365.36-31.672s0.09 0.385 0.198 0.551c0.07 0.107 0.325 0.269 0.325 0.269h4.669s0.231-0.152 0.302-0.266c0.1-0.158 0.141-0.538 0.141-0.538l0.244-0.07v-0.06h-6.122v0.06z" stroke-width=".14012px"/> +<path d="m-349.52-31.77s0.09 0.386 0.198 0.552c0.07 0.107 0.325 0.269 0.325 0.269h4.668s0.232-0.152 0.302-0.266c0.1-0.158 0.141-0.538 0.141-0.538l0.245-0.07v-0.06h-6.123v0.06z" stroke-width=".14012px"/> +<path d="m-358.7-24.99a0.85894 0.85894 0 0 1-0.859 0.8589 0.85894 0.85894 0 0 1-0.859-0.8589 0.85894 0.85894 0 0 1 0.859-0.8589 0.85894 0.85894 0 0 1 0.859 0.8589" stroke-linecap="round" stroke-width=".49969"/> +<path d="m-349.01-24.984a0.85894 0.85894 0 0 1-0.859 0.8589 0.85894 0.85894 0 0 1-0.859-0.8589 0.85894 0.85894 0 0 1 0.859-0.8589 0.85894 0.85894 0 0 1 0.859 0.8589" stroke-linecap="round" stroke-width=".49969"/> +<path d="m-326.42-36.282c-0.04-2.572-0.541-5.079-6.42-5.079-1.686 0-5.944 0-5.944 4.669 0 2.377 1.275 3.912 3.912 4.193l4.756 0.519c2.464 0.259 3.047 1.448 3.047 3.263 0 2.054-0.756 3.459-5.122 3.459-4.799 0-5.318-1.816-5.318-4.215h-1.491c0.04 3.285 1.34 5.49 6.765 5.49 4.842 0 6.614-1.535 6.614-4.842 0-3.998-3.112-4.344-4.495-4.495l-4.583-0.498c-1.902-0.216-2.615-1.124-2.615-2.831 0-2.507 1.426-3.437 4.582-3.437 4.388 0 4.842 1.686 4.842 3.804z" stroke-width="1.8012"/> +<path d="m-312.28-32.976c0-1.815 0-4.085-5.014-4.085-4.604 0-4.972 2.464-4.993 3.891h1.383c0.04-2.594 2.075-2.637 3.567-2.637 3.566 0 3.76 1.189 3.76 2.486 0 1.664-0.691 1.685-1.469 1.707l-3.977 0.108c-1.751 0.04-3.74 0.951-3.74 3.804 0 3.567 2.897 3.718 4.777 3.718 3.458 0 3.999-0.994 4.431-1.837 0.02 0.583 0.108 1.167 0.259 1.664h1.384c-0.368-0.735-0.368-2.032-0.368-2.378zm-1.318 4.107c0 2.075-0.735 3.653-4.28 3.653-2.334 0-3.523-0.454-3.523-2.378 0-0.929 0.108-2.701 2.767-2.766l3.847-0.09c0.476 0 0.951-0.173 1.189-0.475z" stroke-width="1.8012"/> +<path d="m-308.37-36.888v12.731h1.384v-12.731z" stroke-width="1.8012"/> +<g transform="matrix(.16088 0 0 .16088 -235.36 -62.093)" style="text-decoration-color:#000000" aria-label="裁"> +<path d="m-449.06 137.17 0.72 0.733q-0.36 0.36-0.626 0.6-0.267 0.24-0.52 0.427 0.2 0.186 0.413 0.36 0.227 0.16 0.467 0.346l-1.094 0.934q-0.213-0.2-0.413-0.4t-0.387-0.387l0.347 1.12q-1.04 0.36-1.987 0.64-0.946 0.28-1.973 0.587l-0.427-1.374q0.28-0.07 0.534-0.12 0.266-0.05 0.533-0.12v-0.493q0-0.133-0.01-0.267 0-0.133 0.01-0.266-0.187 0.16-0.387 0.32t-0.413 0.306l-0.8-1.226q0.733-0.467 1.173-0.867 0.454-0.4 0.787-0.853h-1.467v-1.374h2.187v-0.733h-2.213v-1.4h2.306v-0.84h-1.92v-1.347h1.92v-1.306h1.48v1.306h1.8v1.347h-1.8v0.84h2.454q-0.03-0.96-0.04-1.84 0-0.893-0.01-1.653h1.334q0 0.893 0.01 1.773t0.05 1.72h3.147v1.4h-3.054q0.05 0.64 0.12 1.227 0.08 0.586 0.187 1.146 0.307-0.533 0.56-1.093 0.253-0.573 0.48-1.2l1.053 0.733q-0.346 0.894-0.76 1.694-0.4 0.8-0.893 1.52 0.05 0.146 0.09 0.28 0.05 0.12 0.12 0.24 0.05 0.133 0.134 0.28 0.08 0.146 0.16 0.266 0.09 0.12 0.173 0.2 0.09 0.08 0.173 0.08 0.07 0 0.12-0.12t0.08-0.306q0.04-0.214 0.07-0.48 0.04-0.28 0.08-0.614l1.213 0.947q-0.04 0.4-0.16 0.8-0.106 0.413-0.28 0.733-0.173 0.334-0.413 0.547t-0.547 0.213q-0.493 0-0.893-0.28-0.387-0.28-0.707-0.733-0.24-0.32-0.453-0.76-1.067 1.12-2.493 1.907l-0.68-1.16q0.826-0.467 1.48-0.987 0.666-0.533 1.2-1.16-0.24-0.88-0.374-1.88-0.133-1-0.2-2.04h-2.64v0.733h2.467v1.374zm3.494-3.627q-0.147-0.333-0.267-0.6t-0.267-0.52q-0.133-0.267-0.306-0.533-0.16-0.267-0.4-0.614l1.12-0.626q0.266 0.346 0.44 0.613 0.186 0.267 0.32 0.52 0.146 0.253 0.253 0.507 0.12 0.253 0.253 0.573zm-5.014 6.147q-0.813-0.854-1.453-1.694-0.08 0.107-0.12 0.147v2.027q0.733-0.187 1.573-0.48zm0.254-1.6q0.213-0.174 0.413-0.387t0.467-0.533h-1.614q0.187 0.253 0.36 0.48 0.187 0.213 0.374 0.44z"/> +</g> +</g> </svg> + diff --git a/assets/stop.svg b/assets/stop.svg new file mode 100644 index 0000000..462efaf --- /dev/null +++ b/assets/stop.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.956 3.956" height="14.952" width="14.952" stroke-width=".07"><circle r="1.943" cy="1.978" cx="1.978" fill="#f9f9f9" stroke="gray"/><g fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><path d="M1.406 1.374L2.67 2.638" paint-order="markers fill stroke"/><path d="M2.67 1.372L1.406 2.636" paint-order="markers fill stroke"/></g></svg> \ No newline at end of file diff --git a/assets/surprised.png b/assets/surprised.png new file mode 100644 index 0000000..973273a Binary files /dev/null and b/assets/surprised.png differ diff --git a/assets/tc-gcc.svg b/assets/tc-gcc.svg new file mode 100644 index 0000000..564b0c1 --- /dev/null +++ b/assets/tc-gcc.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle r="1.908" cy="1.909" cx="1.908" opacity=".12" fill="#f9f9f9"/><path d="M1.372 1.81c.001-.31-.182-.32-.453-.32-.17 0-.248.015-.32.075s-.09.138-.09.38c0 .196.01.27.06.352.06.096.165.108.384.108.15 0 .332 0 .39-.152.03-.07.03-.094.03-.358H.932v.165h.24c0 .16-.06.16-.24.16-.22 0-.222-.052-.222-.24 0-.295.02-.3.23-.3.158 0 .228 0 .223.133z"/><path d="M2.362 1.832c0-.31-.142-.34-.445-.34-.378 0-.42.117-.42.47 0 .403.112.444.465.444.336 0 .405-.092.408-.36h-.205c0 .166-.02.173-.246.173-.206 0-.217-.025-.217-.287 0-.252.03-.255.244-.255.16 0 .214.01.214.154z"/><path d="M3.35 1.832c0-.31-.14-.34-.445-.34-.377 0-.42.117-.42.47 0 .403.112.444.465.444.335 0 .405-.092.408-.36h-.206c0 .166-.02.173-.245.173-.206 0-.218-.025-.218-.287 0-.252.03-.255.245-.255.16 0 .214.01.214.154z"/></svg> diff --git a/assets/tc-llvm.svg b/assets/tc-llvm.svg new file mode 100644 index 0000000..8817783 --- /dev/null +++ b/assets/tc-llvm.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle cx="1.908" cy="1.908" r="1.908" opacity=".12" fill="#f9f9f9"/><text y="-89.817" x="-674.181" xml:space="preserve" font-size=".805" font-family="A-OTF Shin Go Pro" letter-spacing="0" word-spacing="0"><tspan y="3.175" x="0.629" font-weight="800">LLVM</tspan></text><path d="M2.018 3.04c-.102-.01-.203-.044-.352-.118l-.138-.07-.087.04c-.23.104-.396.123-.518.06-.114-.058-.143-.19-.06-.28.028-.03.07-.062.058-.042.022.001.026-.003.02-.006s.02-.006.056-.007a.68.68 0 0 1 .284.067c.05.02.09.033.088.03s.016.004.042.02l.047.027.052-.03.13-.08.077-.05-.004-.036c-.005-.042-.028-.093-.04-.09s-.01.04-.002.082c.005.02.004.026-.003.02s-.014-.03-.023-.116-.02-.056-.022.047l-.004.074c-.012-.003-.015-.01-.016-.02l-.007-.083c-.008-.092-.022-.07-.023.04-.001.088-.007.104-.02.054a.35.35 0 0 1-.012-.09l-.003-.046-.006.038c-.004.023-.006.03-.007.016s-.007-.027-.014-.032c-.015-.012-.016-.052-.004-.077s.058-.04.106-.04h.04l-.033.008c-.044.01-.08.025-.08.033.058-.005.092-.007.088 0s.006.008.018.01c.03.004.044.026.03.05.006.003.017-.002.025-.002s.016-.006.02-.013.017-.013.047-.013c.055 0 .055-.013.001-.067a.51.51 0 0 0-.265-.124c-.01 0-.05.033-.09.076l-.074.075-.04-.02a3.25 3.25 0 0 1-.296-.198C.917 2.09.81 1.99.76 1.922l-.034-.045-.014.045c-.02.063-.02.04.001-.03.077-.265.19-.46.31-.54.028-.02.047-.034.042-.034a.47.47 0 0 0-.149.115 1.7 1.7 0 0 0-.29.837c-.001.033-.005.06-.008.06-.008 0-.095-.215-.137-.338-.094-.28-.144-.55-.148-.797-.001-.048-.005-.084-.01-.08 0 .016-.006.023-.015.028-.017-.001-.02.002-.025.008.01-.022.017-.025.017-.022s.01-.005.02-.016a.59.59 0 0 1 .197-.092.6.6 0 0 1 .119-.014C.704 1.005.717 1 .695.977s.05 0 .155.053a2.58 2.58 0 0 1 .301.19c.017.014.033.023.035.02s-.047-.085-.108-.156C1.046 1.047.962.96.893.893L.767.772.876.86c.126.1.274.242.327.312l.037.054c0 .003-.01.012-.025.02-.046.026-.027.056.036.058.044.002.046.005.023.03-.01.01-.013.02-.01.025s.012.036.017.07c.014.088.043.176.087.26l.034.073c-.007 0-.064-.102-.085-.153a.86.86 0 0 1-.037-.118c-.008-.037-.015-.058-.016-.046-.002.07.05.208.114.313.023.037.025.047.023.1s-.003.06.008.026c.006-.018.01-.03.012-.023.017.004.033.013.047.025s.02.023.016.025a.12.12 0 0 0-.025.015c.017.013.04.027.063.048s.028.024.015.01c-.04-.044-.025-.04.026.006a.74.74 0 0 0 .202.12c.018.006.02.005.01-.007.02-.006.047.005.07.016a.48.48 0 0 0 .087.029c.055.012.146.005.173-.013.01-.007.02-.01.02-.006.013-.014.012-.023.002-.033.037-.015.055-.034.04-.044s-.008-.02.012-.02.04-.026.022-.032-.01-.017.01-.02.018-.008.017-.026.003-.023.01-.023c.013 0 .024-.023.016-.035-.003-.004.002-.013.01-.02s.013-.016.01-.022.001-.017.008-.024.01-.02.008-.027-.001-.018.004-.02a.28.28 0 0 0 .008-.087c0-.184-.073-.334-.19-.39-.088-.043-.23-.035-.277.016-.023.024-.043.074-.043.106 0 .024-.001.025-.02.017-.04-.015-.038-.01.01.05a1 1 0 0 1 .086.123c.043.075.054.086.086.087.013 0 .023.004.023.01 0 .013-.04.01-.07-.005l-.026-.013.018.03c.01.017.015.03.01.03s-.005.012-.003.026l.004.026-.028-.026c-.016-.014-.042-.03-.06-.035-.023-.007-.03-.013-.028-.023.013-.043.01-.056-.003-.017a.31.31 0 0 0-.015.105c.001.032.002.045.004.03.007-.067.012-.083.024-.078s.012.016.012.035.01.053.02.08c.015.038.018.05.01.056-.015.01-.055.008-.074-.002s-.016-.014-.012-.05c.01-.068 0-.09-.05-.122-.063-.04-.077-.076-.05-.13.01-.018.01-.025.002-.027s-.01-.033-.01-.11c0-.104.001-.11.024-.152a.3.3 0 0 1 .04-.058c.015-.013.016-.013.016.004.015.01.017-.01.015-.034s0-.043.005-.046c0 .01 0 .02.004.03.006.016.008.014.018-.018.014-.044.025-.06.025-.034s.013.023.026-.007.02-.032.027-.006c.005.02.005.02.023-.004s.03-.03.035-.01c.003.008-.01.018-.04.032-.065.03-.108.08-.106.12.007-.001.015-.004.023-.001-.01-.025-.01-.025.007-.016s.024.006.055-.016c.074-.052.23-.044.32.017.078.054.138.157.156.267.008.05.008.08 0 .146-.016.13-.052.227-.135.365l-.018.03.02-.024c.053-.06.14-.25.14-.304 0-.014.005-.02.02-.02-.01-.008-.012-.014-.007-.04.003-.017.005-.042.005-.057 0-.024.003-.027.03-.03s.03-.002.032.022.003.02.005-.008l.001-.034-.036.002c-.034.002-.034.001-.006-.004.034-.007.04-.02.025-.075-.047-.185-.227-.34-.4-.345h-.03L2 1.174c.117-.034.31.09.386.244.047.098.06.162.06.29-.001.095-.022.275-.042.345-.004.015-.002.02.01.02.018 0 .065-.027.093-.052.014-.013.02-.014.02-.005.001.024.024-.03.025-.057 0-.015.012-.05.026-.08.032-.064.032-.063.044-.047.005.007.016.016.025.02-.001-.025 0-.036.013-.06.016-.032.016-.032.025-.014s.01.018.01-.004c0-.012.003-.03.006-.038-.02.02-.02-.01-.005-.06.033-.11.044-.206.044-.377l-.008-.19c-.004-.018-.007.04-.006.155l.001.183-.007-.183c-.006-.147-.01-.186-.02-.197s-.013-.024-.012-.035c.008.005.018.01.044.01a.24.24 0 0 1 .068.013c.018.007.04.013.048.013.02 0 .1.074.134.123.015.02.046.076.07.122.05.1.058.1.01.002-.09-.185-.19-.29-.284-.29-.022 0-.022-.003-.004-.032.014-.022.014-.022-.002-.01s-.063.012-.062-.004c0-.005.008-.025.017-.043s.014-.026.01-.017c-.015.05-.01.043.03-.027l.06-.102c.02-.03.022-.04.003-.025-.014.01-.002-.005.093-.124.044-.055.067-.078.05-.05-.04.064-.073.105-.08.1 0 .01-.013.037-.033.066a1.18 1.18 0 0 0-.116.223c0 .007.025-.015.056-.05A1.37 1.37 0 0 1 3 .735c.054-.04.068-.044.06-.02-.004.015 0 .017.032.017.06 0 .124.03.184.086.05.048.137.164.137.185 0 .005-.015-.013-.033-.04a.53.53 0 0 0-.059-.075L3.297.863l.007.062a3.58 3.58 0 0 1-.009.548 2.5 2.5 0 0 1-.067.326l-.01.025-.006-.033c-.006-.03-.058-.22-.068-.247-.003-.007-.013.02-.024.063-.055.212-.173.447-.296.588l-.053.067c-.008.012-.015.018-.015.013 0-.015-.075-.094-.102-.108s-.028-.01-.08.01a.38.38 0 0 0-.23.179.68.68 0 0 1-.177.172c-.026.017-.048.035-.048.04 0 .01.01.008.028-.005.01-.007.02-.01.023-.008.02-.006.022 0 .02.005.026-.001.027.001.024.02s-.004.023.017.01c.038-.025.123-.004.123.03-.04-.028-.046-.022-.043.023.002.023 0 .04-.004.04s-.024.016-.044.037-.04.04-.044.04.02-.044.045-.068c.008-.008.017-.026.02-.04s.008-.024.013-.015c.007-.018-.002-.03-.02-.037-.012-.004-.015-.001-.015.02 0 .03-.007.03-.036.008-.04-.03-.074-.017-.1.036l-.017.035.004-.03c.003-.026.002-.03-.01-.027s-.025.013-.038.024c-.03.026-.066.026-.09 0-.017-.02-.017-.02-.05-.005a.35.35 0 0 1-.056.021.37.37 0 0 0-.066.029l-.132.07-.09.048.025.025c.014.014.03.023.035.02-.003.014.09.07.164.1s.17.055.242.06c.036.003.058.008.05.01a.39.39 0 0 1-.079-.002zm-.744-.157a1.3 1.3 0 0 0 .16-.066c.082-.042.224-.13.224-.14 0-.003-.01.001-.022.01-.138.09-.352.18-.505.21l-.075.018c-.002.002.028.002.067-.001.045-.003.1-.014.15-.03zm-.09-.014a1.05 1.05 0 0 0 .188-.07l.028-.015-.112-.047-.14-.055c-.09-.027-.153-.018-.198.028-.023.023-.028.034-.028.065 0 .043.022.077.062.095s.138.015.2-.001zm.424-.016zm.117-.167zm.146-.08c.005-.005-.005-.001-.022.01-.036.02-.053.023-.046.005-.022.002-.04.004-.05.002s-.023 0-.028.006 0 .01.018.01.027.005.028.01c-.02-.006-.02-.002-.017.002s-.002.014-.01.02.015-.003.05-.022.07-.038.075-.043zm-.167.043c-.028.013-.038.02-.038.023s.032-.013.038-.023zm.33-.024c0-.012-.004-.01-.022.01s-.016.018.003.008c.01-.005.02-.014.02-.018zm-.222-.036c.01-.007.013-.017.01-.025-.008-.02-.018-.017-.028.01a.01.01 0 0 0 .018.014zm.084.003zm.04-.03c-.01.003-.018.01-.018.01 0 .007.007.002.018-.01zm-.11-.03zm.242-.13c.03-.057.037-.096.027-.13-.006-.022-.008-.018-.012.03a.33.33 0 0 1-.077.178c-.016.02-.03.04-.03.044 0 .014.07-.08.092-.12zm-.25.114c0-.002-.008-.01-.02-.02l-.02-.016.016.02c.015.018.022.023.022.016zm.402-.083l.023-.03-.026-.027-.058-.065c-.018-.02-.03-.03-.028-.023a.88.88 0 0 1 .01.088c.003.04.01.082.012.09s.002.022-.007.033.001.004.02-.01a.31.31 0 0 0 .056-.056zm-.457.045c-.025-.02-.033-.014-.025-.001s.02.012.025.001zm-.385-.42c.003-.055.002-.07-.003-.05-.01.036-.026.21-.024.255s.02-.105.027-.205zm.92.226c-.018-.008-.02-.005-.017 0 .018.008.02.005.017 0zm.03-.068c.015-.05.016-.06.007-.067-.017-.01-.03-.008-.015.003s.012.01-.002.024c-.023.023-.032.1-.012.1.002 0 .012-.027.022-.06zm-.202.03zm.602-.213c-.005-.037-.012-.066-.014-.064s.001.035.006.072.012.066.014.064-.001-.034-.006-.072zM1.48 1.994c-.002-.02-.003-.006-.003.03s.001.053.003.036.002-.047 0-.067zm1.163-.038c-.002-.017-.003-.003-.003.03s.002.05.003.03.002-.045 0-.063zm-1.206-.002zm1.162-.062c.024-.05.013-.06-.012-.013-.013.025-.024.05-.025.053-.001.014.023-.013.036-.04zM1.4 1.8c-.016-.013-.017-.012-.013-.006zm-.025-.034zm.48-.092c-.01-.007-.02-.024-.02-.04l-.003-.027-.001.03c-.001.03.01.05.03.05zm.023-.024zm-.594-.076zm-.008-.025zm.423-.048l.028-.027-.018-.03-.018-.03.02.005c.018.005.02.003.017-.062l-.001-.067-.02.028a.33.33 0 0 0-.046.162c0 .024.002.044.005.044s.018-.012.033-.027zm.012-.028zm0-.146zm-.446.178zm-.008-.038zm1.868-.028zm-.01-.026zm-1.865-.008zm1.508.001zM1.76 1.34c-.01-.01-.013.026-.003.05.01-.028.008-.045.003-.05zm1.346.057zM1.09 1.314zm.074-.165c-.01-.015-.053-.06-.094-.102C.996.973.996.972 1.057 1.038l.09.102c.034.042.043.047.016.008zm-.86-.01zm.652-.026C.906 1.08.728.99.723.995s0 .012.003.024.012.022.036.027a1.07 1.07 0 0 1 .198.081c.046.028.043.015-.004-.015zM.54 1.037c.032-.008.056-.016.054-.018C.58 1.008.36 1.08.35 1.1c-.002.003.027-.006.064-.02s.094-.034.126-.042zM2.993.79c.03-.017.044-.03.047-.045s.002-.02-.002-.02a.77.77 0 0 0-.16.136c-.005.01.007.004.032-.015s.06-.042.083-.055zm.27.025C3.226.78 3.17.75 3.147.75c-.007 0 .012.013.042.03a.56.56 0 0 1 .079.049c.04.034.037.024-.004-.013zM2.867.727c.008-.012.01-.02.005-.016S2.843.75 2.85.75s.01-.01.018-.022zm-.75 2.31zm.045-.001zm-.48-.14zm-.025-.017zm-.033-.017zm.694-.056c0-.006.006-.022.013-.035s.013-.032.013-.042c.01.006-.008.077-.02.083zm-.058-.008c-.001-.01.028-.06.042-.075.008-.008.018-.028.02-.045.006-.033.02-.04.02-.01-.001.024-.014.09-.015.074-.001-.006-.016.005-.034.025s-.033.034-.033.032zm.034-.088zm-.946-.008zm1.004 0zm-1.054-.025c-.02-.01-.035-.02-.03-.02a.25.25 0 0 1 .073.037c0 .006 0 .006-.042-.017zM1.21 2.64c-.018-.01-.024-.014-.013-.01.024.006.067.028.054.027-.005 0-.023-.008-.042-.016zm-.075-.024c.003-.007.016-.004.025 0-.003.007-.016.004-.025 0zm.485-.008zM1.087 2.6c.006-.002.015-.002.02 0-.022.004-.027.002-.02 0zm.5-.045zm-.012-.033zm.06.004zm.062-.474zM.67 2.05zm.954-.045c-.03-.022-.063-.05-.075-.063s-.028-.022-.038-.022c.006-.014.018-.008.046.018a1.29 1.29 0 0 0 .081.068c.038.03.05.04.042.04a1.01 1.01 0 0 1-.056-.041zm-.945.007zm.01-.03zm.82-.115l-.02-.026.024.023c.013.013.022.024.02.026s-.013-.008-.024-.023zm.392-.003c0-.003.007-.01.016-.016s.018-.008.026.002c.002.02 0 .02 0 .013-.035.01-.042.01-.042.001zm.067-.007c0-.013.02-.037.03-.037.016 0 .016.012-.001.03s-.03.018-.03.008zm.054-.03zm-.076-.04l-.015-.023.02.013c.02.014.032.033.018.033-.004 0-.015-.01-.023-.023zm-.062.002zm.125-.02l-.02-.023.025.02.03.023c-.01.002-.022-.008-.033-.02zm-.602.002zm.57-.12zm.703-.004zm-.71-.017zm.72-.013zm-.728-.004zM1.9 1.54c-.033-.036-.043-.05-.025-.036.033.027.096.1.082.097-.001 0-.027-.028-.056-.06zm.795.044zm.01-.047zm.01-.066c0-.018.002-.025.004-.015s.002.025 0 .033zm-.885.003zm-.037-.21c0-.002.007-.008.015-.015-.008.017-.015.022-.015.015zm-1.59-.06c.01-.012.022-.02.024-.02s-.005.01-.015.02-.022.02-.024.02.005-.01.015-.02zm.042-.027c0-.005.007-.014.015-.02-.005.014-.01.024-.012.025zm1.69-.023c.006-.007.014-.013.018-.013.007.02 0 .025-.01.025zm.046.004c.017-.008.02-.005.02 0-.02.008-.024.005-.02 0zm.857-.105zM2.745.89c0-.008.034-.065.04-.065s-.002.01-.01.023c-.017.03-.03.05-.03.042zM2.79.808zm.017-.025z" fill="#9da4a8"/><path d="M.985 2.97C.94 2.96.9 2.942.908 2.935s.016.002.03.01.043.016.064.02.04.008.042.012c.006.01-.007.008-.06-.005zm.073.002c-.01-.012-.008-.013.11-.018.014-.001.027-.003.03-.005a.42.42 0 0 1 .132-.036c.003-.01.103-.06.123-.06l.06-.033c.032-.016.048-.02.06-.014s-.005.017-.084.055l-.12.052c-.012.003-.026.008-.03.012-.018.013-.108.042-.118.038a.24.24 0 0 1-.083.017c-.053.004-.07.003-.077-.007zm-.194-.07C.85 2.887.838 2.87.838 2.868c.01.007.024.023.037.038s.022.027.02.027-.017-.013-.03-.03zm-.034-.05zM.82 2.828zm.552-.02a2.62 2.62 0 0 1 .184-.093c.002 0 .012-.003.02-.008l.017-.008-.017.014a1.58 1.58 0 0 1-.209.104zm.21-.008c-.014-.01-.016-.007.045-.04.04-.022.052-.026.067-.02s.012.01-.04.038c-.04.02-.063.027-.07.023zm.123-.068c-.014-.008-.01-.012.033-.04s.05-.03.074-.02l.025.012-.054.028c-.062.032-.058.03-.078.02zM1.6 2.694zm-.645-.01C.948 2.68.94 2.675.937 2.67s-.01-.006-.023.001c-.024.015-.03.013-.014-.005.045-.05.163-.055.27-.013.033.013.06.026.06.03s-.065-.007-.086-.018c-.015.002-.037.004-.064.002-.032-.002-.05-.006-.047-.013-.008.01-.067.033-.08.03zm.92-.013a2.79 2.79 0 0 1 .1-.056c.11-.06.156-.09.21-.137.035-.03.036-.03.01-.004-.015.016-.027.034-.027.04-.02.01-.025.013-.025.016s-.02.02-.044.035c-.032.02-.047.036-.053.057s-.007.03.003.033c.02.008.003.02-.03.02-.023 0-.036-.005-.045-.017l-.012-.017-.04.017c-.038.017-.053.02-.045.014zm.15-.03c.01-.01.017-.022.017-.03s-.005-.01-.033.004-.04.025-.028.036.028.006.045-.01zm.21-.02c.007-.022.01-.02.006-.017.008.024.01.03.008.03zM2.11 2.62zm-.38-.017c-.016-.016-.017-.038-.003-.035.006.001.012-.007.013-.02s.007-.026.012-.03.006-.016.004-.023c-.006-.016.014-.017.028-.001-.04.028-.04.094-.008.11l.02.01H1.77c-.015 0-.033-.006-.04-.013zm.55-.018c.008-.002.02-.002.025 0-.03.004-.033.002-.025 0zm-.388-.016c.01-.008.025-.027.032-.043.01-.026.01-.03-.002-.044-.008-.01-.02-.013-.027-.01s-.018-.002-.025-.01c.002.04-.008.035-.02-.001-.005-.017-.02-.045-.03-.063-.022-.034-.018-.036.03-.022.022.007.036.018.047.04s.027.045.04.063c.023.032.025.033.036.017.007-.01.016-.024.02-.033a.05.05 0 0 1-.067-.025.54.54 0 0 0-.092-.126c-.003.003.002.012.01.02.014.015.013.016-.026.02s-.043.001-.073-.025a.61.61 0 0 0-.203-.098c-.043-.007-.043-.01-.05-.158l-.005-.117.04.036c.074.068.253.17.32.183.032.006.037.02.007.02-.01 0-.023.004-.026.008s.005.008.02.008a.35.35 0 0 1 .156.063c.053.037.068.037.024 0-.02-.017-.028-.03-.02-.03s-.007-.008-.032-.018-.043-.018-.04-.02.036.002.077.01a.35.35 0 0 0 .081.008c-.015.013-.018.025-.018.07a.16.16 0 0 1-.017.086c-.01.016-.014.022-.01.013l.013-.042c.006-.022.006-.023-.004-.01-.006.008-.014.015-.016.015-.026-.003-.04.001-.034.01S2 2.355 1.987 2.34a.59.59 0 0 0-.107-.11c-.01-.006-.007.002.012.02l.03.03h-.1l.046.015c.07.023.097.04.126.08.017.024.03.037.038.034s.01.001-.002.022c-.017.033-.13.148-.146.148zm-.024-.057zm.087-.03zm-.437-.043c.013-.02.042-.03.09-.036.02-.002.025-.001.017.005s-.018.006-.026.003-.02.002-.027.01-.017.01-.022.008-.013.003-.02.012-.014.017-.018.017zm.165.014zm-.017-.027c0-.01-.005-.02-.01-.022.016-.006.03.02.02.03zm.594-.04c0-.007.06-.106.068-.11.013-.008-.016.053-.04.085s-.03.034-.03.026zm-.25-.01zm-.73-.037C1.06 2.21.835 2.026.752 1.906L.73 1.873l.016-.047c.027-.078.08-.186.13-.263.078-.12.163-.192.266-.23.037-.013.044-.014.056-.003s.017.012.022.012.01.028.014.062c.01.124.063.268.134.375.034.05.035.063.018.13-.02.077-.03.164-.04.317l-.01.144zm1.048.02zm-.967-.075c.01-.133.047-.284.08-.323.015-.02.016-.02.02.05.003.04.012.1.02.14l.015.068-.038.03c-.02.017-.053.048-.07.07l-.033.04zm.978.05a.47.47 0 0 0 .009-.056c.003-.03.002-.04-.006-.035.01-.012.02-.028.024-.042s.02-.03.053-.048a.22.22 0 0 0 .113-.112.79.79 0 0 1 .049-.086l.03-.043.01.037c.005.02.01.067.01.104s.003.07.007.07a.75.75 0 0 0 .011-.223c-.007-.053-.001-.062.016-.023.012.03.025.033.026.006.001-.01.004.002.01.027s.006.05.004.054c-.005-.036-.022-.068-.02-.046 0 .007.006.028.013.046a1.55 1.55 0 0 1 .037.159c.024.124.024.125.02.054-.002-.04-.014-.13-.026-.2-.027-.16-.027-.21 0-.31.038-.145.048-.37.023-.548-.006-.042-.005-.05.008-.065s.016-.015.066.007c.106.048.212.193.298.408l.02.048-.02.073a1.75 1.75 0 0 1-.175.426c-.048.08-.16.227-.178.234-.008.003-.024-.007-.043-.03a.38.38 0 0 0-.059-.054l-.03-.02-.06.023a.48.48 0 0 0-.159.085c-.017.015-.032.026-.034.024.014-.007.007.008-.02.04-.016.02-.024.024-.02.014zm.06-.12c.03-.038.03-.04.004-.015s-.048.054-.04.054c.002 0 .018-.018.035-.04zM.61 2.314C.438 1.91.36 1.595.34 1.236l-.005-.103.045-.02a.53.53 0 0 1 .254-.049c.084.001.113.004.176.023a1.01 1.01 0 0 1 .275.131c.106.068.108.072.05.078-.16.016-.286.164-.396.462a1.79 1.79 0 0 0-.117.486c-.004.045-.01.077-.012.07zm1.716-.054zm-.19-.17zm.248-.02zm-.215-.03zm.224-.008zm-1.004-.02zm.827-.008c-.002-.006.003-.02.012-.027s.014-.02.012-.026.003-.016.012-.024c.02-.02.02-.01-.01.043-.013.025-.025.04-.027.034zm.187-.033zm.01-.046c.002.02 0 .014 0 0zm-.61-.023c-.006-.006-.01-.027-.01-.048 0-.028.002-.035.008-.026.01-.012.042.064.042.077s-.03.007-.04-.004zm.463-.007zm.156-.017c0-.012.003-.036.006-.054s.007-.026.007-.013-.003.036-.006.054-.007.026-.007.013zm.208 0zm-.715-.025c.018-.008.02-.005.017 0-.018.008-.02.005-.017 0zm-.17-.08c-.02-.018-.02-.02-.001-.014.01.003.02.01.023.016.007.02 0 .018-.02-.003zm1.477.005l-.023-.105a1.76 1.76 0 0 0-.138-.385c-.09-.18-.156-.255-.248-.276l-.043-.01L2.83.94c.072-.072.12-.108.185-.133.08-.03.192-.016.26.037.023.018.034.124.034.315 0 .11-.005.21-.014.28-.013.103-.046.28-.062.335l-.007.025zm-1.512-.03zm.19-.052c-.01-.008-.028-.02-.037-.024s-.018-.02-.02-.036c-.003-.025.01-.036.02-.018-.01.008-.013.016-.013.026s.004.017.016.013.018.001.02.01.015.02.027.028.02.014.014.015-.018-.006-.03-.014zm-.15-.12c.004-.008.01-.02.01-.03v-.023l.01.024c.01.027.001.042-.018.03zm-.87-.063zm.02-.03zm.072-.077c.057-.056.108-.087.158-.095.024-.004.02-.002-.013.01-.054.02-.105.052-.158.1-.025.023-.02.016.013-.016zm.786.018zm-.024-.052c.001-.05.001-.05.008-.02.01.046.01.07-.001.07-.005 0-.008-.022-.007-.052zm-.484.006c0-.018.002-.025.004-.015s.002.025 0 .033zm-.01-.042zm.017-.008zm.57-.017c.01-.012.022-.02.024-.02s-.005.01-.015.02-.022.02-.024.02.005-.01.015-.02zM1.75 1.29c0-.017.004-.03.008-.03s.014-.013.02-.03.014-.03.017-.03c-.01.023.01.017.024 0 .008-.01.02-.017.023-.017s-.01.016-.03.036-.044.05-.05.07l-.012.033v-.03zm.108-.002zm.184-.014a.41.41 0 0 0-.092-.004l-.054.004.042-.012c.046-.013.113-.016.113-.005 0 .004.01.01.023.016.028.01.018.012-.03.001zm-.87-.028zm.018-.02c.015-.006.017-.01.013-.024-.006-.024.007-.01.014.013s-.014.032-.027.01zm-.048-.001zm-.043-.03l-.023-.022.027.02c.015.01.027.02.027.022 0 .007-.01.001-.03-.02zm.056-.02c-.01-.014-.016-.025-.014-.025s.012.01.022.025.016.025.014.025-.012-.01-.022-.025zm.98.02zm-1.076-.03zm.12-.005c.017.008.022.015.015.015-.002 0-.008-.007-.015-.015zm.7.006c.003-.005.012-.008.02-.008-.015.02-.026.02-.02.008zm-.85-.017c.02.01.016.01 0 0zM1 1.133zm.117-.01l-.016-.02.02.016c.018.015.023.022.016.022-.002 0-.01-.008-.02-.02zm.033 0l-.016-.02.02.016c.018.015.023.022.016.022-.002 0-.01-.008-.02-.02zm-.176-.008zm-.02-.01zm.117-.036l-.03-.03.03.03c.03.027.035.034.03.034a.3.3 0 0 1-.031-.031zm.025-.008l-.02-.023.023.02c.02.02.027.026.02.026s-.012-.01-.023-.023zM.53 1.045zm.04-.007zm.095-.01zm.022-.007zm2.38-.265z" fill="#313845"/><path d="M1.4 2.795c.007-.007.083-.038.083-.033s-.017.012-.038.02c-.037.016-.052.02-.044.013zm.426-.11zM2 2.665c-.02-.006-.02-.007-.003-.008.01-.001.02-.005.024-.01.028.027.02.03-.02.018zm.025-.004zm-.884-.01zm.775.001zm-.803-.008zm.82 0zm-.968-.01zm.12 0zM1 2.625c.008-.002.02-.002.025 0-.03.004-.033.002-.025 0zm.05 0zm.926-.007zm.26 0zm-.496-.015c-.01-.007-.012-.014-.006-.023.017.01.027.02.037.025-.01.008-.023.003-.03-.002zm.003-.01zm.25.017zm-.25-.075zm.014-.02c.007-.01.008-.015.001-.023.026-.01.027.002.007.02zm.186-.013zM2 2.46zm-.485-.02c0-.004.008-.013.02-.018.02-.01.02-.01.003.008s-.022.022-.022.01zm.15-.004zm.35-.006c0-.005-.007-.018-.015-.027.02.014.03.026.026.03zm-.46-.01zm.045-.01zm.064.001zm-.402-.083C1.05 2.197.86 2.042.77 1.922c-.033-.044-.033-.045-.023-.08a1.24 1.24 0 0 1 .159-.316c.01-.01.003.004-.016.033-.023.036-.032.06-.03.07.014.05.164.144.26.164a.89.89 0 0 0 .22-.001c.006-.002.02.01.03.025s.02.025.02.02-.012-.024-.026-.045c-.028-.04-.075-.13-.075-.14 0-.004.015.022.034.056l.054.09c.02.028.024.043.014.078l-.006.02-.007-.02c-.001.02-.004.063-.01.1-.01.058-.025.218-.03.352l-.002.04zm.073-.182zm.017-.16zm.008-.042zm-.55-.228zM.82 1.7zm.008-.017zm.025-.05c-.02.017-.025.028-.025.032.02-.017.025-.028.025-.032zm1.12.736zm-.614-.082c.007-.077.036-.22.05-.253.01-.024.01-.018-.001.03.008.001.017-.002.025.001s.014 0 .014-.018.01.02.02.072l.02.095-.134.133zm.093-.204zm.287.234c-.043-.035-.138-.08-.194-.095l-.04-.01-.006-.045C1.5 2.142 1.5 2.117 1.5 2.11s.007.013.01.043.008.045.01.033.01-.024.037-.032a.3.3 0 0 0 .066-.031l.03-.02.036.017c.04.02.036.016-.057-.043l-.062-.044c0-.002.025.012.056.032a.89.89 0 0 0 .192.097c.035.01.04.025.01.025-.023 0-.02.01.007.027.013.008.036.025.052.038l.03.024-.046-.003c-.025-.002-.05 0-.058.005s-.01.01.005.028c.03.032.028.036-.008.038-.03.001-.04-.004-.072-.028zM.597 2.274l-.152-.427c-.06-.214-.1-.46-.1-.633V1.13l.05-.02a.85.85 0 0 1 .098-.031.95.95 0 0 1 .25-.004c.014.003-.024.005-.084.004s-.077 0-.04.002c.057.003.068.005.056.013-.02.014-.038.113-.04.195a.61.61 0 0 0 .099.323c.016.023.023.04.018.048s-.01.022-.012.033.015-.02.04-.073l.05-.094a.64.64 0 0 1-.038.09c-.088.186-.16.45-.172.622l-.006.083zM.62 2.16zm.025-.142zm.008-.025zm.014-.077zm.008-.025zm.008-.025zm.01-.024zm.006-.026zM.71 1.79zm.008-.025zm.02-.04c-.013.01-.016.02-.016.025.013-.01.016-.02.016-.025zM.36 1.138zm.156-.052zM2.07 2.31zm.69-.106l-.03-.217c-.03-.196-.03-.214-.008-.293.026-.092.035-.157.04-.3.006-.128.006-.13.007-.038a1.64 1.64 0 0 1-.019.264l-.005.038.01-.042c.006-.023.012-.037.012-.03.002.024.122.004.208-.035.024-.01.06-.03.08-.046.026-.018.04-.022.04-.015.005-.007.001-.02-.004-.028s-.007-.02-.005-.02.012.017.022.043l.018.047-.017.066c-.057.222-.18.466-.308.612l-.042.048-.006-.052zm-.04-.434zm0-.03zm.027-.07c0-.007-.005.001-.012.017-.015.038-.014.053 0 .02.006-.014.01-.03.01-.038zm.37-.125c-.01.022-.005.02 0 0zm-.003-.03zm-.42.68l-.048-.048.005-.134c.006-.15.005-.15.048.005.017.06.05.226.043.226s-.023-.022-.05-.048zm-.026-.25c-.004.04-.002.048 0 .038s.002-.027 0-.038zm-.212.258c.044-.042.046-.052.008-.038l-.024.01.02-.02.05-.04c.016-.012.026-.025.023-.028-.024.01-.017-.01-.001-.04s.033-.057.038-.06c-.025.043-.022.054.01.07.026.012.03.018.03.048.001.02.004.01.007-.03l.005-.063.003.142-.035.01a.98.98 0 0 0-.098.041l-.072.034c-.005.002.01-.014.032-.035zm.164-.06zm-.07-.1c-.015.022-.014.023-.006.015zm-.676.176zm-.382-.18c0-.035.002-.05.003-.03s.002.045 0 .063-.003.003-.003-.03zm.05-.027l-.016-.02.02.016c.018.015.023.022.016.022-.002 0-.01-.008-.02-.02zm1.034-.044zm-1.133-.013zm.046.004zm1.113-.03zm-.788-.022c-.015-.004-.03-.04-.02-.05.005.034.02.045.03.03.018.026.015.027-.01.02zm.02-.007zm-.017-.033zm1.394-.153a2.11 2.11 0 0 0-.179-.487c-.016-.03-.026-.05-.02-.046s.024.037.043.07c.032.06.05.08.02.024-.012-.023-.012-.026.004-.046.05-.064.074-.217.047-.31a.57.57 0 0 0-.03-.081L3.08.81l.027-.004c-.045.001-.074.004-.082.007.03-.025.144-.02.19.002.07.036.073.038.084.135a2.66 2.66 0 0 1-.053.755l-.02.082-.015-.073zm-.056-.25zm-.025-.067zm-.008-.017zm-.01-.026zm-.006-.015zm-.008-.017zm.1-.502zm-.02-.01zm-1.3.868zm-.03-.018zm-.567-.03c-.008-.02-.006-.026.004-.01.003.02-.001.017-.004.01zm.564 0zm.008-.017zm-.586-.02zm-.008-.025zm-.008-.03zm-.396-.046c.008-.014.02-.03.027-.036s.001.006-.012.026c-.027.04-.038.048-.015.01zm.388.012zm-.277-.05l.05-.046c.002 0-.016.02-.04.046l-.05.046c-.002 0 .016-.02.04-.046zm.27.004zm-.344-.02c0-.002.007-.008.015-.015-.008.017-.015.022-.015.015zm2.192-.018zm-1.347-.008zm1.338-.017zm-.008-.017zm-1.815-.02zm.486-.005zm1.32.01zm-1.832-.008zm1.81-.04zm-.02-.036zm-1.255-.013zm1.246-.004zm-.025-.042zm-1.154-.025zm1.13-.008zm.037-.03zm-.083-.026l-.025-.027.027.025c.025.023.03.03.025.03s-.014-.012-.027-.027zm.067.001zM.905 1.13zM.888 1.12zm-.025-.008zM.817 1.1c.003-.007.016-.004.025 0-.003.007-.016.004-.025 0zm2.112-.023l-.03-.03.03.03.03.03c0 .006-.007 0-.034-.03zm-2.15.01zm2.076.002zm-.084-.084zm.017-.017zM2.884.89c.008-.01.017-.017.02-.017S2.9.882 2.892.89s-.017.017-.02.017.003-.008.01-.017zm.034-.026c.006-.01.08-.053.083-.05s-.017.015-.042.03-.043.023-.04.02z" fill="#1e2531"/></svg> diff --git a/assets/tc-mingw32.svg b/assets/tc-mingw32.svg new file mode 100644 index 0000000..5524cfa --- /dev/null +++ b/assets/tc-mingw32.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle cx="1.908" cy="1.908" r="1.908" opacity=".12" fill="#f9f9f9"/><rect transform="matrix(.693024 .720914 -.720914 .693024 677.365 113.288)" ry=".628" y="408.405" x="-549.632" height="2.619" width="2.619" fill="#b3b3b3" paint-order="markers fill stroke"/><g fill="#fff"><path d="M.845 2.108c0 .328.08.425.555.425.254 0 .512-.03.512-.35 0-.208-.122-.256-.166-.273.145-.05.145-.204.145-.24 0-.33-.295-.33-.518-.33-.38 0-.498.08-.504.395h.243c0-.116 0-.17.25-.17.18 0 .288 0 .288.135 0 .124-.09.124-.16.124h-.244v.195h.244c.07 0 .176 0 .176.143 0 .13-.11.147-.253.147-.31 0-.327-.03-.324-.2z"/><path d="M2.81 2.08c.08-.01.18-.05.23-.103.07-.07.08-.213.08-.267 0-.354-.255-.37-.525-.37-.478 0-.51.156-.512.415h.24c0-.164.03-.193.295-.193.226 0 .26.05.26.162 0 .1-.03.13-.144.143l-.38.04c-.29.03-.292.28-.292.403v.208h1.05v-.223h-.808v-.04c0-.11.06-.127.14-.138z"/></g><path d="M.77 1.702v.454h.07v-.3c0-.08.05-.1.114-.1.1 0 .1.06.1.103v.294h.07V1.86c0-.08.04-.103.112-.103.09 0 .09.05.09.09v.304h.07v-.32c0-.04 0-.142-.15-.142-.06 0-.11.02-.137.07-.03-.05-.07-.07-.133-.07s-.1.02-.132.06V1.7z"/><path d="M1.51 1.702v.454h.07v-.454zm0-.153v.08h.08v-.08z"/><path d="M1.697 1.702v.454h.07V1.89c0-.06 0-.132.125-.132.116 0 .117.06.117.102v.296h.07v-.308c0-.1-.05-.152-.166-.152-.04 0-.107 0-.146.06v-.06z"/><path d="M2.64 1.68c-.08 0-.09.03-.1.05-.03-.03-.09-.03-.145-.03-.12 0-.19.03-.19.13 0 .01 0 .06.03.09-.05.01-.06.06-.06.08 0 .05.03.06.04.07-.02 0-.05.03-.05.09 0 .09.08.112.23.112.158 0 .222-.03.222-.122 0-.06-.02-.104-.122-.11l-.218-.02c-.02 0-.04 0-.04-.04 0-.02.01-.03.03-.04.01 0 .03.03.14.03s.185-.02.185-.137c0-.03 0-.05-.01-.06.01-.02.02-.03.07-.03zm-.243.08c.1 0 .127.02.127.07 0 .06-.03.08-.124.08s-.13 0-.13-.08c0-.06.05-.07.126-.07zm.06.323c.07 0 .1.02.1.06s-.02.06-.15.06c-.15 0-.17-.02-.17-.06 0-.02 0-.03.02-.05 0 0 .03-.02.04-.02z"/><path d="M3.06 2.156h.1l.115-.454h-.07l-.09.396-.09-.396h-.1l-.09.396-.09-.396h-.07l.116.454h.1l.09-.4z"/></svg> diff --git a/assets/tc-mingw64.svg b/assets/tc-mingw64.svg new file mode 100644 index 0000000..a004222 --- /dev/null +++ b/assets/tc-mingw64.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle r="1.908" cy="1.908" cx="1.908" opacity=".12" fill="#f9f9f9"/><rect width="2.619" height="2.619" x="-538.604" y="407.444" ry=".628" transform="matrix(.693024 .720914 -.720914 .693024 669.029 106.003)" fill="#b3b3b3" paint-order="markers fill stroke"/><g fill="#fff"><path d="M1.882 1.69c0-.307-.192-.35-.497-.35-.486 0-.518.166-.518.688 0 .484.205.505.554.505.29 0 .47-.05.47-.396 0-.227-.08-.367-.425-.367-.256 0-.308.05-.356.1 0-.284.06-.312.31-.312.21 0 .223.06.222.136zm-.47.293c.2 0 .247.05.247.157 0 .135-.02.18-.25.18s-.295-.03-.295-.16c0-.173.1-.18.297-.18z"/><path d="M2.62 1.352l-.594.7v.24h.692v.23h.242v-.23h.19V2.07h-.19v-.718zm.1.718h-.442l.442-.517z"/></g><path d="M.77 1.702v.454h.07v-.3c0-.08.05-.1.114-.1.1 0 .1.06.1.103v.294h.07V1.86c0-.08.04-.103.112-.103.09 0 .1.05.1.09v.304h.07v-.32c0-.04 0-.142-.15-.142-.06 0-.11.02-.137.07-.03-.05-.07-.07-.133-.07s-.1.02-.132.06V1.7z"/><path d="M1.51 1.702v.454h.07v-.454zm0-.153v.08h.08v-.08z"/><path d="M1.697 1.702v.454h.07V1.89c0-.06 0-.132.125-.132.116 0 .117.06.117.102v.296h.07v-.308c0-.1-.05-.152-.166-.152-.04 0-.107 0-.146.06v-.06z"/><path d="M2.64 1.68c-.08.001-.09.03-.1.05-.03-.03-.09-.03-.145-.03-.12 0-.19.03-.19.13 0 .01 0 .06.03.09-.05.01-.06.06-.06.07 0 .05.03.06.04.07-.02 0-.05.03-.05.09 0 .09.08.112.23.112.158 0 .222-.03.222-.122 0-.06-.02-.104-.122-.11l-.218-.02c-.02 0-.04 0-.04-.03 0-.02.01-.03.03-.04.01 0 .03.03.14.03s.185-.02.185-.137c0-.03 0-.05-.01-.06.01-.02.02-.03.07-.03zm-.243.08c.1 0 .127.02.127.07 0 .06-.03.08-.124.08s-.13 0-.13-.08c0-.06.04-.07.126-.07zm.06.323c.07 0 .1.02.1.06s-.02.06-.15.06c-.15 0-.17-.02-.17-.06 0-.02 0-.03.02-.05 0 0 .03-.02.04-.02z"/><path d="M3.06 2.156h.1l.115-.454h-.07l-.1.396-.09-.396h-.1l-.09.396-.09-.396h-.07l.116.454h.1l.09-.4z"/></svg> diff --git a/assets/tc-msvc.svg b/assets/tc-msvc.svg new file mode 100644 index 0000000..dd47f12 --- /dev/null +++ b/assets/tc-msvc.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.816 3.816" height="14.423" width="14.423" xmlns:v="https://vecta.io/nano"><circle r="1.908" cy="1.908" cx="1.908" opacity=".12" fill="#f9f9f9"/><path d="M2.382 3.194c-.03-.01-.154-.14-.475-.495l-.448-.48c-.007 0-.14.104-.297.232-.33.27-.3.258-.483.183-.172-.07-.156-.002-.16-.713s-.01-.663.115-.724c.217-.105.192-.113.533.166l.29.23c.005 0 .208-.218.45-.485.456-.502.467-.512.553-.512.028 0 .61.275.64.303.056.05.055.024.055 1.01 0 1.113.032.967-.244 1.102-.407.198-.45.213-.53.183zm.117-1.3l-.01-.524a19.92 19.92 0 0 0-.634.52c-.014.017-.012.023.018.047l.322.262.296.23c.005-.003.01-.243.01-.534zm-1.4.1a.56.56 0 0 0 .075-.095 2.9 2.9 0 0 0-.298-.326c-.015 0-.026.556-.012.627l.008.04.076-.082.15-.166z" fill="#b88aed"/><path d="M2.312 3.12l-.395-.43-.387-.426-.07-.074-.268.22c-.37.305-.33.29-.502.214-.18-.078-.163.002-.162-.714s-.014-.64.142-.707c.188-.08.163-.088.407.11l.265.216.112.086c.006 0 .453-.485.792-.86.11-.12.14-.14.188-.13.058.015.063.046.063.394v.32l-.336.276-.34.288c-.004.013.003.02.407.347l.273.227c.015.022.01.635-.004.668-.012.026-.062.054-.095.054-.006 0-.046-.036-.09-.08zM1.036 2.084l.16-.186a3.42 3.42 0 0 0-.331-.359c-.015 0-.03.67-.015.706.01.028.026.014.186-.162zM2.485 3.19c.008-.003.02-.003.022.001-.03.006-.032.003-.022-.001zm.66-.39zm0-.035z" fill="#9260cc"/><path d="M.708 2.62c-.202-.083-.18.002-.18-.72l.012-.607c.012-.055.084-.012.186.1l.107.116.004.174.006.39c.001.26-.013.256.183.045.175-.19.175-.19.22-.136l.11.12c.04.044.075.084.075.09s-.214.188-.495.415c-.083.067-.1.068-.227.016zm1.043-.795c-.067-.058-.07-.064-.043-.078.053-.027.14-.025.175.003l.034.028-.046.048c-.057.058-.05.058-.12-.001zm.187-.087c-.012-.013-.01-.017.018-.024.062-.015.18-.114.195-.164.007-.024.078-.037.078-.015 0 .013-.042.034-.05.025a1.37 1.37 0 0 1-.207.184c-.01.006-.023.003-.032-.007zm-.315-.112c.013-.001.02.006.02.008 0 .01-.01.005-.02-.008zm-.122-.02c0-.006.005-.015.012-.02.012.01.006.02 0 .023zm.522-.001c.004-.006.012-.012.018-.012.01.018.003.023-.007.023zm-.432-.1c.012-.016.026-.03.032-.03s-.001.013-.017.03c-.035.037-.042.038-.015 0zm.666-.018c.004-.006.013-.012.02-.012 0 .018-.008.023-.014.023zm.055-.036c.003-.008.012-.018.02-.02.008.02-.002.028-.01.03zm-.636-.04zm.035-.035z" fill="#6e40a4"/></svg> diff --git a/assets/ubuntu-focal-aarch64.svg b/assets/ubuntu-focal-aarch64.svg new file mode 100644 index 0000000..0a4b1e3 --- /dev/null +++ b/assets/ubuntu-focal-aarch64.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.886 3.886" height="14.687" width="14.687" xmlns:v="https://vecta.io/nano"><circle cx="1.943" cy="1.943" r="1.943" fill="#f9f9f9" fill-opacity=".522"/><path d="M1.92 3.69c.008-.001.02-.001.03 0-.03.002-.037.001-.03 0zm.372-.13c.018-.016.018-.016.01-.007zm-.738-.02l-.01-.012.012.01zm1.295-.322c.016-.013.012-.007.005 0-.017.013-.012.007-.005 0zM.998 3.2l-.018-.02L1 3.2l.02.02c0 .004-.004 0-.02-.018zm1.89-.022zM.64 2.388zm2.585-.055c0-.016.001-.022.002-.014s.001.02 0 .03zm-.152-.364l-.015-.017.017.015.017.017c0 .004-.004 0-.02-.015zm-.095-.122c.001.02 0 .015 0 0zm-2.09-.06zm1.973-.282zM1.024 1.48c.022-.023.027-.027.027-.023s-.01.012-.025.025L1 1.506zm1.785-.03zm-.116-.086zM1.31 1.342l-.015-.002.018-.015c.022-.02.017-.024-.032-.03s-.09-.01-.108-.02l-.016-.006.013-.005c.007-.003.017-.01.022-.013s.006-.01-.042-.025c-.053-.017-.093-.035-.098-.045.013.001.055-.01.055-.015s-.02-.014-.046-.027S1.005 1.11.99 1.1l-.026-.02.023-.004c.04-.007.04-.008.005-.033a.65.65 0 0 1-.06-.047L.906.973H.93C.97.973.97.967.933.934.915.917.892.894.883.88L.867.858.898.86C.943.865.944.857.9.816.88.797.858.775.853.767L.845.752h.032C.894.752.91.75.91.747A.28.28 0 0 0 .869.66L.85.634 1.004.63.985.61C.972.598.968.592.973.59.985.58 1.047.584 1.09.594c.023.006.044.01.046.008.008-.005.004-.02-.01-.034S1.112.553 1.122.553a.95.95 0 0 1 .119.029c.015-.01.01-.02-.004-.035l-.02-.02.02.004c.028.005.074.022.1.038.012.007.025.01.028.01.01-.005.006-.024-.005-.04.001-.015.054.015.08.036s.03.024.038.014.007-.017.005-.03l-.003-.02.024.017a.37.37 0 0 1 .049.042c.022.023.025.024.03.017s.007-.018.006-.03.001-.02.003-.02a.6.6 0 0 1 .054.053c.056.058.052.057.064.018.003-.01.005-.008.024.01a.56.56 0 0 1 .12.208c.008.035.007.098-.002.127a.19.19 0 0 1-.077.088l-.023.012-.042-.03C1.62.976 1.502.905 1.393.854c-.058-.027-.216-.092-.242-.1-.007-.002.03.02.08.048.15.082.236.136.326.204.075.056.144.12.144.134 0 .02-.038.083-.067.11-.054.048-.144.08-.234.087l-.06.004c-.01.001-.023 0-.03-.001zm1.18-.001c-.148-.01-.253-.064-.3-.15-.01-.02-.02-.04-.02-.047s.017-.027.043-.05c.09-.085.232-.18.427-.288l.08-.048c-.027.007-.184.07-.242.1-.11.052-.226.122-.322.195l-.042.03-.023-.012C2.067 1.05 2.03 1.01 2.02.98S2.01.89 2.018.853a.56.56 0 0 1 .12-.208c.02-.018.02-.02.024-.01.013.04.01.04.064-.018L2.28.563c.002 0 .003.01.003.02s.002.024.006.03.01.006.03-.017c.013-.014.035-.033.05-.042l.025-.017-.003.02c-.002.013 0 .023.005.03s.01.01.038-.014.08-.05.088-.05c-.02.032-.022.05-.013.055.003.002.015-.002.028-.01.027-.016.073-.033.1-.038l.02-.004-.02.02c-.014.014-.02.024-.017.03.03-.002.12-.024.13-.024-.017.032-.02.044-.013.05.002.001.023-.002.046-.008.043-.01.105-.013.117-.006.005.003.001.01-.012.022l-.02.018.076.003.076.003-.018.026c-.016.024-.04.074-.04.087 0 .003.014.005.032.005h.032l-.01.014c-.005.008-.026.03-.046.05-.044.04-.044.05.002.045l.03-.003-.016.023c-.01.012-.032.036-.05.053-.038.033-.037.04.004.04h.024L2.942 1a.67.67 0 0 1-.06.047c-.033.022-.033.024.007.03l.024.004-.024.017a.64.64 0 0 1-.072.042c-.027.013-.05.026-.05.028s.042.016.05.013c-.001.012-.04.03-.094.047s-.05.017-.042.025.015.01.022.013l.013.005-.016.006a.48.48 0 0 1-.108.019c-.05.004-.054.01-.032.03.016.014.017.016.007.016-.022.003-.05.002-.078 0zM.987 1.242zM.87 1.07zm2.138-.024c.018-.016.018-.016.01-.007zM.763.632zM2.028.63c.018-.016.018-.016.01-.007zM1.804.593l-.01-.012.012.01zM.777.582C.795.567.795.567.79.575zM2.08.577c.018-.016.018-.016.01-.007zM.852.55C.86.548.872.548.88.55.85.55.844.55.852.55zm2.14 0c.006-.001.017-.001.024 0-.025.002-.03.001-.024 0zm-.8-.028A.14.14 0 0 1 2.216.5c.001 0-.007.01-.018.02s-.022.02-.023.02.007-.01.018-.02zM1.048.49zm.527-.01c.006-.001.017-.001.024 0-.025.002-.03.001-.024 0zm.693 0c.007-.001.017-.001.024 0-.025.002-.03.001-.024 0zM1.134.47zM2.72.47c.02.001.02.005.01.005zM1.467.454zm.92 0zm.244-.01z" fill="#69be46"/><path d="M1.87 3.583c-.114-.02-.26-.112-.288-.18-.01-.023-.006-.036.015-.06.083-.088.383-.125.576-.07.045.013.103.042.118.06.02.024.018.044-.008.084-.04.06-.136.125-.23.155-.05.016-.134.022-.183.013zm.55-.33c-.058-.02-.077-.056-.077-.142.001-.14.072-.284.196-.4s.216-.15.29-.113c.026.013.043.04.06.09a.43.43 0 0 1-.055.381c-.03.04-.092.102-.127.125-.09.062-.21.087-.288.06zm-1.1-.001c-.128-.02-.254-.113-.313-.233a.43.43 0 0 1-.051-.192c-.004-.097.017-.156.065-.18.064-.03.166-.005.265.07.045.034.123.117.146.154.08.133.097.292.04.352-.025.026-.09.04-.15.03zm.546-.123c-.082-.016-.143-.048-.2-.105-.07-.07-.1-.14-.098-.234.002-.218.22-.376.457-.328.025.005.062.02.095.035.046.022.06.033.095.067.096.096.124.223.078.346-.017.044-.06.104-.098.136s-.106.067-.153.078a.51.51 0 0 1-.176.006zM.855 2.58a.4.4 0 0 1-.069-.384c.025-.075.085-.152.152-.194.046-.03.075-.017.095.04.012.035.016.163.008.232-.02.163-.07.3-.116.33-.026.015-.038.01-.07-.023zm2.082.007c-.06-.05-.112-.244-.113-.423-.001-.103.01-.15.04-.178.017-.016.036-.013.07.01.114.074.176.2.17.342-.002.03-.007.07-.01.087-.013.05-.046.113-.076.146-.023.025-.03.03-.045.03s-.023-.005-.033-.013zm-1.56-.08c-.102-.028-.18-.11-.21-.218-.013-.048-.01-.137.003-.19.037-.14.15-.262.282-.307a.42.42 0 0 1 .182-.013c.105.022.193.11.222.22.013.048.01.137-.003.19-.037.14-.148.26-.282.307-.035.012-.053.015-.105.016-.044.001-.07-.001-.09-.006zm.98-.015c-.152-.033-.274-.15-.324-.308a.49.49 0 0 1-.003-.215c.038-.11.117-.185.22-.207.23-.05.476.17.475.422 0 .153-.095.278-.234.308-.045.01-.094.01-.137 0zm.464-.62c-.083-.03-.262-.166-.39-.298-.066-.068-.096-.108-.096-.128s.022-.02.108-.02c.18.001.314.08.38.225a.42.42 0 0 1 .04.163c.004.066 0 .074-.04.06zm-1.795-.01c-.015-.015 0-.134.025-.197.022-.056.05-.1.098-.143a.44.44 0 0 1 .342-.105l.053.004c.007.078-.305.358-.453.43-.04.02-.054.02-.065.01zm.815-.132a.41.41 0 0 1-.188-.083c-.027-.024-.05-.067-.05-.092 0-.062.093-.145.207-.183a.4.4 0 0 1 .263.003c.09.03.17.09.196.145.04.087-.026.167-.17.203-.056.014-.203.018-.26.008z" fill="#c31848"/><path d="M1.594 3.342zm.897-.077zm-1.153-.01zm.042 0zm.535-.01a.14.14 0 0 1 .032 0c-.035.002-.04.001-.032 0zm.087 0zm.37-.016zm.405-.093c.01-.01.02-.018.02-.018s-.005.008-.015.018-.02.018-.02.018.005-.008.015-.018zm-.794-.001zm-.322-.108l-.01-.012.012.01zM2.91 2.86zM1.566 2.82zm-.208-.04l-.02-.022.022.02c.02.02.025.024.02.024s-.01-.01-.022-.022zm1.553.012zm-1.345-.02zm1.4-.17zM1.64 2.59c.017-.016.018-.016.01-.007zm.037-.037c.017-.016.018-.016.01-.007zm-.23-.033c.008-.001.02-.001.026 0-.03.002-.034.001-.026 0zm-.2-.084l-.013-.014.014.013c.013.012.017.016.013.016s-.007-.007-.014-.014zm1.39-.017zm-.91-.007c0-.001.008-.01.017-.017l.017-.015-.015.017c-.014.016-.02.02-.02.015zm.435-.018zm-.03-.03l-.01-.012.012.01zm-1.367-.01zm0-.058zm2.06-.084zm-1.775-.045c0-.017.001-.024.002-.014s.001.024 0 .032zm.11 0zm.71-.042zm.956-.02zm-.808-.034zM.87 2.055zM1.008 2zm.263-.087c0-.001.008-.01.017-.017l.017-.015-.015.017c-.014.016-.02.02-.02.015zm.504-.055l-.013-.014.014.013c.013.012.017.016.013.016s-.007-.007-.014-.014zm.316-.003c0-.001.008-.01.017-.017l.017-.015-.015.017c-.014.016-.02.02-.02.015zM1.02 1.79zm1.838 0zm-1.302-.016c.008-.001.02-.001.026 0-.03.002-.034.001-.026 0zm.378-.032c.008-.001.02-.001.03 0-.03.002-.037.001-.03 0zm.31-.088c.016-.013.012-.007.005 0-.017.013-.012.007-.005 0zM1.39 1.63zm1.045-.043l-.03-.033.033.03c.03.03.035.034.03.034s-.016-.015-.033-.033zm-.154-.014zM1.12 1.55c0-.001.008-.01.017-.017l.017-.015-.015.017c-.014.016-.02.02-.02.015zm.368-.016c.017-.016.018-.016.01-.007zm.063-.096zm.834-.01zm.1 0zm-1.07-.01zm.047 0c.024.001.02.002.01.002zm.512-.063z" fill="#981238"/><path d="M1.53 3.52l-.01-.012.012.01zm.087-.064c-.013-.013-.022-.024-.02-.024s.013.01.026.024.022.024.02.024-.013-.01-.026-.024zm.666-.133zm.183-.057zm.047 0zm-1.192-.01zm.562-.01zm.142 0zm.836-.04c.018-.016.018-.016.01-.007zm-.113-.04zM1.082 3.13c-.013-.013-.022-.024-.02-.024a.18.18 0 0 1 .026.024c.013.013.022.024.02.024a.18.18 0 0 1-.026-.024zm.797.004zm.92-.02c.018-.016.018-.016.01-.007zm-1.12-.07zm-.037-.037zm1.267-.13zM.953 2.81c0-.02.001-.028.002-.017s.001.027 0 .037zm.613.026zM1.39 2.81l-.01-.012.012.01zm1.522-.04zm-1.345-.016zm-.24-.01l-.01-.012.012.01zm1.193-.02l.034-.032c.001 0-.01.014-.03.032l-.034.032c-.001 0 .01-.014.03-.032zM.892 2.614zm1.96-.006zM.86 2.59l-.02-.022.022.02.022.022c0 .004-.005 0-.024-.02zm2.146-.007a.12.12 0 0 1 .02-.018c.001 0-.005.008-.015.018s-.02.018-.02.018.005-.008.015-.018zm-.08-.001l-.013-.014.014.013c.013.012.017.016.013.016s-.007-.007-.014-.014zm-.71-.02l-.018-.02.02.018.02.02c0 .004-.004 0-.02-.018zm-.794-.042zm.063 0zm.44-.07c.01-.001.025-.001.034 0-.036.002-.044.001-.034 0zm.684-.01c.018-.016.018-.016.01-.007zm-.903-.016c.018-.016.018-.016.01-.007zm-.484-.012zm.95-.005zm.473-.003c.018-.016.018-.016.01-.007zM.762 2.372zm.992.006c.018-.016.018-.016.01-.007zm1.35-.064c0-.017.001-.024.002-.014s.001.024 0 .032zM.64 2.288zm.4-.016zm.116-.02zm1.665-.018zM1.046 2.21zm1.68-.026c.001.02 0 .015 0 0zm-1.68-.058zm.966-.026zm.808-.01zM.846 2.08c.018-.016.018-.016.01-.007zm1.167-.035zm-.148-.003zM.89 2.034zm2.07-.02zm-2.195-.02c.017-.016.018-.016.01-.007zm2.12-.023zm-2.09-.01c.018-.016.018-.016.01-.007zm.457-.037c.018-.016.018-.016.01-.007zm1.34-.05l-.03-.03.03.03c.028.027.033.032.03.032s-.014-.014-.03-.03zm-.8 0zm-.493.002c.018-.016.018-.016.01-.007zm.23-.107zm.063 0zm.307-.032zm.08 0zm.06-.005zm.525-.027zm-1.244-.02c.018-.016.018-.016.01-.007zm1.207-.012zM1.368 1.65zm.27-.01l-.013-.014.014.013c.013.012.017.016.013.016s-.007-.006-.014-.014zm.843-.008l-.015-.017.017.015c.016.014.02.02.015.02s-.008-.008-.017-.017zm-1.034-.056l.03-.026a.15.15 0 0 1-.023.026l-.028.026c-.001 0 .01-.012.023-.026zm-.344-.012c.018-.016.018-.016.01-.007zM2.39 1.54l-.013-.014.014.013c.014.012.017.016.013.016s-.007-.007-.014-.014zm.357 0l-.013-.014.014.013c.014.012.017.016.013.016s-.007-.007-.014-.014zM1.15 1.517c.018-.016.018-.016.01-.007zm.352 0c.017-.016.018-.016.01-.007zm1.334-.04l-.018-.02.02.018.02.02c0 .004-.004 0-.02-.018zm-1.192-.01c.01-.01.02-.018.02-.018s-.005.008-.015.018-.02.018-.02.018.005-.008.015-.018zm.683-.02zm-.937-.032zm.1 0zm.39-.063zm-.59-.01c.003-.004.035-.002.035.002-.03.002-.036 0-.035-.002zm.054.001c.007-.001.016-.001.02 0-.023.002-.028.001-.02 0zm1.17 0c.007-.001.016-.001.02 0-.024.002-.028.001-.02 0zm.038.002c0-.004.032-.006.035-.002-.026.004-.035.003-.035.002zm-1.277-.05zm1.318 0zm.274-.046c.017-.016.018-.016.01-.007zm-1.813-.067c.022-.001.02 0 .01.002zm1.755.001zm-1.902-.04l-.013-.014.014.013c.014.012.017.016.013.016S.915 1.15.908 1.142zm.065-.068c.024.001.02.002.01.002zm1.912 0c.024.001.02.002.01.002zM1.866.918c.001.02 0 .015 0 0zm.142-.003zM3.11.75zM.85.75zm2.17 0zM1.725.63l-.01-.012.012.01zm.42.003c.01-.01.02-.012.02-.003-.03.015-.03.015-.02.003zM2.06.597c.01-.01.02-.018.02-.018s-.005.008-.015.018-.02.018-.02.018.005-.008.015-.018zM3.077.575zM.888.545C.905.53.906.53.9.538zM1.67.522l-.018-.02.02.018.02.02c0 .004-.004 0-.02-.018zM1.104.47c.024.001.02.002.01.002zm1.642 0c.007-.001.016-.001.02 0-.023.002-.028.001-.02 0zM1.21.444zm1.436 0z" fill="#610b23"/><path d="M1.835 3.68c-.105-.02-.204-.07-.283-.145-.018-.017-.04-.03-.067-.04-.07-.028-.144-.067-.217-.117-.083-.056-.072-.05-.125-.075-.137-.065-.253-.21-.288-.36C.85 2.915.833 2.86.82 2.817L.783 2.7C.775 2.668.77 2.655.746 2.627a.49.49 0 0 1-.101-.244c-.02-.166.066-.363.2-.455.032-.022.04-.044.045-.107.007-.104.035-.198.078-.265a.62.62 0 0 1 .157-.152l.05-.037c0-.002-.01-.007-.024-.01-.035-.01-.057-.026-.073-.055-.007-.014-.017-.025-.02-.025-.015 0-.06-.027-.072-.043-.007-.01-.016-.026-.02-.037s-.008-.02-.01-.02-.018-.012-.035-.028C.89 1.122.877 1.1.877 1.075c0-.01-.008-.02-.023-.037C.83 1.015.817.98.823.957.825.952.818.937.807.92.784.887.776.86.783.837.787.822.786.817.774.8.754.77.752.738.768.71.78.69.78.685.774.67a.13.13 0 0 1-.007-.04c0-.047.035-.078.09-.078C.882.553.888.55.9.54.92.516.953.504 1 .503 1.034.5 1.043.5 1.054.49s.02-.013.053-.015.046-.007.06-.015.024-.012.06-.01c.063.005.065.005.092-.005l.025-.01.04.014c.034.012.046.014.073.01s.037-.001.062.01.04.014.06.014c.04 0 .06.01.086.036.02.02.023.022.045.022s.03.004.056.023a.48.48 0 0 1 .147.209c.008.023.016.042.018.042s.007-.012.013-.028a.5.5 0 0 1 .157-.225c.02-.017.03-.02.05-.02S2.18.54 2.198.52c.028-.03.048-.037.09-.037.027 0 .04-.003.06-.014s.03-.013.063-.01.042.001.063-.008c.032-.014.073-.015.093-.002.013.008.018.01.042.003.038-.01.08-.004.1.01.013.01.024.01.05.01s.037.002.054.014.027.014.06.015c.043.002.09.02.103.04.006.01.012.01.034.01.07 0 .105.043.088.107-.007.028-.007.03.003.048.014.024.012.067-.005.09-.01.014-.01.02-.008.042.004.03-.005.054-.028.085-.01.014-.014.023-.013.038.002.025-.014.06-.035.08-.01.01-.017.02-.02.034-.006.032-.027.063-.058.086-.02.015-.03.028-.037.044-.01.03-.04.057-.075.068-.022.007-.027.012-.032.027-.01.025-.033.043-.07.054-.017.005-.03.01-.03.012s.02.016.046.032a.68.68 0 0 1 .17.166c.042.072.066.156.07.25.004.073.01.09.058.124.08.06.14.15.173.263.013.043.014.055.014.13s-.001.088-.014.13c-.018.06-.042.107-.082.16-.027.037-.034.05-.042.084a1.79 1.79 0 0 1-.036.116l-.038.127c-.034.148-.152.29-.3.36-.022.01-.065.036-.095.058a1.11 1.11 0 0 1-.212.119.29.29 0 0 0-.096.058c-.08.067-.148.106-.233.128-.063.016-.18.02-.24.01zm.2-.103a.49.49 0 0 0 .223-.129c.068-.07.065-.11-.01-.15-.12-.063-.35-.073-.523-.023-.088.026-.15.07-.15.106 0 .06.134.164.25.195.046.012.047.012.11.013.043 0 .07-.002.1-.01zm.523-.317a.48.48 0 0 0 .344-.323.42.42 0 0 0 .009-.113c0-.09-.007-.124-.035-.18-.015-.03-.02-.036-.046-.05s-.033-.015-.068-.013c-.07.004-.145.05-.244.148-.113.113-.176.25-.176.383 0 .053.005.077.022.103.03.044.106.062.194.045zm-1.11-.02c.044-.022.06-.056.06-.133 0-.06-.015-.122-.045-.185a.47.47 0 0 0-.108-.151c-.113-.11-.228-.158-.316-.133-.093.026-.115.194-.046.357.023.054.048.09.093.135.086.086.184.13.286.126.04-.001.056-.004.076-.014zm.586-.113a.5.5 0 0 0 .087-.032c.04-.02.057-.03.095-.07s.05-.054.067-.09a.28.28 0 0 0 .031-.161c-.003-.09-.03-.147-.094-.213a.4.4 0 0 0-.551-.007c-.068.065-.1.13-.103.22-.002.038 0 .062.006.09.03.124.132.225.266.26a.52.52 0 0 0 .195.002zm-1.09-.54a.83.83 0 0 0 .088-.56C1.02 2.003.997 1.98.98 1.98c-.035 0-.127.082-.166.148a.42.42 0 0 0-.022.353.37.37 0 0 0 .077.117c.028.025.045.022.076-.01zm2.07-.01c.06-.063.092-.154.093-.258a.35.35 0 0 0-.039-.18c-.037-.074-.134-.166-.176-.166-.05 0-.073.07-.068.214.007.217.078.418.147.418.013 0 .022-.006.044-.03zm-1.445-.073c.143-.046.263-.18.294-.33.018-.085.007-.178-.03-.247-.023-.045-.08-.1-.126-.123-.158-.077-.363-.008-.476.16-.05.077-.075.152-.075.24s.03.168.092.23c.04.04.087.067.14.08s.127.007.18-.01zm.94-.01c.1-.026.182-.112.21-.218.013-.05.012-.137-.002-.192-.04-.15-.166-.28-.31-.318-.05-.013-.128-.014-.173-.002-.095.025-.173.1-.206.2-.02.058-.02.153-.003.215.044.155.164.275.313.316a.48.48 0 0 0 .168 0zm.352-.677c0-.072-.01-.11-.036-.168-.052-.11-.155-.187-.28-.213-.067-.014-.196-.012-.21.003-.01.01-.008.01.006.038.018.034.102.124.18.193.148.13.278.216.32.212h.018v-.063zm-1.75.03a1.76 1.76 0 0 0 .424-.364c.04-.06.033-.065-.083-.065a.38.38 0 0 0-.213.044.38.38 0 0 0-.205.25.6.6 0 0 0-.012.152c.006.016.04.01.09-.016zm.92-.107c.1-.01.18-.044.22-.09a.11.11 0 0 0 .016-.138c-.032-.054-.123-.117-.21-.144a.47.47 0 0 0-.213-.006c-.09.022-.188.084-.225.142-.073.113.096.24.323.242.023 0 .063-.002.09-.005zm-.628-.4c.155-.016.26-.08.297-.182l.008-.022-.046-.044A1.42 1.42 0 0 0 1.447.93C1.38.885 1.25.81 1.188.78 1.17.77 1.156.76 1.157.76a2.1 2.1 0 0 1 .271.114c.102.05.2.11.275.167.028.02.053.038.056.038.01 0 .044-.024.064-.045.03-.033.043-.065.043-.117 0-.084-.045-.178-.127-.273L1.72.62l-.007.02-.01.025a.25.25 0 0 1-.052-.049L1.597.563c-.005.04-.01.053-.016.053S1.57.608 1.56.6C1.546.58 1.49.537 1.48.537c.008.025.007.04-.004.05-.034-.02-.118-.07-.125-.063.02.03.022.048.014.053-.003.002-.015-.003-.03-.01a.38.38 0 0 0-.101-.037l-.02-.004.02.022c.028.03.023.04-.017.027-.03-.01-.11-.026-.11-.023s.007.01.016.017c.015.015.02.03.012.034-.002.001-.02-.002-.04-.007C1.057.585.974.58.97.59.968.59.973.6.982.608s.016.017.016.02S.964.632.924.632H.85L.86.65c.016.024.047.09.047.098S.902.753.877.75C.86.75.845.75.845.75.845.76.867.788.9.818s.037.036.03.04-.032.007-.05 0c-.013.01.027.055.057.08S.97.973.93.973H.906L.92.987c.007.008.032.028.055.045l.044.036c.001.003-.01.005-.025.005s-.027.002-.027.005.045.04.092.064l.044.024c.006.005-.014.012-.033.012-.01 0-.02.002-.02.005a.32.32 0 0 0 .106.048c.023.007.04.014.04.017s-.01.01-.02.014l-.018.01.02.008a.39.39 0 0 0 .079.014c.073.007.078.01.06.026-.007.007-.018.015-.023.02s-.005.006.022.007c.018 0 .056-.002.085-.005zm1.18-.001c-.006-.003-.016-.012-.023-.02-.017-.017-.012-.02.06-.026a.39.39 0 0 0 .079-.014l.02-.008-.018-.01c-.01-.005-.02-.01-.02-.014s.016-.01.04-.017c.044-.013.107-.04.107-.048s-.01-.005-.02-.005c-.018 0-.038-.007-.033-.012s.02-.012.045-.024c.046-.024.092-.056.092-.064 0-.003-.012-.005-.026-.005s-.026-.002-.026-.004.017-.017.04-.032S2.942 1 2.952.99L2.97.973h-.023c-.04 0-.04-.006-.007-.035s.07-.072.07-.08c-.03.007-.053.007-.063 0s0-.012.03-.04S3.03.762 3.03.75c0-.001-.014-.001-.03 0-.025.002-.032.001-.032-.005a.49.49 0 0 1 .047-.098l.01-.017h-.074c-.04 0-.074-.002-.074-.004s.007-.01.016-.02.014-.017.013-.02C2.902.58 2.82.585 2.78.595c-.02.005-.04.01-.04.007-.008-.005-.003-.02.012-.034L2.768.55c0-.003-.08.014-.11.023-.04.012-.045.004-.017-.027l.02-.022-.02.004a.38.38 0 0 0-.101.037c-.035.02-.044.008-.022-.027l.01-.018-.013.004c-.02.007-.04.017-.075.044s-.035.025-.042.017-.01-.023-.003-.038c-.008-.01-.067.036-.082.053-.02.024-.03.018-.03-.02-.007-.018-.032.006-.06.035l-.05.053L2.17.656C2.166.648 2.164.638 2.164.634c0-.018-.012-.01-.045.03-.02.024-.045.06-.058.083-.066.112-.07.222-.012.283.02.02.058.048.067.048.003 0 .03-.017.056-.038a2.07 2.07 0 0 1 .448-.245c.137-.054.14-.054.045-.005-.19.1-.36.215-.456.307l-.04.04.008.022c.04.117.177.185.372.186.035 0 .04-.001.032-.006z"/><g fill="#fff"><path d="M1.883 2.154c0-.22-.208-.22-.285-.22-.193 0-.325.04-.325.41 0 .3.08.387.318.387.14 0 .304-.03.304-.254 0-.207-.15-.244-.288-.244-.05 0-.175 0-.24.09-.001-.08.01-.186.04-.242.04-.06.13-.06.183-.06.172 0 .194.06.193.135zm-.296.16c.11 0 .214.02.214.16 0 .125-.05.17-.215.17-.09 0-.212-.01-.212-.158 0-.07.01-.173.213-.173z"/><path d="M2.413 1.942l-.425.492v.106h.464v.182h.09V2.54h.135v-.09h-.135v-.513zm.04.513h-.37l.37-.432z"/></g></svg> \ No newline at end of file diff --git a/assets/virt-qemu.svg b/assets/virt-qemu.svg new file mode 100644 index 0000000..9d9cd02 --- /dev/null +++ b/assets/virt-qemu.svg @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + id="svg23667" + version="1.1" + viewBox="0 0 3.8163 3.8163" + height="3.8163mm" + width="3.8163mm"> +<defs + id="defs23661"> +<radialGradient + r="55.8594" + fy="86.4452" + fx="98.0015" + cy="86.4452" + cx="98.0015" + gradientTransform="matrix(7.4619e-4,0.715502,-0.593998,6.19478e-4,95.0379,-44.4857)" + gradientUnits="userSpaceOnUse" + id="radialGradient3019" + xlink:href="#linearGradient4024" /> +<linearGradient + id="linearGradient4024"> +<stop + style="stop-color:#bebebe;stop-opacity:1" + offset="0" + id="stop4026" /> +<stop + style="stop-color:#000000;stop-opacity:1" + offset="1" + id="stop4028" /> +</linearGradient> +<linearGradient + y2="878.907" + x2="142.819" + y1="831.523" + x1="142.819" + gradientUnits="userSpaceOnUse" + id="linearGradient3021" + xlink:href="#linearGradient3560" /> +<linearGradient + id="linearGradient3560"> +<stop + style="stop-color:#ffffff;stop-opacity:1" + offset="0" + id="stop3562" /> +<stop + style="stop-color:#ffffff;stop-opacity:0" + offset="1" + id="stop3564" /> +</linearGradient> +<linearGradient + y2="61.7408" + x2="37.3406" + y1="22.6288" + x1="37.3406" + gradientTransform="matrix(1.11867,0,0,1.11867,-1.94964,-2.03029)" + gradientUnits="userSpaceOnUse" + id="linearGradient3023" + xlink:href="#linearGradient11579" /> +<linearGradient + id="linearGradient11579"> +<stop + id="stop11581" + offset="0" + style="stop-color:#ff771c;stop-opacity:1" /> +<stop + id="stop11583" + offset="1" + style="stop-color:#e72404;stop-opacity:1" /> +</linearGradient> +</defs> +<metadata + id="metadata23664"> +<rdf:RDF> +<cc:Work + rdf:about=""> +<dc:format>image/svg+xml</dc:format> +<dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> +<dc:title></dc:title> +</cc:Work> +</rdf:RDF> +</metadata> +<g + transform="translate(-171.961,-165.157)" + id="layer1"> +<g + transform="translate(841.902,266.555)" + style="display:inline" + id="g23659"> +<circle + r="1.90815" + cy="-99.4896" + cx="-668.033" + id="path2695-6-36-3-8-7-3-6-6-2-0-9-5-9-8" + style="display:inline;opacity:0.420955;fill:#f9f9f9;stroke:none;stroke-width:0.068301;stroke-linecap:round" /> +<g + transform="matrix(0.054059,0,0,0.054059,-670.422,-101.943)" + id="g3397"> +<path + style="fill:url(#radialGradient3019);fill-opacity:1;stroke:none" + d="m 44.525,17.1082 c -15.5132,0 -28.0964,12.5059 -28.0964,27.9392 0,15.4333 12.5832,27.9393 28.0964,27.9393 5.0761,0 9.8287,-1.3338 13.9382,-3.6771 l 1.4613,3.6771 h 1.21 10.0569 0.063 L 66.8229,62.0341 c 3.6282,-4.7077 5.7827,-10.5961 5.7827,-16.9867 0,-15.4333 -12.5675,-27.9392 -28.0806,-27.9392 z" + id="path3834-7-7-2-5-5-0-5-4-6" /> +<ellipse + ry="24.6429" + rx="35.3571" + cy="856.291" + cx="142.5" + style="fill:url(#linearGradient3021);fill-opacity:1;stroke:none" + id="path3661-8" + transform="matrix(0.52623,0,0,0.649697,-30.8298,-522.302)" /> +<rect + style="fill:#010101;fill-opacity:0.882353;stroke:none" + id="rect4054" + width="1.11114" + height="0.984151" + x="59.2217" + y="33.2021" /> +<circle + r="6.56599" + cy="856.393" + cx="115.662" + style="fill:#000000;fill-opacity:1;stroke:none" + id="path4442-9" + transform="matrix(0.502844,0,0,0.502844,-11.0624,-401.993)" /> +<rect + style="fill:#000000;fill-opacity:1;stroke:none" + id="rect4444-2" + width="18.9288" + height="2.76589" + x="46.1108" + y="24.0663" + transform="matrix(0.989749,0.142818,-0.189726,0.981837,0,0)" /> +<rect + style="fill:#000000;fill-opacity:1;stroke:none" + id="rect4446-6" + width="3.30167" + height="1.19097" + x="58.1347" + y="33.9422" /> +<path + style="fill:url(#linearGradient3023);fill-opacity:1" + d="m 37.0625,21.7188 c -0.029,1.0988 -1.948,0.15 -2.0312,1.4062 -2.124,-0.9456 0.1811,1.4347 -0.031,1.6563 h -0.031 L 34.8125,24.75 c -0.1025,-0.037 -0.2486,-0.083 -0.4687,-0.2187 -1.4834,1.1818 -1.5022,3.6404 -3.0313,5 -1.003,2.1647 -1.267,4.4945 -2,6.75 -0.361,2.1636 -0.6618,4.3335 -0.125,6.5 0.1612,1.7607 -0.3537,4.1692 1.25,5.4062 2.3771,1.9526 4.3478,4.4001 6.3438,6.75 3.1126,3.8305 5.8749,7.9599 8.3125,12.25 0.011,0.02 0.02,0.042 0.031,0.062 l 0.8125,1.7188 c 0.015,0.034 0.016,0.062 0.031,0.094 0.1316,0.2773 0.2731,0.4824 0.4375,0.5313 -0.026,-0.1711 -0.066,-0.4344 -0.094,-0.625 -0.2327,-1.5987 -0.4878,-3.737 -0.625,-5.5 -0.7952,-5.1748 -3.3523,-9.8737 -4.3125,-15 -0.021,-0.1544 -0.04,-0.3109 -0.062,-0.4688 -0.01,-0.063 -0.023,-0.124 -0.031,-0.1875 v -0.094 l -0.062,-0.5625 h -0.031 c -0.3145,-2.5359 -0.4044,-5.2684 1.4375,-7.25 1.8805,-1.8968 4.6716,-2.3694 6.9375,-3.6875 0.5038,-0.2973 1.0215,-0.5506 1.5625,-0.7188 0,0 0.027,0 0.031,0 1.5673,-0.4844 3.2463,-0.4525 4.875,-0.1875 0.024,0 0.038,0 0.062,0 0.1481,0.025 0.3217,0.065 0.4687,0.094 1.1924,0.1062 3.5699,0.573 4,-0.25 -1.6454,-1.4011 -3.9969,-1.3242 -6,-1.875 -0.3171,-0.058 -0.7037,-0.12 -1.0937,-0.1563 L 51.5,32.6875 51,32.5625 v 0.031 l -7.0312,-1.375 C 43.8571,31.117 43.7372,31.0145 43.625,30.9375 L 50.5,31.7813 V 31.75 c 0.2414,0.054 0.4765,0.097 0.7188,0.125 v 0.031 l 4.8437,0.875 c 0.8352,0.3115 1.6957,0.5415 2.5313,0.5937 1.3791,0.2007 3.1413,2.2762 3.5937,2.375 C 61.9658,33.5843 59.7953,32.6044 58,31.9688 55.9025,30.949 52.7695,30.9764 52.4375,28.1563 52.3492,27.9598 52.2636,27.7824 52.1563,27.5938 L 51.3438,26.5 h 0.031 c -1.1149,-1.3159 -2.6762,-2.2902 -4.3437,-2.6562 -1.6551,-0.5713 -3.5309,-1.1479 -5.2813,-1.375 -0.594,1.749 -1.2591,-1.3675 -2.6875,-0.4375 -1.2521,0.1482 -1.0678,0.4807 -2,-0.3125 z m 9,5.0937 c 0.4535,-0.012 0.9791,0.07 1.5,0.25 1.3892,0.4799 2.2979,1.5095 2.0313,2.2813 -0.2667,0.7717 -1.6109,0.9799 -3,0.5 -1.3892,-0.48 -2.2979,-1.4783 -2.0313,-2.25 0.1666,-0.4824 0.7442,-0.7613 1.5,-0.7813 z" + id="path3499-9-7-6" /> +<rect + style="fill:#010101;fill-opacity:0.882353;stroke:none" + id="rect4061" + width="0.158734" + height="0.190481" + x="60.6186" + y="34.6307" /> +<rect + style="fill:#010101;fill-opacity:0.882353;stroke:none" + id="rect4063" + width="0.0158734" + height="0.206354" + x="60.4757" + y="34.4561" /> +</g> +</g> +</g> +</svg> diff --git a/assets/windows-10.png b/assets/windows-10.png new file mode 100644 index 0000000..79b9c67 Binary files /dev/null and b/assets/windows-10.png differ diff --git a/assets/windows-10.svg b/assets/windows-10.svg new file mode 100644 index 0000000..fbdd1a2 --- /dev/null +++ b/assets/windows-10.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.886 3.886" height="14.687" width="14.687"><circle r="1.943" cy="1.943" cx="1.943" fill="#f9f9f9" fill-opacity=".12"/><path d="M2.498 3.044l-.558-.08-.16-.02v-.886h1.218V3.11h-.05c-.03 0-.23-.03-.45-.06zM1.18 2.86L.75 2.8v-.764h.917v.44c0 .412 0 .44-.03.44-.02 0-.225-.03-.46-.06zM.745 1.564c0-.38 0-.382.04-.39.04 0 .84-.118.87-.118.01 0 .02.176.02.433v.434l-.04.01-.463.01H.748zm1.035-.08V1.04l.05-.01c.03 0 .28-.04.558-.08l.56-.08h.05v1.07H1.78z" fill="#000000"/></svg> diff --git a/cmake/Description b/cmake/Description new file mode 100644 index 0000000..593fcb6 --- /dev/null +++ b/cmake/Description @@ -0,0 +1,2 @@ +Sai is a CI build tool to coordinate and perform distributed +building of git commits on a variety of platforms. diff --git a/etc-sai-EXAMPLE/builder/conf b/etc-sai-EXAMPLE/builder/conf index 83360cd..b371b51 100644 --- a/etc-sai-EXAMPLE/builder/conf +++ b/etc-sai-EXAMPLE/builder/conf @@ -4,12 +4,13 @@ # "systemd-nspawn": { "path": "/home/sai/sai-nspawn", + "overlaypath": "/home/sai/sai-nspawn", "instances": 3, "timeout": 1800 }, # - # physically connected targets, like microconstroller boards + # physically connected targets, like microcontroller boards # "target": [ { @@ -31,7 +32,7 @@ # "masters": [ { -# "url": "wss://warmcat.com/builder", +# "url": "wss://warmcat.com/sai/builder", "url": "wss://localhost:443/builder", "selfsigned": 1, "priority": 1 diff --git a/etc-sai-EXAMPLE/master/conf.d/unixskt b/etc-sai-EXAMPLE/master/conf.d/unixskt index 6c5fddc..527045f 100644 --- a/etc-sai-EXAMPLE/master/conf.d/unixskt +++ b/etc-sai-EXAMPLE/master/conf.d/unixskt @@ -1,21 +1,24 @@ -# you should create one file like this per vhost in /etc/sai/conf.d +# you should create one file like this per vhost in /etc/sai/master/conf.d { "vhosts": [{ # this has no special meaning but needs to be unique "name": "unixskt", "unix-socket": 1, + # this should be owned by sai and allow group access from + # whatever group your webserver runs under + "unix-socket-perms": "sai:apache", # multiple vhosts can exist with different unix skt names - "interface": "/var/run/sai-unixskt", + "interface": "/var/run/sai", # required for avatar cache "enable-client-ssl": "on", "mounts": [ { - # static assets like js, css, fonts "mountpoint": "/sai", "default": "index.html", "origin": "file:///usr/local/share/sai/assets", + "origin": "callback://com-warmcat-sai", "cache-max-age": "7200", "cache-reuse": "1", "cache-revalidate": "0", @@ -45,16 +48,33 @@ # "headers": [{ - "content-security-policy": "default-src 'none'; img-src 'self' data: https://travis-ci.org https://api.travis-ci.org https://ci.appveyor.com https://scan.coverity.com; script-src 'self'; font-src 'self'; style-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none';", + "content-security-policy": "default-src 'none'; img-src 'self' data:; script-src 'self'; font-src 'self'; style-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none';", "x-content-type-options": "nosniff", "x-xss-protection": "1; mode=block", - "x-frame-options": "deny", "referrer-policy": "no-referrer" }], "ws-protocols": [ - {"com-warmcat-sai": { + # this is the lhs of the path to use, not a full filepath + # the events go in ...-events.sqlite3 and the related + # info for an event goes in its own database file in + # ...-event-xxxx.sqlite3 where xxxx is the event uuid. + # + "database": "/var/lib/sai/sai-master", + # + # the push hook notification script creates a JSON + # object and signs it with a secret... "notification- + # key" should match the secret the notification hook + # script has that we're willing to accept. + # + # It should be a 64-char hex-ascii representation of + # a random 32-byte sequence, you can produce a strong + # random key in the correct format like this + # + # dd if=/dev/random bs=32 count=1 | sha256sum | cut -d' ' -f1 + # + "notification-key": "xxxxxxxxxxxx", # template HTML to use for this vhost. You'd normally # copy this to gitohashi-vhostname.html and modify it # to show the content, logos, links, fonts, css etc for @@ -105,8 +125,10 @@ # this dir is served via avatar-proxy # "cache-dir": "/var/cache/libjsongit2" - }} + } + } ] } ] } + diff --git a/hooks/sai.sh b/hooks/sai.sh new file mode 100755 index 0000000..b01e315 --- /dev/null +++ b/hooks/sai.sh @@ -0,0 +1,65 @@ +#!/bin/sh + +REPO_FETCH_URL_BASE="https://libwebsockets.org/repo" + +if [ $(git rev-parse --is-bare-repository) = true ] +then + RN=$(basename "$PWD") +else + RN=$(basename $(readlink -nf "$PWD"/..)) +fi + +RN=${RN%.git} + +echo sai update hook $RN... +rm -f .sai.json .sai.json.b64 +git show $3 -- .sai.json | patch -p1 --merge +cat .sai.json +cat .sai.json | base64 -w0 > .sai.json.b64 +SJL=`stat .sai.json.b64 -c %s` + +if [ -z $SJL -o $SJL = "0" ] ; then + cat /home/sai/.sai.json | base64 -w0 > .sai.json.b64 + SJL=`stat .sai.json.b64 -c %s` +fi + + + +TF=`mktemp` + +echo "{\"schema\":\"com-warmcat-sai-notification\"," > $TF +echo " \"action\":\"repo-update\"," >> $TF +echo " \"repository\":{" >> $TF +echo " \"name\":\"$RN\"" >> $TF +echo " \"fetchurl\":\"$REPO_FETCH_URL_BASE\"" >> $TF +echo " }," >> $TF +echo " \"nonce\":\"`dd if=/dev/urandom bs=32 count=1 | sha256sum | cut -d' ' -f1`\"," >> $TF +echo " \"ref\":\"$1\"," >> $TF +echo " \"hash\":\"$3\"," >> $TF +echo " \"saifile_len\":$SJL," >> $TF +echo -n " \"saifile\":\"" >> $TF +# disallow any nested JSON monkey business by base64-encoding it +cat .sai.json.b64 >> $TF +echo "\"" >> $TF +echo "}" >> $TF + +HM=`cat $TF | sha256hmac -k /etc/sai/private/lws-sai-notification-token | cut -d' ' -f1` + +if [ $SJL = "0" ] ; then + echo "No saifile" + exit 0 +fi + +cat $TF +echo $HM + +curl --header "authorization: sai sha256=$HM" \ + -F"file=@$TF;type=application/json" \ + -A "sai-notifier" \ + https://warmcat.com/sai/update-hook +#curl --header "X-Sai-Signature: sha256=$HM" -F"file=@$TF;name=notification;type=application/json" -A "sai-notifier" http://127.0.0.1:4444 + +rm -f $TF + +exit 0 + diff --git a/scripts/builder-conf b/scripts/builder-conf new file mode 100644 index 0000000..c5ea5c1 --- /dev/null +++ b/scripts/builder-conf @@ -0,0 +1,14 @@ +{ + "perms": "sai:nobody", + "home": "/home/sai", + "host": "myhostname", + + "platforms": [ + { + "name": "linux-ubuntu-xenial-amd64", + "instances": 1, + "masters": [ "wss://warmcat.com/sai/builder" ] + } + ] +} + diff --git a/scripts/gen-selfsigned-certs.sh b/scripts/gen-selfsigned-certs.sh index 261826b..2ea08bd 100755 --- a/scripts/gen-selfsigned-certs.sh +++ b/scripts/gen-selfsigned-certs.sh @@ -5,13 +5,20 @@ if [ ! -z "$1" ] ; then DEST=$1 fi -sudo openssl ecparam -name secp256r1 -genkey -out $DEST/sai-selfsigned-key.pem +sudo openssl ecparam -name secp256r1 -genkey -out $DEST/sai-selfsigned-key.pem.1 echo -e "\n\n\n\n\n\n" | \ sudo openssl req -new -x509 \ -sha256 \ -subj "/CN=localhost" \ -days 10000 \ - -key $DEST/sai-selfsigned-key.pem \ + -key $DEST/sai-selfsigned-key.pem.1 \ -out $DEST/sai-selfsigned-cert.pem + +# +# mbedtls can't handle the extra EC parameters stanza +# +sudo sed '/^-----BEGIN\ EC\ PARAMETERS-----/,/^-----END\ EC\ PARAMETERS-----/{/^#/!{/^\$/!d}}' $DEST/sai-selfsigned-key.pem.1 > sai-selfsigned-key.pem +sudo cp sai-selfsigned-key.pem $DEST +sudo rm sai-selfsigned-key.pem echo diff --git a/scripts/sai-builder.service b/scripts/sai-builder.service new file mode 100644 index 0000000..96f3c30 --- /dev/null +++ b/scripts/sai-builder.service @@ -0,0 +1,10 @@ +[Unit] +Description=Sai Builder + +[Service] +Environment="LWS_NTP_SERVER=192.168.4.1" +ExecStart=/usr/local/bin/sai-builder + +[Install] +WantedBy=multi-user.target + diff --git a/scripts/sai-master.service b/scripts/sai-master.service new file mode 100644 index 0000000..041c738 --- /dev/null +++ b/scripts/sai-master.service @@ -0,0 +1,13 @@ +[Unit] +Description=Sai Master +After=syslog.target + +[Service] +ExecStart=/usr/local/bin/sai-master +ExecReload=/usr/bin/kill -HUP $MAINPID +ExecStop=/usr/bin/killall sai-master + +[Install] +WantedBy=multi-user.target + + diff --git a/src/builder/CMakeLists.txt b/src/builder/CMakeLists.txt new file mode 100644 index 0000000..62d9b30 --- /dev/null +++ b/src/builder/CMakeLists.txt @@ -0,0 +1,89 @@ +set(CPACK_DEBIAN_BUILDER_PACKAGE_NAME "sai-builder") + +set(SRCS + b-sai.c + b-conf.c + b-comms.c + b-mirror.c + b-nspawn.c + b-task.c + b-artifacts.c + b-logproxy.c +) + +set(requirements 1) +require_lws_config(LWS_WITH_CLIENT 1 requirements) +require_lws_config(LWS_WITH_SPAWN 1 requirements) +require_lws_config(LWS_WITH_STRUCT_JSON 1 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) +require_lws_config(LWS_WITH_DIR 1 requirements) + +if (requirements) + add_executable("sai-builder" ${SRCS}) + if (APPLE) + set_property(TARGET sai-builder PROPERTY MACOSX_RPATH YES) + endif() + + # + # libgit2 paths (builder) + # + if (SAI_BUILDER) + # + # libgit2 paths + # + find_path( SAI_GIT2_INC_PATH NAMES "git2.h") + find_library(SAI_GIT2_LIB_PATH NAMES "git2") + + if (SAI_GIT2_INC_PATH AND SAI_GIT2_LIB_PATH) + set(SAI_DEPLIBS "${SAI_GIT2_LIB_PATH}" ${SAI_DEPLIBS}) + include_directories(BEFORE "${SAI_GIT2_INC_PATH}") + else() + message(FATAL_ERROR " Unable to find libgit2") + endif() + + target_link_libraries("sai-builder" ${SAI_GIT2_LIB_PATH}) + endif() + + set(CMAKE_REQUIRED_LIBRARIES ${SAI_GIT2_LIB_PATH}) + + CHECK_C_SOURCE_COMPILES("#include <git2.h> + int main(void) { + const git_error *e = git_error_last(); + return !!e->message; + }" SAI_HAVE_LIBGIT2_GIT_ERROR) + + if (SAI_HAVE_LIBGIT2_GIT_ERROR) + set(CMAKE_C_FLAGS "-DSAI_HAVE_LIBGIT2_GIT_ERROR ${CMAKE_C_FLAGS}") + endif() + + + if (SAI_LWS_INC_PATH) + target_include_directories("sai-builder" PRIVATE ${SAI_LWS_INC_PATH}) + endif() + + if (HAS_LIBCAP) + find_library(CAP_LIB_PATH NAMES "cap") + endif() + + target_link_libraries("sai-builder" ${SAI_LWS_LIB_PATH}) + + if (LWS_OPENSSL_LIBRARIES) + target_link_libraries("sai-builder" ${LWS_OPENSSL_LIBRARIES}) + endif() + + if (SAI_EXT_PTHREAD_LIBRARIES) + target_link_libraries("sai-builder" ${SAI_EXT_PTHREAD_LIBRARIES}) + endif() + if (HAS_LIBCAP) + target_link_libraries("sai-builder" ${CAP_LIB_PATH}) + endif() + + if (MSVC OR WIN32) + target_link_libraries("sai-builder" ws2_32.lib userenv.lib psapi.lib iphlpapi.lib) + endif() + + install(TARGETS "sai-builder" + RUNTIME DESTINATION "${BIN_DIR}" COMPONENT builder) + +endif(requirements) +include(CPack) diff --git a/src/builder/b-artifacts.c b/src/builder/b-artifacts.c new file mode 100644 index 0000000..b16767f --- /dev/null +++ b/src/builder/b-artifacts.c @@ -0,0 +1,183 @@ +/* + * sai-builder com-warmcat-sai client protocol implementation + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * This deals with POSTing artifacts to the related sai-master, using a secret + * that was passed to the builder as part of the task that was allocated + */ + +#include <libwebsockets.h> + +#include "b-private.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#ifdef _MSC_VER +/* don't complain about our use of open */ +#pragma warning(disable:4996) +#endif + +static int +saib_artifact_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ + // sai_artifact_t *ap = (sai_artifact_t *)userobj; + + return 0; +} + +static int +saib_artifact_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, + size_t *len, int *flags) +{ + sai_artifact_t *ap = (sai_artifact_t *)userobj; + lws_struct_serialize_t *js; + size_t w; + int n; + + *flags = 0; + + if (!ap->sent_json) { + *flags |= LWSSS_FLAG_SOM; + + js = lws_struct_json_serialize_create( + lsm_schema_json_map_artifact, + LWS_ARRAY_SIZE(lsm_schema_json_map_artifact), + 0, ap); + if (!js) + return -1; + + lws_struct_json_serialize(js, buf, *len, &w); + *len = w; + + ap->sent_json = 1; + lws_ss_request_tx(ap->ss); + lwsl_notice("%s: sent JSON %s\n", __func__, (const char *)buf); + + return 0; + } + + if (ap->fd == -1) + return 1; /* nothing to send */ + + n = read(ap->fd, buf, +#if defined(WIN32) + (unsigned int) +#endif + *len); + if (n < 0) { + lwsl_err("%s: artifact read failed: fd: %d, errno: %d\n", + __func__, ap->fd, errno); + *len = 0; + close(ap->fd); + ap->fd = -1; + return -1; + } + if (!n) { + lwsl_notice("%s: file EOF\n", __func__); + return 1; /* nothing to send */ + } + + ap->ofs += n; + lwsl_debug("%s: %p: writing %d at +%llu / %llu\n", __func__, ap->ss, n, + (unsigned long long)ap->ofs, (unsigned long long)ap->len); + *len = (size_t)n; + if (ap->ofs == ap->len) { + lwsl_notice("%s: reached logical end of artifact\n", __func__); + *flags |= LWSSS_FLAG_EOM; + close(ap->fd); + ap->fd = -1; + } + + /* even if we finished, we want to come back to close */ + lws_ss_request_tx(ap->ss); + + return 0; +} + +static int +saib_artifact_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + sai_artifact_t *ap = (sai_artifact_t *)userobj; + struct stat s; + + lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state), + (unsigned int)ack); + + switch (state) { + case LWSSSCS_CREATING: + // lwsl_notice("%s: CREATING '%s'\n", __func__, ap->path); + ap->fd = open((const char *)ap->opaque_data, O_RDONLY +#if defined(WIN32) + /* ugh */ + | _O_BINARY +#endif + ); + if (ap->fd == -1) + return -1; + if (fstat(ap->fd, &s)) { + close(ap->fd); + ap->fd = -1; + return -1; + } + ap->len = s.st_size; + break; + + case LWSSSCS_DESTROYING: + + // lwsl_notice("%s: LWSSSCS_DESTROYING\n", __func__); + /* + * We clean up everything about the upload here + */ + lws_dll2_remove(&ap->list); + if (ap->fd != -1) { + close(ap->fd); + ap->fd = -1; + } + unlink(ap->path); + break; + + case LWSSSCS_CONNECTED: + // lwsl_user("%s: CONNECTED: %p\n", __func__, ap->ss); + lws_ss_request_tx_len(ap->ss, (unsigned long)ap->len); + break; + + case LWSSSCS_DISCONNECTED: + // lwsl_notice("%s: LWSSSCS_DISCONNECTED\n", __func__); + /* don't retry */ + return -1; + + default: + break; + } + + return 0; +} + +const lws_ss_info_t ssi_sai_artifact = { + .handle_offset = offsetof(sai_artifact_t, ss), + .opaque_user_data_offset = offsetof(sai_artifact_t, opaque_data), + .rx = saib_artifact_rx, + .tx = saib_artifact_tx, + .state = saib_artifact_state, + .user_alloc = sizeof(sai_artifact_t), + .streamtype = "sai_artifact" +}; diff --git a/src/builder/b-comms.c b/src/builder/b-comms.c new file mode 100644 index 0000000..3acb7f6 --- /dev/null +++ b/src/builder/b-comms.c @@ -0,0 +1,571 @@ +/* + * sai-builder com-warmcat-sai client protocol implementation + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> + +#include "b-private.h" + +#include "../common/struct-metadata.c" + +static int +saib_m_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ + struct sai_plat_master *spm = (struct sai_plat_master *)userobj; + //struct sai_plat *sp = (struct sai_plat *)spm->sai_plat; + + lwsl_info("%s: len %d, flags: %d\n", __func__, (int)len, flags); + lwsl_hexdump_info(buf, len); + + if (saib_ws_json_rx_builder(spm, buf, len)) + return 1; + + return 0; +} + +/* + * We come here for every platform's threadpool sync + */ + +static int +tp_sync_check(struct lws_dll2 *d, void *user) +{ + sai_plat_t *sp = lws_container_of(d, sai_plat_t, sai_plat_list); + struct sai_plat_master *spm = (struct sai_plat_master *)user; + struct sai_nspawn *ns; + int n, soe; + void *vp; + + /* + * Let's look into every nspawn for each platform then... + */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + sp->nspawn_owner.head) { + + ns = lws_container_of(d, struct sai_nspawn, list); + soe = ns->state; + + /* + * We can't deal with nspawns bound to a different master or + * nspawns not with an active threadpool task + */ + + if (!ns->tp_task || spm != ns->spm) + goto next; + + /* + * We may not be the only threadpool task that wants + * to sync... so bear in mind we want to loop after + * handling this particular one + */ + + // lwsl_notice("%s: tp svc, state %d '%s'\n", __func__, + // ns->state, ns->pending_mirror_log); + + /* + * We got here by threadpool sync... logify the + * >saib> message from the thread... + */ + + if (ns->pending_mirror_log[0]) { + lwsl_notice("%s: logging %s\n", __func__, + ns->pending_mirror_log); + saib_log_chunk_create(ns, ns->pending_mirror_log, + strlen(ns->pending_mirror_log), 3); + ns->pending_mirror_log[0] = 0; + } + + //(soe == NSSTATE_WAIT_REMOTE_MIRROR || + // soe == NSSTATE_FAILED || + // soe == NSSTATE_CHECKOUT || + // soe == NSSTATE_CHECKEDOUT) && + + n = lws_threadpool_task_status(ns->tp_task, &vp); + lwsl_info("%s: WRITEABLE: ss=%p: " + "task %p, priv %p, status %d\n", __func__, spm->ss, + ns->tp_task, vp, n); + switch (n) { + case LWS_TP_STATUS_FINISHED: + case LWS_TP_STATUS_STOPPED: + case LWS_TP_STATUS_QUEUED: + case LWS_TP_STATUS_RUNNING: + case LWS_TP_STATUS_STOPPING: + goto next; + + case LWS_TP_STATUS_SYNCING: + /* + * This is what the threadpool thread wants to hear from + * us in order to continue on. The choice in the second + * arg is whether to ask the thread to stop or not. + * + * MIRROR: let thread continue on to CHECKOUT + * WAIT_REMOTE_MIRROR: continue to wait + * CHECKOUT: go back into CHECKOUT + * FAILED or CHECKEDOUT: we're done, stop the thread + * + * This wakes the stalled task, we can't read its + * state after this + */ + + lws_threadpool_task_sync(ns->tp_task, + soe == NSSTATE_CHECKEDOUT || + soe == NSSTATE_FAILED); + + if (soe != NSSTATE_CHECKEDOUT && + soe != NSSTATE_FAILED) { + lwsl_notice("%s: task still going, status %d, " + "sp = %p, sp->ss = %p\n", + __func__, n, sp, ns->spm->ss); + goto next; + } + /* + * We asked for the task to stop... let's move on + * while that's happening + */ + break; + + default: + return 1; + } + + /* + * The thread is over... either FAILED... + */ + + if (soe == NSSTATE_FAILED) { + lwsl_notice("%s: thread over with FAILED\n", + __func__); + saib_set_ns_state(ns, NSSTATE_FAILED); + saib_task_grace(ns); + goto next; + } + + /* + * ...or we did the mirror and let's spawn the actual task now + */ + + lwsl_notice("%s: Destroying checkout thread, spawning task\n", + __func__); + ns->tp_task = NULL; + + lws_sul_cancel(&ns->sul_task_cancel); + + saib_set_ns_state(ns, NSSTATE_BUILD); + + n = saib_spawn(ns); + if (!n) + goto next; + + lwsl_err("%s: spawn failed: %d\n", __func__, n); + + lwsl_notice("%s: failing spawn cleanly\n", __func__); + saib_set_ns_state(ns, NSSTATE_FAILED); + saib_task_grace(ns); + +next: + + if (ns->artifact_owner.head) { + /* + * This nspawn has an outstanding artifact to upload + */ + } + + lwsl_debug("%s: next tp sync\n", __func__); + + } lws_end_foreach_dll_safe(d, d1); + + return 0; +} + +/* + * We cover requested tx for any instance of a platform that can takes tasks + * from the same master... it means just by coming here, no particular + * platform / sai_plat is implied... + */ + +static int +saib_m_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, + int *flags) +{ + struct sai_plat_master *spm = (struct sai_plat_master *)userobj; + uint8_t *start = buf, *end = buf + (*len) - 1, *p = start; + struct ws_capture_chunk *chunk; + struct sai_plat *sp = NULL; + lws_struct_serialize_t *js; + lws_dll2_t *star, *walk; + struct sai_nspawn *ns; + size_t w = 0; + int n = 0; + + /* + * We are the ss / wsi that any threadpool instances on any platform + * with tasks for this master are trying to sync to. We need to handle + * and resume them all. + */ + + lws_dll2_foreach_safe(&builder.sai_plat_owner, spm, tp_sync_check); + + /* + * Any builder state updates / rejections to process? + */ + + if (spm->rejection_list.count) { + struct lws_dll2 *d = lws_dll2_get_head(&spm->rejection_list); + struct sai_rejection *rej = + lws_container_of(d, struct sai_rejection, list); + + lwsl_notice("%s: issuing %s\n", __func__, + rej->task_uuid[0] ? "task rejection" : "load update"); + + js = lws_struct_json_serialize_create(lsm_schema_json_task_rej, + LWS_ARRAY_SIZE(lsm_schema_json_task_rej), 0, rej); + if (!js) + return -1; + + n = lws_struct_json_serialize(js, start, + lws_ptr_diff(end, start), &w); + lws_struct_json_serialize_destroy(&js); + + lwsl_hexdump_notice(start, w); + + n = (int)w; + + lws_dll2_remove(&rej->list); + free(rej); + + lws_ss_request_tx(spm->ss); + goto sendify; + } + + switch (spm->phase) { + case PHASE_BUILDING: + case PHASE_IDLE: + break; + + default: + + /* + * Update master with platform status + */ + + js = lws_struct_json_serialize_create(lsm_schema_map_plat, + LWS_ARRAY_SIZE(lsm_schema_map_plat), 0, + &builder.sai_plat_owner); + if (!js) + return -1; + + n = lws_struct_json_serialize(js, start, + lws_ptr_diff(end, start), &w); + lws_struct_json_serialize_destroy(&js); + + sp = (sai_plat_t *)builder.sai_plat_owner.head; + lwsl_hexdump_warn(sp, sizeof(*sp)); + lwsl_hexdump_notice(start, w); + + *len = (int)w; + spm->phase = PHASE_IDLE; + *flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM; + + if (spm->logs_in_flight) + lws_ss_request_tx(spm->ss); + + return 0; + } + + /* + * Are there some logs to dump? + */ + + if (!spm->logs_in_flight) + return 1; /* nothing to send */ + + /* + * Yes somebody has some logs... since we handle all logs on any + * platform doing tasks for the same master, we have to take care not + * to favour draining logs for any busy tasks over letting others + * getting a chance at the mic. If we just scan for guys with logs + * from the start of the list each time, we will never deal with guys + * far from the list head while anybody closer has logs. + * + * For that reason we remember the last dll2 who wrote logs, and start + * looking for the next nspawn with pending logs after him next time. + * + * That requires statefully rotating through... + * + * platform in platform list : nspawn in platform's nspawn list + * = = + * spm->last_logging_platform : spm->last_logging_nspawn + * + * ... filtered for nspawns associated with our spm / master SS link. + * + * The platforms and nspawns are allocated at conf-time statically. + */ + + star = NULL; + do { + if (!spm->last_logging_nspawn) { + /* start at the start */ + sp = spm->last_logging_platform = lws_container_of( + builder.sai_plat_owner.head, + sai_plat_t, sai_plat_list); + walk = spm->last_logging_nspawn = sp->nspawn_owner.head; + } else { + /* if we can move on, move on */ + sp = spm->last_logging_platform; + walk = spm->last_logging_nspawn->next; + } + + /* if no more nspawns, try moving to next platform */ + if (!walk) { + if (!sp->sai_plat_list.next) + /* if no more platforms, wrap around to first */ + sp = spm->last_logging_platform = + lws_container_of( + builder.sai_plat_owner.head, + sai_plat_t, sai_plat_list); + else + sp = spm->last_logging_platform = + lws_container_of( + sp->sai_plat_list.next, + sai_plat_t, sai_plat_list); + + /* use the first nspawn in our new platform */ + walk = sp->nspawn_owner.head; + } + + spm->last_logging_nspawn = walk; + + if (walk == star) { + lwsl_notice("%s: did not find logs: %d expected\n", + __func__, spm->logs_in_flight); + return 1; /* nothing to do */ + } + + if (!star) /* take first usable one as the starting point */ + star = walk; + + ns = lws_container_of(walk, struct sai_nspawn, list); + if (spm != ns->spm || !ns->chunk_cache.count || !ns->chunk_cache.tail) + continue; + + /* + * We're going to process a chunk + */ + + chunk = lws_container_of(ns->chunk_cache.tail, + struct ws_capture_chunk, list); + + lws_dll2_remove(&chunk->list); + + if (ns->task) { + + n = lws_snprintf((char *)p, lws_ptr_diff(end, p), + "{\"schema\":\"com-warmcat-sai-logs\"," + "\"task_uuid\":\"%s\", \"timestamp\": %llu," + "\"channel\": %d, \"len\": %d, ", + ns->task->uuid, (unsigned long long)lws_now_usecs(), + chunk->stdfd, (int)chunk->len); + + if (ns->finished_when_logs_drained && !ns->chunk_cache.count) + /* + * Let the last guy report the finished state + */ + n += lws_snprintf((char *)p + n, lws_ptr_diff(end, p) - n, + "\"finished\":%d,", ns->retcode); + + n += lws_snprintf((char *)p + n, lws_ptr_diff(end, p) - n, + "\"log\":\""); + + // puts((const char *)&chunk[1]); + // puts((const char *)start); + + n += lws_b64_encode_string((const char *)&chunk[1], + (int)chunk->len, (char *)&start[n], + (int)lws_ptr_diff(end, p) - n - 5); + + p[n++] = '\"'; + p[n++] = '}'; + p[n] = '\0'; + // puts((const char *)start); + } + + ns->chunk_cache_size -= sizeof(*chunk) + chunk->len; + free(chunk); + + spm->logs_in_flight--; + lwsl_debug("%s: spm logs_in_flight %d\n", __func__, + spm->logs_in_flight); + + if (ns->finished_when_logs_drained && !ns->chunk_cache.count) { + + /* + * He's in DONE state, and the draining he was waiting + * for has now happened + */ + lwsl_notice("%s: drained and empty\n", __func__); + saib_task_destroy(ns); + } + + break; + + } while (walk != star); + + +sendify: + + *flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM; + *len = n; + + if (spm->phase != PHASE_IDLE || spm->logs_in_flight) + lws_ss_request_tx(spm->ss); + + if (!n) + return 1; + + return 0; +} + +static int +cleanup_on_ss_destroy(struct lws_dll2 *d, void *user) +{ + struct sai_plat_master *spm = (struct sai_plat_master *)user; + sai_plat_t *sp = lws_container_of(d, sai_plat_t, sai_plat_list); + + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + sp->nspawn_owner.head) { + struct sai_nspawn *ns = + lws_container_of(d, struct sai_nspawn, list); + + if (ns->spm == spm && ns->tp) { + lwsl_notice("%s: calling threadpool_destroy\n", __func__); + lws_threadpool_finish(ns->tp); + lws_threadpool_destroy(ns->tp); + ns->tp = NULL; + ns->tp_task = NULL; + } + } lws_end_foreach_dll_safe(d, d1); + + return 0; +} + +static int +cleanup_on_ss_disconnect(struct lws_dll2 *d, void *user) +{ + struct sai_plat_master *spm = (struct sai_plat_master *)user; + sai_plat_t *sp = lws_container_of(d, sai_plat_t, sai_plat_list); + struct ws_capture_chunk *cc; + + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + sp->nspawn_owner.head) { + struct sai_nspawn *ns = lws_container_of(d, + struct sai_nspawn, list); + + if (ns->spm == spm) { + + /* + * This pss is about to go away, make sure the ns + * can't reference it any more no matter what happens + */ + + ns->spm = NULL; + + //if (ns->lsp) + // lws_spawn_piped_kill_child_process(ns->lsp); + + /* clean up any capture chunks */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, e, e1, + ns->chunk_cache.head) { + cc = lws_container_of(e, + struct ws_capture_chunk, list); + lws_dll2_remove(&cc->list); + free(cc); + } lws_end_foreach_dll_safe(e, e1); + + } + } lws_end_foreach_dll_safe(d, d1); + + return 0; +} + +static int +saib_m_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + struct sai_plat_master *spm = (struct sai_plat_master *)userobj; + + lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state), + (unsigned int)ack); + + switch (state) { + case LWSSSCS_DESTROYING: + + /* + * If the logical SS itself is going down, every platform that + * used us to connect to their master and has nspawns are also + * going down + */ + lws_dll2_foreach_safe(&builder.sai_plat_owner, spm, + cleanup_on_ss_destroy); + + break; + + case LWSSSCS_CONNECTED: + lwsl_user("%s: CONNECTED: %p\n", __func__, spm->ss); + spm->phase = PHASE_START_ATTACH; + lws_ss_request_tx(spm->ss); + break; + + case LWSSSCS_DISCONNECTED: + /* + * clean up any ongoing spawns related to this connection + */ + + lwsl_user("%s: DISCONNECTED\n", __func__); + lws_dll2_foreach_safe(&builder.sai_plat_owner, spm, + cleanup_on_ss_disconnect); + break; + + case LWSSSCS_ALL_RETRIES_FAILED: + break; + + case LWSSSCS_QOS_ACK_REMOTE: + lwsl_notice("%s: LWSSSCS_QOS_ACK_REMOTE\n", __func__); + break; + + default: + break; + } + + return 0; +} + +const lws_ss_info_t ssi_sai_builder = { + .handle_offset = offsetof(struct sai_plat_master, ss), + .opaque_user_data_offset = offsetof(struct sai_plat_master, opaque_data), + .rx = saib_m_rx, + .tx = saib_m_tx, + .state = saib_m_state, + .user_alloc = sizeof(struct sai_plat_master), + .streamtype = "sai_builder" +}; diff --git a/src/builder/b-conf.c b/src/builder/b-conf.c new file mode 100644 index 0000000..f037a5f --- /dev/null +++ b/src/builder/b-conf.c @@ -0,0 +1,427 @@ +/* + * sai-builder conf.c + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <time.h> +#include <fcntl.h> + +#include "b-private.h" + +/* global part */ + +static const char * const paths_global[] = { + "home", + "perms", + "host", +}; + +enum enum_paths_global { + LEJPM_HOME, + LEJPM_PERMS, + LEJPM_HOST, +}; + +/* platform-related part */ + +static const char * const paths[] = { + + "platforms[].name", + "platforms[].instances", + "platforms[].env[].*", + "platforms[].env[]", + "platforms[].masters", + "platforms[]", +}; + +enum enum_paths { + + LEJPM_PLATFORMS_NAME, + LEJPM_PLATFORMS_INSTANCES, + LEJPM_PLATFORMS_ENV_ITEM, + LEJPM_PLATFORMS_ENV, + LEJPM_PLATFORMS_MASTERS, + LEJPM_PLATFORMS, +}; + +struct jpargs { + struct sai_builder *builder; + + struct sai_nspawn *nspawn; + struct sai_plat *sai_plat; + + struct sai_platform *pl; + + int next_master_index; + int next_plat_index; +}; + +static signed char +saib_conf_cb(struct lejp_ctx *ctx, char reason) +{ + struct jpargs *a = (struct jpargs *)ctx->user; + sai_plat_master_ref_t *mref; + struct sai_plat_master *cm; + struct lws_ss_handle *h; + const char **pp, *pq; + char temp[65]; + int n; + +#if 0 + lwsl_notice(" %d: %s (%d)\n", reason, ctx->path, ctx->path_match); + for (n = 0; n < ctx->wildcount; n++) + lwsl_notice(" %d\n", ctx->wild[n]); +#endif + + if (reason == LEJPCB_OBJECT_START) { + switch (ctx->path_match - 1) { + + case LEJPM_PLATFORMS: + /* + * Create the sai_plat object + */ + a->sai_plat = lwsac_use_zero(&a->builder->conf_head, + sizeof(*a->sai_plat), 4096); + if (!a->sai_plat) + return -1; + + a->sai_plat->instances = 1; /* default */ + + lws_dll2_add_tail(&a->sai_plat->sai_plat_list, + &a->builder->sai_plat_owner); + break; + + default: + return 0; + } + } + + /* we only match on the prepared path strings */ + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + if (reason == LEJPCB_VAL_NUM_INT) { + n = atoi(ctx->buf); + switch (ctx->path_match - 1) { + + case LEJPM_PLATFORMS_INSTANCES: + lwsl_notice("%s: instances %d\n", __func__, n); + a->sai_plat->instances = n; + /* + * Instantiate n nspawns bound to this platform + */ + + for (n = 0; n < a->sai_plat->instances; n++) { + struct sai_nspawn *ns = malloc(sizeof(*ns)); + if (!ns) + return -1; + + memset(ns, 0, sizeof(*ns)); + ns->instance_idx = n; + ns->builder = &builder; + ns->sp = a->sai_plat; + ns->task = NULL; + lws_dll2_add_head(&ns->list, + &a->sai_plat->nspawn_owner); + } + break; + + default: + break; + } + + return 0; + } + + if (reason != LEJPCB_VAL_STR_END) + return 0; + + /* only the end part of the string, where we know the length */ + + switch (ctx->path_match - 1) { + + case LEJPM_PLATFORMS_ENV: + lwsl_notice("env %s %s\n", ctx->path, ctx->buf); + return 0; + // break; + + case LEJPM_PLATFORMS_NAME: + n = lws_snprintf(temp, sizeof(temp), "%s.%.*s", + builder.host, ctx->npos, ctx->buf); + a->sai_plat->name = lwsac_use(&a->builder->conf_head, n + 1, 512); + memcpy((char *)a->sai_plat->name, temp, n + 1); + lwsl_notice("%s: platform: %.*s, name %s\n", __func__, + ctx->npos, ctx->buf, a->sai_plat->name); + pp = &a->sai_plat->platform; + a->sai_plat->index = a->next_plat_index++; + break; + + case LEJPM_PLATFORMS_MASTERS: + + mref = lwsac_use_zero(&a->builder->conf_head, sizeof(*mref), + 512); + + /* + * The builder as a whole maintains only one SS connection to + * each master. If there are multiple platforms supported by + * the builder that want to accept tasks from the same master, + * only the first platform creates the SS to the master, and + * the others just use that. + * + * The sai_plat_master is instantiated as the ss userdata. + * + * So let's see if we already have the connection created + * for this master... + */ + + lws_start_foreach_dll(struct lws_dll2 *, p, + a->builder->sai_plat_master_owner.head) { + cm = lws_container_of(p, sai_plat_master_t, list); + + if (!strncmp(ctx->buf, cm->url, ctx->npos)) { + /* we already have a logical connection... */ + mref->spm = cm; + cm->refcount++; + lws_dll2_add_tail(&mref->list, + &a->sai_plat->masters); + + return 0; + } + } lws_end_foreach_dll(p); + + /* + * This is the first plat that wants to talk to this master, + * we need to create the logical SS connection + */ + + if (lws_ss_create(builder.context, 0, &ssi_sai_builder, 0, &h, + NULL, NULL)) { + lwsl_err("%s: failed to create secure stream\n", + __func__); + return -1; + } + + cm = lws_ss_to_user_object(h); /* the sai_plat_master object */ + cm->index = a->next_master_index++; + + /* hook the ss up to the master url */ + + cm->url = lwsac_use(&a->builder->conf_head, + 2 *(ctx->npos + 1), 512); + memcpy((char *)cm->url, ctx->buf, ctx->npos); + ((char *)cm->url)[ctx->npos] = '\0'; + + lwsl_notice("%s: binding ss to %s\n", __func__, cm->url); + lws_ss_set_metadata(h, "url", cm->url, strlen(cm->url)); + + pq = cm->url; + while (*pq && (pq[0] != '/' || pq[1] != '/')) + pq++; + + if (*pq) { + n = 0; + pq += 2; + while (pq[n] && pq[n] != '/') + n++; + } else { + pq = cm->url; + n = ctx->npos; + } + + cm->name = cm->url + ctx->npos + 1; + memcpy((char *)cm->name, pq, n); + ((char *)cm->name)[n] = '\0'; + + while (strchr(cm->name, '.')) + *strchr(cm->name, '.') = '_'; + while (strchr(cm->name, '/')) + *strchr(cm->name, '/') = '_'; + + /* add us to the builder list of unique masters */ + lws_dll2_add_head(&cm->list, &a->builder->sai_plat_master_owner); + + /* add us to this platforms's list of masters it accepts */ + mref->spm = cm; + cm->refcount++; + lws_dll2_add_tail(&mref->list, &a->sai_plat->masters); + + lws_ss_client_connect(h); + + return 0; + + default: + return 0; + } + + *pp = lwsac_use(&a->builder->conf_head, ctx->npos + 1, 512); + if (!*pp) + return 1; + memcpy((char *)(*pp), ctx->buf, ctx->npos); + ((char *)(*pp))[ctx->npos] = '\0'; + + return 0; +} + +static signed char +saib_conf_global_cb(struct lejp_ctx *ctx, char reason) +{ + struct jpargs *a = (struct jpargs *)ctx->user; + const char **pp; +#if 0 + int n; + + lwsl_notice("%s: reason: %d, path: %s, match %d\n", __func__, + reason, ctx->path, ctx->path_match); + for (n = 0; n < ctx->wildcount; n++) + lwsl_notice(" %d\n", ctx->wild[n]); +#endif + + + /* we only match on the prepared path strings */ + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + if (reason != LEJPCB_VAL_STR_END) + return 0; + + /* only the end part of the string, where we know the length */ + + switch (ctx->path_match - 1) { + case LEJPM_HOME: + pp = &a->builder->home; + break; + + case LEJPM_PERMS: + pp = &a->builder->perms; + break; + + case LEJPM_HOST: + pp = &a->builder->host; + break; + + default: + return 0; + } + + *pp = lwsac_use(&a->builder->conf_head, ctx->npos + 1, 512); + if (!*pp) + return 1; + memcpy((char *)(*pp), ctx->buf, ctx->npos); + ((char *)(*pp))[ctx->npos] = '\0'; + + return 0; +} + +int +saib_config_global(struct sai_builder *builder, const char *d) +{ + unsigned char buf[128]; + struct lejp_ctx ctx; + int n, m, fd; + struct jpargs a; + + memset(&a, 0, sizeof(a)); + a.builder = builder; + +#if defined(WIN32) + lws_snprintf((char *)buf, sizeof(buf) - 1, "%s\\conf", d); +#else + lws_snprintf((char *)buf, sizeof(buf) - 1, "%s/conf", d); +#endif + + fd = lws_open((char *)buf, O_RDONLY); + if (fd < 0) { + lwsl_err("Cannot open %s\n", (char *)buf); + return 2; + } + lwsl_info("%s: %s\n", __func__, (char *)buf); + lejp_construct(&ctx, saib_conf_global_cb, &a, + paths_global, LWS_ARRAY_SIZE(paths_global)); + + do { + n = read(fd, buf, sizeof(buf)); + if (!n) + break; + + m = lejp_parse(&ctx, buf, n); + } while (m == LEJP_CONTINUE); + + close(fd); + n = ctx.line; + lejp_destruct(&ctx); + + return 0; +} + +int +saib_config(struct sai_builder *builder, const char *d) +{ + unsigned char buf[128]; + struct lejp_ctx ctx; + int n, m, fd; + struct jpargs a; + + memset(&a, 0, sizeof(a)); + a.builder = builder; + +#if defined(WIN32) + lws_snprintf((char *)buf, sizeof(buf) - 1, "%s\\conf", d); +#else + lws_snprintf((char *)buf, sizeof(buf) - 1, "%s/conf", d); +#endif + + fd = lws_open((char *)buf, O_RDONLY); + if (fd < 0) { + lwsl_err("Cannot open %s\n", (char *)buf); + return 2; + } + lwsl_notice("%s: %s\n", __func__, (char *)buf); + lejp_construct(&ctx, saib_conf_cb, &a, paths, LWS_ARRAY_SIZE(paths)); + + do { + n = read(fd, buf, sizeof(buf)); + if (!n) + break; + + m = lejp_parse(&ctx, buf, n); + } while (m == LEJP_CONTINUE); + + close(fd); + n = ctx.line; + lejp_destruct(&ctx); + + if (m < 0) { + lwsl_err("%s/conf(%u): parsing error %d: %s\n", d, n, m, + lejp_error_to_string(m)); + return 2; + } + + lwsl_notice("%s: parsing completed\n", __func__); + + return 0; +} + +void +saib_config_destroy(struct sai_builder *builder) +{ + lwsac_free(&builder->conf_head); +} diff --git a/src/builder/b-logproxy.c b/src/builder/b-logproxy.c new file mode 100644 index 0000000..0fb9732 --- /dev/null +++ b/src/builder/b-logproxy.c @@ -0,0 +1,61 @@ +/* + * sai-builder - logproxy + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * We create a listening UDS socket for each platform / instance / log channel + * so subprocesses can just dump raw log content and have it understood and + * handled correctly, without having to worry about datagram size or coalescing. + */ +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include "b-private.h" + +static int +callback_logproxy(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct saib_logproxy *slp = lws_vhost_user(lws_get_vhost(wsi)); + + switch (reason) { + + case LWS_CALLBACK_RAW_RX: + if (!slp->ns->spm) + break; + + /* + * We just turn whatever comes into a log of the appropriate + * channel according to the vhost it appeared on + */ + saib_log_chunk_create(slp->ns, in, len, slp->log_channel_idx); + break; + + default: + break; + } + + return 0; +} + +const struct lws_protocols protocol_logproxy = { + "protocol-logproxy", + callback_logproxy, + 0, + 2048, 2048, NULL, 0 +}; diff --git a/src/builder/b-mirror.c b/src/builder/b-mirror.c new file mode 100644 index 0000000..f83deb2 --- /dev/null +++ b/src/builder/b-mirror.c @@ -0,0 +1,671 @@ +/* + * sai-builder com-warmcat-sai client protocol implementation + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <assert.h> + +#include "b-private.h" + +#include <git2.h> + +enum { + SRFS_REQUESTING, + SRFS_PROCESSING, + SRFS_FAILED, + SRFS_SUCCEEDED, +}; + +/* + * Request to do the remote -> local mirror action + */ + +typedef struct sai_mirror_req { + lws_dll2_t list; + + struct sai_nspawn *ns; + + char path[100]; /* local mirror path */ + char url[100]; + char hash[130]; + char ref[96]; + + int state; +} sai_mirror_req_t; + +static void +sai_mirror_req_state_set(sai_mirror_req_t *req, int n) +{ + lwsl_notice("%s: req %p: %d -> %d\n", __func__, req, req->state, n); + req->state = n; +} + +enum { + SAIB_CHECKOUT_OK, + SAIB_CHECKOUT_NOT_IN_LOCAL_MIRROR, + SAIB_CHECKOUT_CHECKOUT_FAILED, +}; + +static int +sai_mirror_local_checkout(struct sai_nspawn *ns) +{ + git_checkout_options co_opts = GIT_CHECKOUT_OPTIONS_INIT; + git_fetch_options opts = GIT_FETCH_OPTIONS_INIT; + git_repository *git_repo_build_specific = NULL; + char dp[384], spec[96], *paths[] = { spec }; + git_strarray rfs = { paths, 1 }; + git_object *treeish; + git_remote *remote; + int n, tries = 2; + + /* + * Remove anything that was already in the build-specific dir + */ + + lwsl_notice("%s: rm -rf %s\n", __func__, ns->inp); + lws_dir(ns->inp, NULL, lws_dir_rm_rf_cb); + + /* + * Make sure the build-specific dir itself is left standing in there. + * + * We can only create files and dirs using the global sai:nobody + * credentials since we have dropped root long ago + */ + + mkdir(ns->inp, 0755); + + /* + * Create the build-specific git dir and init it + */ + + n = git_repository_init(&git_repo_build_specific, ns->inp, 0); + if (n) { +#if defined(SAI_HAVE_LIBGIT2_GIT_ERROR) + const git_error *e = git_error_last(); + + lwsl_err("%s: unable to init temp repo %s: %d %s\n", + __func__, ns->inp, n, e ? e->message : "?"); +#else + lwsl_err("%s: unable to init temp repo %s: %d\n", + __func__, ns->inp, n); +#endif + + return 1; + } + + /* + * Attempt to fetch the ref we are interested in from our local mirror + * + * Create a temp remote against the destination repo + */ + + if (git_remote_create_anonymous(&remote, git_repo_build_specific, + ns->path)) { + lwsl_err("%s: cant find remote %s\n", __func__, + ns->git_repo_url); + + git_repository_free(git_repo_build_specific); + return 1; + } + + lws_snprintf(spec, sizeof(spec), "ref-%s:ref-%s", ns->hash, ns->hash); + + lwsl_notice("%s: Attempting to fetch %s from mirror to %s\n", + __func__, spec, ns->inp); + + /* + * Fetch the tree from the local mirror to our local build repo + * This may take an open-ended amount of time + */ + + n = git_remote_fetch(remote, &rfs, &opts, "fetch"); + git_remote_free(remote); + if (n) { + lwsl_notice("%s: git_remote_fetch() says %d\n", __func__, n); +#if defined(SAI_HAVE_LIBGIT2_GIT_ERROR) + const git_error *e = git_error_last(); + + lwsl_err("%s: git_remote_fetch libgit err: %d %s\n", + __func__, n, e ? e->message : "?"); +#endif + git_repository_free(git_repo_build_specific); + return SAIB_CHECKOUT_NOT_IN_LOCAL_MIRROR; + } + + /* + * Check out the commit we fetched into the ephemeral local build dir... + * this should be a formality since we already retreived the ref into + * ephemeral local build dir's repo. + */ + + lws_snprintf(spec, sizeof(spec), "ref-%s", ns->hash); + n = git_revparse_single(&treeish, git_repo_build_specific, spec); + if (n) { + lwsl_notice("%s: revparse %s failed: %d\n", __func__, spec, n); + git_repository_free(git_repo_build_specific); + return SAIB_CHECKOUT_NOT_IN_LOCAL_MIRROR; + } + +again: + co_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + n = git_checkout_tree(git_repo_build_specific, treeish, &co_opts); + git_object_free(treeish); + if (n) { + if (n == GIT_EUNBORNBRANCH && tries--) { + lwsl_warn("%s: git checkout says HEAD is empty branch\n", + __func__); + + /* + * It's telling us we need to delete the local mirror + * HEAD and retry + */ + + lws_snprintf(dp, sizeof(dp), "%s/HEAD", ns->inp); + unlink(dp); + + goto again; + } + goto co_failed; + } + + lws_snprintf(spec, sizeof(spec), "refs/heads/ref-%s", ns->hash); + git_repository_set_head(git_repo_build_specific, spec); + + lwsl_notice("%s: checkout OK\n", __func__); + + git_repository_free(git_repo_build_specific); + + return SAIB_CHECKOUT_OK; + +co_failed: + lwsl_err("%s: git checkout failed: %d\n", __func__, n); + git_repository_free(git_repo_build_specific); + + return SAIB_CHECKOUT_CHECKOUT_FAILED; +} + + +/* + * We are a threadpool thread handling queueing on remote mirror "thread_repo", + * and afterwards checking out a ref from the local copy. + * + * The main git2 process just goes away and does the fetch atomically. Wrapping + * it in an extra threadpool thread lets us continue to be resposive while we're + * waiting for the remote mirror fetch. + */ + +enum lws_threadpool_task_return +saib_mirror_task(void *user, enum lws_threadpool_task_status s) +{ + struct sai_nspawn *ns = (struct sai_nspawn *)user; + sai_mirror_instance_t *mi; + sai_mirror_req_t *req; + int n, m; + + lwsl_warn("%s: entry: state %d\n", __func__, ns->state); + + mi = &builder.mi; + + if (s == LWS_TP_STATUS_STOPPING) + return LWS_TP_RETURN_STOPPED; + + switch (ns->state) { + case NSSTATE_INIT: + case NSSTATE_MOUNTING: + + case NSSTATE_STARTING_MIRROR: + lwsl_notice("%s: NSSTATE_STARTING_MIRROR\n", __func__); + ns->state = NSSTATE_CHECKOUT_SPEC; + /* fallthru */ + + case NSSTATE_CHECKOUT_SPEC: + /* + * First attempt to checkout from local mirror, in case we + * already fetched it to the local mirror for an earlier task + */ + + lwsl_notice("%s: NSSTATE_CHECKOUT_SPEC\n", __func__); + switch (sai_mirror_local_checkout(ns)) { + case SAIB_CHECKOUT_OK: + /* + * oh we have it then... do a final sync and have the + * foreground handler STOP us when we resume + */ + + lwsl_notice("%s: syncing checkout %s done\n", __func__, + ns->inp); + ns->state = NSSTATE_CHECKEDOUT; + + lws_snprintf(ns->pending_mirror_log, + sizeof(ns->pending_mirror_log), + ">saib> Local mirror checkout %s\n", ns->hash); + + return LWS_TP_RETURN_SYNC; + + case SAIB_CHECKOUT_NOT_IN_LOCAL_MIRROR: + /* + * Basically it means we have to get it from the remote + * repo into the local mirror first... clear down the + * local build dir + */ + + lws_dir(ns->inp, NULL, lws_dir_rm_rf_cb); + + /* + * Make sure the dir itself is left standing in there + */ + + mkdir(ns->inp, 0755); + + /* + * Form a request, kick the remoting thread and then + * wait on the condition in the request + */ + + req = malloc(sizeof(*req)); + if (!req) + goto fail; + + /* + * The request needs to exist with a standalone + * lifetime regardless of what's happening to the ns or + * threadpool thread while it waits, or proceeds + * asynchronously + */ + + lwsl_notice("%s: NOT_IN_LOCAL_MIRROR %s\n", __func__, ns->ref); + memset(req, 0, sizeof(*req)); + req->state = SRFS_REQUESTING; + req->ns = ns; + lws_strncpy(req->url, ns->git_repo_url, sizeof(req->url)); + lws_strncpy(req->hash, ns->hash, sizeof(req->hash)); + lws_strncpy(req->ref, ns->ref, sizeof(req->ref)); + lws_strncpy(req->path, ns->path, sizeof(req->path)); + + pthread_mutex_lock(&mi->mut); + lws_dll2_add_tail(&req->list, &mi->pending_req); + + pthread_cond_broadcast(&mi->cond); + pthread_mutex_unlock(&mi->mut); + + ns->state = NSSTATE_WAIT_REMOTE_MIRROR; + ns->mirror_wait_budget = 600; + + lws_snprintf(ns->pending_mirror_log, + sizeof(ns->pending_mirror_log), + ">saib> Starting REMOTE MIRROR FETCH\n"); + + return LWS_TP_RETURN_SYNC; + + case SAIB_CHECKOUT_CHECKOUT_FAILED: + + lws_snprintf(ns->pending_mirror_log, + sizeof(ns->pending_mirror_log), + ">saib> Checkout failed...\n"); + + goto fail; + } + + break; + + case NSSTATE_CHECKOUT: + /* + * We come here after the remote -> local mirror operation + * seemed to go well, to complete the flow by checking out + * the newly-fetched commit. We can only go OK or fail. + */ + + lwsl_notice("%s: NSSTATE_CHECKOUT\n", __func__); + m = sai_mirror_local_checkout(ns); + lwsl_notice("%s: sai_mirror_local_checkout() says %d\n", __func__, m); + switch (m) { + case SAIB_CHECKOUT_OK: + /* + * oh we have it then... do a final sync and have the + * foreground handler STOP us when we resume + */ + + lwsl_notice("%s: syncing checkout %s done\n", __func__, + ns->inp); + ns->state = NSSTATE_CHECKEDOUT; + + lws_snprintf(ns->pending_mirror_log, + sizeof(ns->pending_mirror_log), + ">saib> Remote->Local->Checked out %s\n", ns->hash); + + return LWS_TP_RETURN_SYNC; + + case SAIB_CHECKOUT_NOT_IN_LOCAL_MIRROR: + /* fallthru */ + lwsl_notice("%s: checkout not it local mirror\n", __func__); + case SAIB_CHECKOUT_CHECKOUT_FAILED: + + lws_snprintf(ns->pending_mirror_log, + sizeof(ns->pending_mirror_log), + ">saib> Checkout failed after mirror...\n"); + lwsl_notice("%s: checkout failed\n", __func__); + + goto fail; + } + break; + + case NSSTATE_CHECKEDOUT: + case NSSTATE_BUILD: + case NSSTATE_DONE: + case NSSTATE_FAILED: + break; + + case NSSTATE_WAIT_REMOTE_MIRROR: + + /* + * We are waiting for our request to be handled... we need to + * watch the req completed list at intervals. Anything on the + * completed_req owner has either completed or failed. + */ + + lwsl_notice("%s: NSSTATE_WAIT_REMOTE_MIRROR\n", __func__); + pthread_mutex_lock(&mi->mut); + + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + mi->completed_req.head) { + sai_mirror_req_t *r = lws_container_of(d, + sai_mirror_req_t, list); + + if (r->ns == ns) { + + /* + * It's our request that has completed + */ + + n = r->state; + + lws_dll2_remove(&r->list); + pthread_mutex_unlock(&mi->mut); + free(r); + + if (n == SRFS_FAILED) { + lwsl_notice("%s: mirror req failed\n", + __func__); + goto fail; + } + + /* + * Leave in NSSTATE_CHECKOUT and come back to + * continue with checking out after we have + * sync'd with comms to master + */ + + ns->state = NSSTATE_CHECKOUT; + lws_snprintf(ns->pending_mirror_log, + sizeof(ns->pending_mirror_log), + ">saib> Remote -> Local OK\n"); + + return LWS_TP_RETURN_SYNC; + } + + } lws_end_foreach_dll_safe(d, d1); + + pthread_mutex_unlock(&mi->mut); /* --- mi->mut unlock */ + +#if defined(WIN32) + Sleep(1000); +#else + sleep(1); +#endif + + if (!--ns->mirror_wait_budget) { + lws_snprintf(ns->pending_mirror_log, sizeof(ns->pending_mirror_log), + ">saib> timed out waiting for mirror\n"); + lwsl_notice("%s: timed out waiting for mirror\n", __func__); + + goto fail; + } + + lws_snprintf(ns->pending_mirror_log, sizeof(ns->pending_mirror_log), + ">saib> Waiting on MIRROR...\n"); + + return LWS_TP_RETURN_SYNC; + } + + return LWS_TP_RETURN_SYNC; + +fail: + + /* + * If we have an incomplete request on the mirror thread, remove it + */ + + pthread_mutex_lock(&mi->mut); + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + mi->pending_req.head) { + sai_mirror_req_t *r = lws_container_of(d, + sai_mirror_req_t, list); + + if (r->ns == ns) { + lws_dll2_remove(&r->list); + free(r); + } + + } lws_end_foreach_dll_safe(d, d1); + pthread_mutex_unlock(&mi->mut); + + lwsl_err("%s: failed\n", __func__); + ns->retcode = SAISPRF_EXIT | 253; + saib_set_ns_state(ns, NSSTATE_FAILED); + saib_task_grace(ns); + + if (saib_queue_task_status_update(ns->sp, ns->spm, NULL)) + return -1; + + return LWS_TP_RETURN_SYNC; +} + +/* + * This thread serially handles requests for remote mirrors, since there's a + * write lock in the filesystem copy when someone is updating the mirror. + * + * It picks takes queued requests from the head + */ + +void * +thread_repo(void *d) +{ + sai_mirror_instance_t *mi = (sai_mirror_instance_t *)d; + sai_mirror_req_t *req, rcopy; + int new_state; + int n; + + lwsl_notice("%s: repo thread start\n", __func__); + + git_libgit2_init(); + + while (!mi->finish) { + git_fetch_options opts = GIT_FETCH_OPTIONS_INIT; + char spec[96], *paths[] = { spec }; + git_strarray rfs = { paths, 1 }; + git_repository *repo_mirror; + git_remote *remote; + + pthread_mutex_lock(&mi->mut); + + /* we sleep if there are no requests pending or ongoing */ + + while (!mi->pending_req.count && !mi->finish) + pthread_cond_wait(&mi->cond, &mi->mut); + + if (mi->finish) { + pthread_mutex_unlock(&mi->mut); + break; + } + + /* + * Starting from the head, look for the first request that's in + * a state we should start the remote for it + */ + + req = NULL; + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + mi->pending_req.head) { + sai_mirror_req_t *r = lws_container_of(d, + sai_mirror_req_t, list); + + if (r->state == SRFS_REQUESTING) { + req = r; + lwsl_notice("%s: setting processing\n", + __func__); + sai_mirror_req_state_set(req, SRFS_PROCESSING); + break; + } + + } lws_end_foreach_dll_safe(d, d1); + + //req = lws_dll2_get_head(&bi->requests); + + //lws_dll2_remove(&->overall_list); + + /* + * The request object on the list might be removed and destroyed + * while we do this long-term mirroring action. So take a temp + * copy of it to set the action up, before releasing the mutex. + */ + if (req) + rcopy = *req; + + pthread_mutex_unlock(&mi->mut); + if (!req) + continue; + + /* + * ... we cannot dereference req after releasing the mutex. + * We made a temp copy of it in rcopy, to set the transaction + * up we can use that so we can be sure it's around for that. + */ + + /* + * git init --bare <path> + */ + + new_state = SRFS_FAILED; + repo_mirror = NULL; + if (git_repository_init(&repo_mirror, rcopy.path, 1)) { + fprintf(stderr, "%s: unable to init sticky repo %s, errno %d\n", + __func__, rcopy.path, errno); + + goto fail_out; + } + + /* + * Fetch over the branch or tag we're interested in from the + * remote repo and into our local sticky mirror + * + * git fetch <remote repo> +<ref>:<ref> + */ + + if (git_remote_create_anonymous(&remote, repo_mirror, rcopy.url)) { + fprintf(stderr, "%s: cant find remote %s\n", __func__, + rcopy.url); + + git_repository_free(repo_mirror); + goto fail_out; + } + + lws_snprintf(spec, sizeof(spec), "%s:ref-%s", rcopy.ref, rcopy.hash); + fprintf(stderr, "%s: fetching %s %s\n", __func__, rcopy.url, spec); + + /* + * This may take an open-ended amount of time + */ + + n = git_remote_fetch(remote, &rfs, &opts, "fetch"); + + git_remote_free(remote); + git_repository_free(repo_mirror); + + if (n) { +#if defined(SAI_HAVE_LIBGIT2_GIT_ERROR) + const git_error *e = git_error_last(); + + if (e) + fprintf(stderr, "%s: git error %s\n", __func__, + e->message); +#endif + + fprintf(stderr, "%s: failed to fetch %s, n: %d\n", + __func__, spec, n); + goto fail_out; + } + + fprintf(stderr, "%s: syncing mirror fetch %s %s successful\n", + __func__, rcopy.url, rcopy.hash); + + new_state = SRFS_SUCCEEDED; + +fail_out: + /* + * We notify the threadpool monitoring thread + */ + + pthread_mutex_lock(&mi->mut); + + /* + * The requestor and its request may have gone. And, it's + * possible we got several queued requests that were actually + * waiting for the same mirror transaction which has now + * succeeded or failed the same for all of them. + * + * Let's take the approach to lock the queue and tell everybody + * at once who's asking for the same thing what the outcome was. + */ + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + mi->pending_req.head) { + sai_mirror_req_t *r = lws_container_of(d, + sai_mirror_req_t, list); + + if ((r->state == SRFS_REQUESTING || + r->state == SRFS_PROCESSING) && + !strcmp(r->url, rcopy.url) && + !strcmp(r->hash, rcopy.hash) && + !strcmp(r->path, rcopy.path) && + !strcmp(r->ref, rcopy.ref)) { + /* + * Change the state of matching guys and put + * them on to the completed_req list owner + */ + fprintf(stderr, "%s: setting req state %d\n", + __func__, new_state); + sai_mirror_req_state_set(r, new_state); + lws_dll2_remove(&r->list); + lws_dll2_add_tail(&r->list, &mi->completed_req); + } + + } lws_end_foreach_dll_safe(d, d1); + + pthread_mutex_unlock(&mi->mut); + } + + lwsl_notice("%s: repo thread exiting\n", __func__); + + git_libgit2_shutdown(); + pthread_exit(NULL); + + return NULL; +} diff --git a/src/builder/b-nspawn.c b/src/builder/b-nspawn.c new file mode 100644 index 0000000..308c7a2 --- /dev/null +++ b/src/builder/b-nspawn.c @@ -0,0 +1,424 @@ +/* + * sai-builder + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Generic unix spawn and stdxxx pipe to wsi mapping + */ + +#include <libwebsockets.h> + +#include <string.h> +#include <signal.h> + +#include <sys/stat.h> +#include <sys/types.h> +#if !defined(WIN32) +#include <unistd.h> +#endif +#include <fcntl.h> +#include <assert.h> + +#include "b-private.h" + +#if !defined(WIN32) +static char csep = '/'; +#else +static char csep = '\\'; +#endif + +extern struct lws_vhost *builder_vhost; + +struct ws_capture_chunk * +saib_log_chunk_create(struct sai_nspawn *ns, void *buf, size_t len, int channel) +{ + struct ws_capture_chunk *chunk = malloc(sizeof(*chunk) + len); + + if (!chunk || !ns->spm) + return NULL; + + memset(chunk, 0, sizeof(*chunk)); + chunk->us = lws_now_usecs(); + chunk->len = len; + chunk->stdfd = channel; + if (len) + memcpy(&chunk[1], buf, len); + + ns->chunk_cache_size += sizeof(*chunk) + len; + + lws_dll2_add_head(&chunk->list, &ns->chunk_cache); + ns->spm->logs_in_flight++; + + return chunk; +} + +static int +callback_sai_stdwsi(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct sai_nspawn *ns = (struct sai_nspawn *)lws_get_opaque_user_data(wsi); + uint8_t buf[600]; + int ilen; + + switch (reason) { + + case LWS_CALLBACK_RAW_CLOSE_FILE: + lwsl_user("%s: RAW_CLOSE_FILE wsi %p: fd: %d, stdfd: %d\n", + __func__, wsi, lws_get_socket_fd(wsi), + lws_spawn_get_stdfd(wsi)); + + ilen = lws_snprintf((char *)buf, sizeof(buf), "Stdwsi %d close\n", lws_spawn_get_stdfd(wsi)); + saib_log_chunk_create(ns, buf, (size_t)ilen, 3); + + if (ns->lsp) + lws_spawn_stdwsi_closed(ns->lsp, wsi); + break; + + case LWS_CALLBACK_RAW_RX_FILE: + if (!ns->spm) + return -1; +#if defined(WIN32) + { + DWORD rb; + if (!ReadFile((HANDLE)lws_get_socket_fd(wsi), buf, sizeof(buf), &rb, NULL)) { + lwsl_debug("%s: read on stdwsi failed\n", __func__); + return -1; + } + ilen = (int)rb; + } +#else + ilen = read((int)(intptr_t)lws_get_socket_fd(wsi), buf, sizeof(buf)); + if (ilen < 1) { + lwsl_debug("%s: read on stdwsi failed\n", __func__); + return -1; + } +#endif + + len = ilen; + + // printf("(%d) %.*s\n", (int)len, (int)len, buf); + + if (!saib_log_chunk_create(ns, buf, len, lws_spawn_get_stdfd(wsi))) + return -1; + + lws_ss_request_tx(ns->spm->ss); + break; + + default: + break; + } + + return 0; +} + +struct lws_protocols protocol_stdxxx = + { "sai-stdxxx", callback_sai_stdwsi, 0, 0 }; + + +static void +sai_lsp_reap_cb(void *opaque, lws_usec_t *accounting, siginfo_t *si, + int we_killed_him) +{ + struct sai_nspawn *ns = (struct sai_nspawn *)opaque; + + saib_log_chunk_create(ns, ">saib> Reaping build process\n", 29, 3); + +#if !defined(WIN32) + lwsl_notice("%s: reaped: timing %dms %dms %dms %dms\n", __func__, + (int)(accounting[0] / 1000), (int)(accounting[1] / 1000), + (int)(accounting[2] / 1000), (int)(accounting[3] / 1000)); + + if (we_killed_him & 1) { + lwsl_notice("%s: Process TIMED OUT by Sai\n", __func__); + ns->retcode = SAISPRF_TIMEDOUT; + goto ok; + } + + if (we_killed_him & 2) { + lwsl_notice("%s: Process killed by Sai due to spew\n", __func__); + ns->retcode = SAISPRF_TERMINATED; + goto ok; + } + + switch (si->si_code) { + case CLD_EXITED: + lwsl_notice("%s: Process Exited with exit code %d\n", + __func__, si->si_status); + ns->retcode = SAISPRF_EXIT | si->si_status; + if (ns->user_cancel) + ns->retcode = SAISPRF_TERMINATED; + break; + case CLD_KILLED: + case CLD_DUMPED: + lwsl_notice("%s: Process Terminated by signal %d / %d\n", + __func__, si->si_status, si->si_signo); + ns->retcode = SAISPRF_SIGNALLED | si->si_signo; + break; + default: + lwsl_notice("%s: SI code %d\n", __func__, si->si_code); + break; + } + +ok: +#else + ns->retcode = SAISPRF_EXIT | (si->retcode & 0xff); +#endif + + saib_task_grace(ns); + saib_set_ns_state(ns, NSSTATE_DONE); + + /* + * add a final zero-length log with the retcode to the list of pending + * logs + */ + + saib_log_chunk_create(ns, NULL, 0, 2); + + lwsl_notice("%s: finished, waiting to drain logs (this ns %d, spm in flight %d)\n", + __func__, ns->chunk_cache.count, + ns->spm ? ns->spm->logs_in_flight : -99); +} + +#if defined(WIN32) + +static const char * const runscript = + "set SAI_INSTANCE_IDX=%d\n" + "set SAI_LOGPROXY=%s\n" + "set SAI_LOGPROXY_TTY0=%s\n" + "set SAI_LOGPROXY_TTY1=%s\n" + "cd %s\\jobs\\%s\\%s &&" + "%s" +; + +#else + +static const char * const runscript = + "#!/bin/bash -x\n" +#if defined(__APPLE__) + "export PATH=/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin\n" +#endif + "export SAI_OVN=%s\n" + "export SAI_PROJECT=%s\n" + "export SAI_REMOTE_REF=%s\n" + "export SAI_INSTANCE_IDX=%d\n" + "export SAI_LOGPROXY=%s\n" + "export SAI_LOGPROXY_TTY0=%s\n" + "export SAI_LOGPROXY_TTY1=%s\n" + "set -e\n" + "cd %s/jobs/$SAI_OVN/$SAI_PROJECT\n" + + "%s\n" + "exit $?\n" +; + +#endif + +int +saib_spawn(struct sai_nspawn *ns) +{ + struct lws_spawn_piped_info info; + char args[290], st[2048], *p; + int fd, n; + const char * cmd[] = { + "/bin/ps", + NULL + }; + char *env[] = { + "PATH=/usr/bin:/bin", + "LANG=en_US.UTF-8", + NULL + }; + + lws_strncpy(st, ns->sp->name, sizeof(st)); + lws_filename_purify_inplace(st); + p = st; + while ((p = strchr(p, '/'))) + *p = '_'; + +#if defined(WIN32) + lws_snprintf(args, sizeof(args), "%s\\sai-build-script-%s-%d.bat", + builder.home, st, ns->instance_idx); +#else + lws_snprintf(args, sizeof(args), "%s/sai-build-script-%s-%d.sh", + builder.home, st, ns->instance_idx); +#endif + + lwsl_hexdump_notice(ns->task->build, strlen(ns->task->build)); + +#if defined(WIN32) + if (_sopen_s(&fd, args, _O_CREAT | _O_TRUNC | _O_WRONLY, + _SH_DENYNO, _S_IWRITE)) + fd = -1; +#else + fd = open(args, O_CREAT | O_TRUNC | O_WRONLY, 0755); +#endif + if (fd < 0) { + lwsl_err("%s: unable to open %s for write\n", __func__, args); + return 1; + } + +#if defined(WIN32) + n = lws_snprintf(st, sizeof(st), runscript, ns->instance_idx, + ns->slp_control.sockpath, + ns->slp[0].sockpath, ns->slp[1].sockpath, + builder.home, ns->fsm.ovname, ns->project_name, + ns->task->build); +#else + n = lws_snprintf(st, sizeof(st), runscript, ns->fsm.ovname, + ns->project_name, ns->ref, ns->instance_idx, + ns->slp_control.sockpath, + ns->slp[0].sockpath, ns->slp[1].sockpath, + builder.home, ns->task->build); +#endif + + /* but from the script's pov, it's chrooted at /home/sai */ + + if (write(fd, st, n) != n) { + close(fd); + lwsl_err("%s: failed to write runscript to %s\n", __func__, args); + return 1; + } + + close(fd); + + cmd[0] = args; + + memset(&info, 0, sizeof(info)); + info.vh = builder.vhost; + info.env_array = env; + info.exec_array = cmd; + info.protocol_name = "sai-stdxxx"; + info.max_log_lines = 10000; + info.timeout_us = 30 * 60 * LWS_US_PER_SEC; + info.reap_cb = sai_lsp_reap_cb; + info.opaque = ns; + info.plsp = &ns->lsp; + + ns->lsp = lws_spawn_piped(&info); + if (!ns->lsp) { + lwsl_err("%s: failed\n", __func__); + + return 1; + } + + lwsl_notice("%s: lws_spawn_piped started\n", __func__); + + return 0; +} + +/* + * This prepares the same filesystem layout whether we can use it for overlayfs, + * or have to use it directly / with chroot + */ + +int +saib_prepare_mount(struct sai_builder *b, struct sai_nspawn *ns) +{ + char homedir[384]; + int n, m; + + /* create the top level overlays container if not already there */ + + n = lws_snprintf(ns->fsm.mp, sizeof(ns->fsm.mp), "%s%coverlays", + b->home, csep); + + mkdir(ns->fsm.mp, 0770); + + /* create a subdir for our overlay pieces */ + + n += lws_snprintf(ns->fsm.mp + n, sizeof(ns->fsm.mp) - n, "%c%s", csep, + ns->fsm.ovname); + m = mkdir(ns->fsm.mp, 0777); + if (m && errno != EEXIST) + goto bail_dir; + lws_strncpy(homedir, ns->fsm.mp, sizeof(homedir)); + + /* create work dir, session layer and mountpoint, retain mountpoint */ + +#if defined(__linux__) && 0 + + lws_snprintf(ns->fsm.mp + n, sizeof(ns->fsm.mp) - n, "%cwork", csep); + m = mkdir(ns->fsm.mp, 0770); + if (m && errno != EEXIST) + goto bail_dir; + + lws_snprintf(ns->fsm.mp + n, sizeof(ns->fsm.mp) - n, "%csession", csep); + m = mkdir(ns->fsm.mp, 0777); + if (m && errno != EEXIST) + goto bail_dir; + + n += lws_snprintf(ns->fsm.mp + n, sizeof(ns->fsm.mp) - n, "%cmountpoint", + csep); + m = mkdir(ns->fsm.mp, 0777); + if (m && errno != EEXIST) + goto bail_dir; + + lws_snprintf(ns->fsm.mp + n, sizeof(ns->fsm.mp) - n, "%chome%csai", + csep, csep); + lws_strncpy(homedir, ns->fsm.mp, sizeof(homedir)); + ns->fsm.mp[n] = '\0'; + + n = lws_fsmount_mount(&ns->fsm); + + if (!n) +#endif + { + + /* these are ephemeral on top of the mountpoint path, we snip + * them off later */ + + n = (int)strlen(ns->fsm.mp); + lws_snprintf(ns->fsm.mp + n, sizeof(ns->fsm.mp) - n, "%chome", + csep); + m = mkdir(ns->fsm.mp, 0700); + if (m && errno != EEXIST) + goto bail_dir; + + lws_snprintf(ns->fsm.mp + n, sizeof(ns->fsm.mp) - n, + "%chome%csai", csep, csep); + lws_strncpy(homedir, ns->fsm.mp, sizeof(homedir)); + + m = mkdir(ns->fsm.mp, 0700); + if (m && errno != EEXIST) + goto bail_dir; + + lws_snprintf(ns->fsm.mp + n, sizeof(ns->fsm.mp) - n, + "%chome%csai%cgit-mirror", csep, csep, csep); + m = mkdir(ns->fsm.mp, 0755); + ns->fsm.mp[n] = '\0'; + if (m && errno != EEXIST) + goto bail_dir; + + return 0; + } +#if defined(__linux__) && 0 + else { + lwsl_err("%s: mount err %d, errno %d\n", __func__, n, errno); + } + + return n; +#else + return 0; +#endif + +bail_dir: + lwsl_err("%s: failed to create dir %s: errno %d\n", __func__, + ns->fsm.mp, errno); + + return 1; +} diff --git a/src/builder/b-private.h b/src/builder/b-private.h new file mode 100644 index 0000000..b01cceb --- /dev/null +++ b/src/builder/b-private.h @@ -0,0 +1,228 @@ +/* + * Sai builder definitions src/builder/b-private.h + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "../common/include/private.h" + +#include <sys/stat.h> +#if defined(WIN32) +#include <direct.h> +#define read _read +#define open _open +#define close _close +#define write _write +#define mkdir(x,y) _mkdir(x) +#define rmdir _rmdir +#define unlink _unlink +#define HAVE_STRUCT_TIMESPEC +#if defined(pid_t) +#undef pid_t +#endif +#endif +#include <pthread.h> +#include <git2.h> + +typedef enum { + PHASE_IDLE, + + PFL_FIRST = 128, + + PHASE_START_ATTACH = PFL_FIRST | 1, + PHASE_SUMM_PLATFORMS = 2, + + PHASE_BUILDING + +} cursor_phase_t; + + + +struct saib_ws_pss; + +enum { + NSSTATE_INIT, + NSSTATE_MOUNTING, + NSSTATE_STARTING_MIRROR, + /* speculatively see if we can already check out locally */ + NSSTATE_CHECKOUT_SPEC, + /* we are stuck waiting for remote mirror thread */ + NSSTATE_WAIT_REMOTE_MIRROR, + /* remote mirror completed, we can check out locally */ + NSSTATE_CHECKOUT, + /* we have finished with threadpool / wait for remote */ + NSSTATE_CHECKEDOUT, + NSSTATE_BUILD, + NSSTATE_DONE, + NSSTATE_FAILED, +}; + +struct saib_logproxy { + char sockpath[128]; + struct sai_nspawn *ns; + int log_channel_idx; +}; + +struct sai_nspawn; + +struct sai_nspawn { + lws_dll2_owner_t chunk_cache; + + char inp[384]; + char path[256]; + char pending_mirror_log[128]; + + /* convenient place to store it */ + struct saib_logproxy slp_control; + struct saib_logproxy slp[2]; + + lws_dll2_t list; /* sai_plat owner lists sai_nspawns */ + struct sai_builder *builder; + struct lws_fsmount fsm; + struct lws_spawn_piped *lsp; + sai_task_t *task; + + lws_dll2_owner_t artifact_owner; /* struct artifact_path */ + + struct lws_threadpool *tp; + struct lws_threadpool_task *tp_task; + lws_sorted_usec_list_t sul_cleaner; + lws_sorted_usec_list_t sul_task_cancel; + + sai_plat_t *sp; /* the sai_plat */ + struct sai_plat_master *spm; /* the sai plat / master with the ss / wsi */ + + size_t chunk_cache_size; + + const char *master_name; /* sai-master name who triggered this, eg, 'warmcat' */ + const char *project_name; /* name of the git project, eg, 'libwebsockets' */ + const char *ref; /* remote refname, eg 'master' */ + const char *hash; /* remote hash */ + const char *git_repo_url; + + int retcode; + int instance_idx; + int mirror_wait_budget; + + uint8_t spins; + uint8_t state; /* NSSTATE_ */ + uint8_t stdcount; + uint8_t term_budget; + + uint8_t finished_when_logs_drained:1; + uint8_t state_changed:1; + uint8_t user_cancel:1; +}; + +typedef struct sai_mirror_instance { + pthread_mutex_t mut; + pthread_cond_t cond; + + pthread_t repo_thread; + + lws_dll2_owner_t pending_req; + lws_dll2_owner_t completed_req; + + uint8_t finish; +} sai_mirror_instance_t; + + +/* + * This represents this builder process as a whole + */ + +struct sai_builder { + lws_dll2_owner_t sai_plat_owner; /* list of platforms we offer */ + lws_dll2_owner_t sai_plat_master_owner; /* masters we connect to */ + lws_dll2_owner_t devices_owner; /* sai_serial_t */ + + struct lwsac *conf_head; + struct lws_context *context; + struct lws_vhost *vhost; + + const char *home; /* home dir, usually /sai/home */ + const char *perms; /* user:group */ + + const char *host; /* prepended before hostname */ + + sai_mirror_instance_t mi; +}; + +struct ws_capture_chunk { + struct lws_dll2 list; + + lws_usec_t us; /* builder time that we saw this */ + size_t len; + uint8_t stdfd; /* 1 = stdout, 2 = stderr */ + + /* len bytes of data is overallocated after this */ +}; + + +extern struct sai_builder builder; +extern const lws_ss_info_t ssi_sai_builder, ssi_sai_mirror, ssi_sai_artifact; +extern const struct lws_protocols protocol_com_warmcat_sai; +int +saib_config_global(struct sai_builder *builder, const char *d); +extern int saib_config(struct sai_builder *builder, const char *d); +extern void saib_config_destroy(struct sai_builder *builder); + +int +saib_overlay_mount(struct sai_builder *b, struct sai_nspawn *ns); + +int +saib_overlay_unmount(struct sai_nspawn *ns); + +int +saib_spawn(struct sai_nspawn *ns); + +int +saib_prepare_mount(struct sai_builder *b, struct sai_nspawn *ns); + +int +saib_ws_json_rx_builder(struct sai_plat_master *spm, const void *in, size_t len); + +int +saib_generate(struct sai_plat *sp, char *buf, int len); + +enum lws_threadpool_task_return +saib_mirror_task(void *user, enum lws_threadpool_task_status s); + +int +saib_set_ns_state(struct sai_nspawn *ns, int state); + +void +saib_task_destroy(struct sai_nspawn *ns); + +void +saib_task_grace(struct sai_nspawn *ns); + +struct ws_capture_chunk * +saib_log_chunk_create(struct sai_nspawn *ns, void *buf, size_t len, int channel); + +int +saib_queue_task_status_update(sai_plat_t *sp, struct sai_plat_master *spm, + const char *rej_task_uuid); + +int +rm_rf_cb(const char *dirpath, void *user, struct lws_dir_entry *lde); + +extern const struct lws_protocols protocol_logproxy; + +void * +thread_repo(void *d); diff --git a/src/builder/b-sai.c b/src/builder/b-sai.c new file mode 100644 index 0000000..b3d0b3d --- /dev/null +++ b/src/builder/b-sai.c @@ -0,0 +1,509 @@ +/* + * sai-builder + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Sai-builder uses a secure streams template to make the client connections to + * the masters listed in /etc/sai/builder/conf JSON config. The URL in the + * config is substituted for the endpoint URL at runtime. + * + * See b-comms.c for the secure stream template and callbacks for this. + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> + +#if defined(__APPLE__) +#include <sys/stat.h> /* for mkdir() */ +#include <unistd.h> /* for chown() */ +#endif + +#if defined(WIN32) +#include <initguid.h> +#include <KnownFolders.h> +#include <Shlobj.h> +#endif + +#include "b-private.h" + +static const char *config_dir = "/etc/sai/builder"; +static int interrupted; +static lws_state_notify_link_t nl; + +struct sai_builder builder; + +extern struct lws_protocols protocol_stdxxx; + +static const char * const default_ss_policy = + "{" + "\"retry\": [" /* named backoff / retry strategies */ + "{\"default\": {" + "\"backoff\": [" "1000," + "2000," + "3000," + "5000," + "10000" + "]," + "\"conceal\":" "99999," + "\"jitterpc\":" "20," + "\"svalidping\":" "300," + "\"svalidhup\":" "350" + "}}" + "]," + + /* + * No certs / trust stores because we will validate using system trust + * store... metadata.url should be set at runtime to something like + * https://warmcat.com/sai + */ + + "\"s\": [" + /* + * The main connection to a master carrying events and logs + */ + "{\"sai_builder\": {" + "\"endpoint\":" "\"${url}\"," + "\"port\":" "443," + "\"protocol\":" "\"ws\"," + "\"ws_subprotocol\":" "\"com-warmcat-sai\"," + "\"http_url\":" "\"\"," /* filled in by url */ + "\"tls\":" "true," + "\"retry\":" "\"default\"," + "\"metadata\": [" + "{\"url\": \"\"}" + "]" + "}}," + /* + * Ephemeral connections to the same master carrying artifact + * JSON + bulk data + */ + "{\"sai_artifact\": {" + "\"endpoint\":" "\"${url}\"," + "\"port\":" "443," + "\"protocol\":" "\"ws\"," + "\"ws_subprotocol\":" "\"com-warmcat-sai\"," + "\"http_url\":" "\"\"," /* filled in by url */ + "\"tls\":" "true," + "\"opportunistic\":" "true," + "\"retry\":" "\"default\"," + "\"metadata\": [" + "{\"url\": \"\"}" + "]" + "}}" + "]}" +; + +static const struct lws_protocols *pprotocols[] = { + &protocol_stdxxx, + &protocol_logproxy, + NULL +}; + +static int lpidx; +static char vhnames[256], *pv = vhnames; + +static struct lws_protocol_vhost_options pvo = { + NULL, /* "next" pvo linked-list */ + NULL, /* "child" pvo linked-list */ + "protocol-logproxy", /* protocol name we belong to on this vhost */ + "ok" /* ignored */ +}; + +static int +saib_create_listen_uds(struct lws_context *context, struct saib_logproxy *lp) +{ + struct lws_context_creation_info info; + + memset(&info, 0, sizeof(info)); + + info.vhost_name = pv; + pv += lws_snprintf(pv, sizeof(vhnames) - (pv - vhnames), "logproxy.%d", lpidx++) + 1; + info.options = LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG | + LWS_SERVER_OPTION_UNIX_SOCK; + info.iface = lp->sockpath; + info.listen_accept_role = "raw-skt"; + info.listen_accept_protocol = "protocol-logproxy"; + info.user = lp; + info.pvo = &pvo; + info.pprotocols = pprotocols; + +#if !defined(__linux__) + unlink(lp->sockpath); +#endif + + lwsl_notice("%s: %s\n", __func__, lp->sockpath); + + if (!lws_create_vhost(context, &info)) { + lwsl_notice("%s: failed to create vh %s\n", __func__, + info.vhost_name); + return -1; + } + + return 0; +} + +static int +app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, + int current, int target) +{ + struct lws_context *context = lws_system_context_from_system_mgr(mgr); + char pur[128]; + + /* + * For the things we care about, let's notice if we are trying to get + * past them when we haven't solved them yet, and make the system + * state wait while we trigger the dependent action. + */ + switch (target) { + + case LWS_SYSTATE_OPERATIONAL: + if (current != LWS_SYSTATE_OPERATIONAL) + break; + + /* + * The builder JSON conf listed masters we want to connect to, + * let's collect the config, make a ss for each and add the + * saim into an lws_dll2 list owned by + * builder->builder->sai_plat_owner + */ + + lwsl_notice("%s: starting platform config\n", __func__); + if (saib_config(&builder, config_dir)) { + lwsl_err("%s: config failed\n", __func__); + + return 1; + } + + /* + * For each platform... + */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, mp, mp1, + builder.sai_plat_owner.head) { + struct sai_plat *sp = lws_container_of(mp, struct sai_plat, + sai_plat_list); + + /* + * ... for each nspawn on the platform... + */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, np, np1, + sp->nspawn_owner.head) { + struct sai_nspawn *ns = + lws_container_of(np, struct sai_nspawn, list); + char *p; + int n; + + lws_strncpy(pur, sp->name, sizeof(pur)); + lws_filename_purify_inplace(pur); + p = pur; + while ((p = strchr(p, '/'))) + *p = '_'; + + /* + * Proxy the control logging channel (this + * is the one that has sai progress info) + */ + + lws_snprintf(ns->slp_control.sockpath, + sizeof(ns->slp_control.sockpath), +#if defined(__linux__) + UDS_PATHNAME_LOGPROXY".%s.%d.saib", +#else + UDS_PATHNAME_LOGPROXY"/%s.%d.saib", +#endif + pur, ns->instance_idx); + + ns->slp_control.ns = ns; + ns->slp_control.log_channel_idx = 3; + + if (saib_create_listen_uds(context, &ns->slp_control)) { + lwsl_err("%s: Failed to create ctl log proxy listen UDS %s\n", + __func__, ns->slp_control.sockpath); + return -1; + } + + /* + * For each additional logging channel... + */ + + for (n = 0; n < (int)LWS_ARRAY_SIZE(ns->slp); n++) { + + /* + * ... create a UDS listening proxy + */ + + lws_snprintf(ns->slp[n].sockpath, + sizeof(ns->slp[n].sockpath), +#if defined(__linux__) + UDS_PATHNAME_LOGPROXY".%s.%d.tty%d", +#else + UDS_PATHNAME_LOGPROXY"/%s.%d.tty%d", +#endif + pur, ns->instance_idx, n); + + ns->slp[n].ns = ns; + ns->slp[n].log_channel_idx = n + 4; + + if (saib_create_listen_uds(context, &ns->slp[n])) { + lwsl_err("%s: Failed to create log proxy listen UDS %s\n", + __func__, ns->slp[n].sockpath); + return -1; + } + } + } lws_end_foreach_dll_safe(np, np1); + } lws_end_foreach_dll_safe(mp, mp1); + + break; + } + + return 0; +} + +static lws_state_notify_link_t * const app_notifier_list[] = { + &nl, NULL +}; + +#if !defined(LWS_WITHOUT_EXTENSIONS) +static const struct lws_extension extensions[] = { + { + "permessage-deflate", + lws_extension_callback_pm_deflate, + "permessage-deflate" + "; client_no_context_takeover" + "; client_max_window_bits" + }, + { NULL, NULL, NULL /* terminator */ } +}; +#endif + +void sigint_handler(int sig) +{ + interrupted = 1; +} + +void +sai_ns_destroy(struct sai_nspawn *ns) +{ + + lws_dll2_remove(&ns->list); + free(ns); +} + + +int main(int argc, const char **argv) +{ + int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct lws_context_creation_info info; +#if defined(WIN32) + char temp[256], stg_config_dir[256]; +#endif + struct stat sb; + const char *p; + void *retval; + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + +#if defined(WIN32) + { + PWSTR wdi = NULL; + + if (SHGetKnownFolderPath(&FOLDERID_ProgramData, + 0, NULL, &wdi) != S_OK) { + lwsl_err("%s: unable to get config dir\n", __func__); + return 1; + } + + if (WideCharToMultiByte(CP_ACP, 0, wdi, -1, temp, + sizeof(temp), 0, NULL) <= 0) { + lwsl_err("%s: problem with string encoding\n", __func__); + return 1; + } + + lws_snprintf(stg_config_dir, sizeof(stg_config_dir), + "%s\\sai\\builder\\", temp); + + config_dir = stg_config_dir; + CoTaskMemFree(wdi); + } +#endif + + /* + * Let's parse the global bits out of the config + */ + + lwsl_notice("%s: config dir %s\n", __func__, config_dir); + if (saib_config_global(&builder, config_dir)) { + lwsl_err("%s: global config failed\n", __func__); + + return 1; + } + + /* + * We need to sample the true uid / gid we should use inside + * the mountpoint for sai:nobody or sai:sai, by looking at + * what the uid and gid are on /home/sai before anything changes + * it + */ + if (stat(builder.home, &sb)) { + lwsl_err("%s: Can't find %s\n", __func__, builder.home); + return 1; + } + +#if defined(__APPLE__) + /* we are still root */ + mkdir(UDS_PATHNAME_LOGPROXY, 0700); + chown(UDS_PATHNAME_LOGPROXY, sb.st_uid, sb.st_gid); +#endif + + /* if we don't do this, libgit2 looks in /root/.gitconfig */ +#if defined(WIN32) + _putenv_s("HOME", builder.home); +#else + setenv("HOME", builder.home, 1); +#endif + + lwsl_user("Sai Builder - " + "Copyright (C) 2019-2020 Andy Green <andy@warmcat.com>\n"); + lwsl_user(" sai-builder [-c <config-file>]\n"); + + memset(&info, 0, sizeof info); + info.port = CONTEXT_PORT_NO_LISTEN; + info.pprotocols = pprotocols; + + info.uid = sb.st_uid; + info.gid = sb.st_gid; + + +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (!lws_cmdline_option(argc, argv, "-n")) + info.extensions = extensions; +#endif + info.pt_serv_buf_size = 32 * 1024; + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | + LWS_SERVER_OPTION_VALIDATE_UTF8 | + LWS_SERVER_OPTION_EXPLICIT_VHOSTS; + + signal(SIGINT, sigint_handler); + + info.pss_policies_json = default_ss_policy; + info.fd_limit_per_thread = 1 + 128 + 1; + + /* hook up our lws_system state notifier */ + + nl.name = "sai-builder"; + nl.notify_cb = app_system_state_nf; + info.register_notifier_list = app_notifier_list; + + /* create the lws context */ + + builder.context = lws_create_context(&info); + if (!builder.context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* ... and our vhost... */ + + builder.vhost = lws_create_vhost(builder.context, &info); + if (!builder.vhost) { + lwsl_err("Failed to create tls vhost\n"); + goto bail; + } + + pthread_mutex_init(&builder.mi.mut, NULL); + pthread_cond_init(&builder.mi.cond, NULL); + + /* + * Our approach is to split off a thread to do the git remote handling + * in a serialized way blocking the related threadpool threads until it + * completes, without blocking the main (lws) event loop thread. + * + * It has to be segregated because there is a repo write lock with one + * owner at a time for remote -> local mirror write operation. + * + * local mirror -> build-specific checkout doesn't need the write lock + * and can happen concurrently at the threadpool threads. + */ + + if (pthread_create(&builder.mi.repo_thread, NULL, thread_repo, + &builder.mi)) { + lwsl_err("%s: repo thread creation failed\n", __func__); + return 1; + } + + while (!lws_service(builder.context, 0) && !interrupted) + ; + +bail: + + /* destroy the unique masters */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, + builder.sai_plat_master_owner.head) { + struct sai_plat_master *cm = lws_container_of(p, + struct sai_plat_master, list); + + lws_dll2_remove(&cm->list); + lws_ss_destroy(&cm->ss); + + } lws_end_foreach_dll_safe(p, p1); + + saib_config_destroy(&builder); + + /* + * Clean up after the threads + */ + + builder.mi.finish = 1; + + pthread_mutex_lock(&builder.mi.mut); + pthread_cond_broadcast(&builder.mi.cond); + pthread_mutex_unlock(&builder.mi.mut); + + pthread_join(builder.mi.repo_thread, &retval); + + pthread_mutex_destroy(&builder.mi.mut); + pthread_cond_destroy(&builder.mi.cond); + + lws_start_foreach_dll_safe(struct lws_dll2 *, mp, mp1, + builder.sai_plat_owner.head) { + struct sai_plat *sp = lws_container_of(mp, struct sai_plat, + sai_plat_list); + + lws_dll2_remove(&sp->sai_plat_list); + + lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, + sp->nspawn_owner.head) { + struct sai_nspawn *ns = lws_container_of(p, + struct sai_nspawn, list); + + sai_ns_destroy(ns); + + } lws_end_foreach_dll_safe(p, p1); + + } lws_end_foreach_dll_safe(mp, mp1); + + lws_context_destroy(builder.context); + + return 0; +} diff --git a/src/builder/b-task.c b/src/builder/b-task.c new file mode 100644 index 0000000..d20dcdd --- /dev/null +++ b/src/builder/b-task.c @@ -0,0 +1,757 @@ +/* + * sai-builder task acquisition + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <sys/stat.h> +#include <assert.h> + +#include "b-private.h" + +#if !defined(WIN32) +static char csep = '/'; +#else +static char csep = '\\'; +#endif + +const lws_struct_map_t lsm_schema_map_m_to_b[] = { + LSM_SCHEMA (sai_task_t, NULL, lsm_task, "com-warmcat-sai-ta"), + LSM_SCHEMA (sai_cancel_t, NULL, lsm_task_cancel, "com.warmcat.sai.taskcan"), +}; + +enum { + SAIB_RX_TASK_ALLOCATION, + SAIB_RX_TASK_CANCEL, +}; + +static const char * const nsstates[] = { + "NSSTATE_INIT", + "NSSTATE_MOUNTING", + "NSSTATE_STARTING_MIRROR", + "NSSTATE_CHECKOUT_SPEC", /* initial speculative checkout */ + "NSSTATE_WAIT_REMOTE_MIRROR", + "NSSTATE_CHECKOUT", + "NSSTATE_CHECKEDOUT", + "NSSTATE_BUILD", + "NSSTATE_DONE", + "NSSTATE_FAILED", +}; + +int +saib_set_ns_state(struct sai_nspawn *ns, int state) +{ + char log[100]; + int n; + + ns->state = state; + ns->state_changed = 1; + + n = lws_snprintf(log, sizeof(log), ">saib> %s\n", nsstates[state]); + + saib_log_chunk_create(ns, log, n, 3); + + if (state == NSSTATE_FAILED) { + ns->retcode = SAISPRF_EXIT | 254; + saib_task_grace(ns); + } + + if (ns->spm && ns->spm->ss) + lws_ss_request_tx(ns->spm->ss); + + return 0; +} + +/* + * update all masters we're connected to about builder status / optional reject + */ + +int +saib_queue_task_status_update(sai_plat_t *sp, struct sai_plat_master *spm, + const char *rej_task_uuid) +{ + struct sai_rejection *rej; + + if (!spm) + return -1; + + rej = malloc(sizeof(*rej)); + + if (!rej) + return -1; + + if (!spm->ss) + return 0; + + memset(rej, 0, sizeof(*rej)); + + /* + * Queue a builder status update / + * optional task rejection + */ + + if (rej_task_uuid) { + lwsl_notice("%s: builder .%d occupied reject\n", + __func__, (int)(intptr_t)spm->opaque_data); + + lws_strncpy(rej->task_uuid, rej_task_uuid, + sizeof(rej->task_uuid)); + } + + lws_snprintf(rej->host_platform, sizeof(rej->host_platform), "%s", + sp->name); + + rej->limit = sp->instances; + rej->ongoing = sp->ongoing; + + lws_dll2_add_tail(&rej->list, &spm->rejection_list); + lws_ss_request_tx(spm->ss); + + return 0; +} + +void +saib_task_destroy(struct sai_nspawn *ns) +{ + ns->finished_when_logs_drained = 0; + lws_sul_cancel(&ns->sul_cleaner); + lws_sul_cancel(&ns->sul_task_cancel); + + /* + * If able, builder should reintroduce himself to get + * another task + */ + if (ns->spm) { + ns->spm->phase = PHASE_START_ATTACH; + lws_ss_request_tx(ns->spm->ss); + } + + if (ns->tp) { + ns->tp_task = NULL; + lwsl_notice("%s: calling threadpool_finish\n", __func__); + lws_threadpool_finish(ns->tp); + lwsl_notice("%s: calling threadpool_destroy\n", __func__); + lws_threadpool_destroy(ns->tp); + ns->tp = NULL; + } + + if (ns->task && ns->task->told_ongoing) { + + /* + * Account that we're not doing this task any more + */ + + lwsl_notice("%s: ongoing %d -> %d\n", __func__, + ns->sp->ongoing, ns->sp->ongoing - 1); + ns->sp->ongoing--; + + /* + * Schedule informing all the masters we're connected to + */ + + saib_queue_task_status_update(ns->sp, ns->spm, NULL); + } + + if (ns->task && ns->task->ac_task_container) { + /* contains the task object */ + lwsac_free(&ns->task->ac_task_container); + ns->task = NULL; + } +} + +static void +saib_sub_cleaner_cb(lws_sorted_usec_list_t *sul) +{ + struct sai_nspawn *ns = lws_container_of(sul, struct sai_nspawn, + sul_cleaner); + lwsl_warn("%s: .%d: Destroying task after grace period\n", + __func__, ns->instance_idx); + + saib_task_destroy(ns); +} + +static int +artifact_glob_cb(void *data, const char *path) +{ + struct sai_nspawn *ns = (struct sai_nspawn *)data; + const char *p, *ph = NULL; + struct lws_ss_handle *h; + char upp[256], s[384]; + sai_artifact_t *ap; + int n; + + /* + * "path" passed the filter... + * + * In order that we can maximize usage of the CI builder, first mv + * the artifact from path to ns->inp + "uploads/" + * + filename part + */ + + p = path; + while (*p) { + if (*p == '/' || *p == '\\') + ph = p + 1; + p++; + } + + if (!ph) + return 1; + + /* + * This builder might complete another task that happens to create the + * same- named artifact before we finish uploading this one, trashing + * the temp copy on disk. So we add the timestamp in the temp upload + * copy filename that this can't happen. + */ + + lws_snprintf(upp, sizeof(upp), "%s../.sai-uploads/%llu-%s", ns->inp, + (unsigned long long)lws_now_usecs(), ph); + + n = lws_snprintf(s, sizeof(s), ">saib> Artifact: %s\n", upp); + saib_log_chunk_create(ns, s, (size_t)n, 3); + + lwsl_notice("%s: moving %s -> %s\n", __func__, path, upp); + if (rename(path, upp)) { + lwsl_err("%s: mv artifact %s %s failed\n", __func__, path, upp); + return 1; + } + + /* pass upp in as the opaque data... we use it during CREATING */ + + if (lws_ss_create(builder.context, 0, &ssi_sai_artifact, upp, &h, + NULL, NULL)) { + lwsl_err("%s: failed to create secure stream\n", + __func__); + return -1; + } + + ap = lws_ss_to_user_object(h); + /* take a copy so we can unlink the path later */ + lws_strncpy(ap->path, upp, sizeof(ap->path)); + + lws_strncpy(ap->task_uuid, ns->task->uuid, sizeof(ap->task_uuid)); + lws_strncpy(ap->artifact_up_nonce, ns->task->art_up_nonce, + sizeof(ap->artifact_up_nonce)); + lws_strncpy(ap->blob_filename, ph, sizeof(ap->blob_filename)); + ap->timestamp = lws_now_usecs(); + + lwsl_notice("%s: artifact ss created '%s'\n", __func__, ap->path); + + + /* + * We need to set the metadata items for the post urlargs. spm->url is + * something like "wss://warmcat.com/sai/builder"... we will send JSON + * on this connection first and that will be understood by the master + * as meaning the bulk data follows. + */ + + lws_ss_set_metadata(h, "url", ns->spm->url, strlen(ns->spm->url)); + lws_ss_client_connect(h); + + return 0; +} + +/* + * We're finished with the nspawn / task one way or the other, but there's + * still stuff we need to send out. Give it some time then force destruction + * of the task and reset the nspawn. + */ + +void +saib_task_grace(struct sai_nspawn *ns) +{ + char filt[32], scandir[256]; + struct lws_tokenize ts; + uint8_t *p, *p1, *ps; + lws_dir_glob_t g; + int m; + + ns->finished_when_logs_drained = 1; + lws_sul_schedule(builder.context, 0, &ns->sul_cleaner, + saib_sub_cleaner_cb, 20 * LWS_USEC_PER_SEC); + + if (!ns->spm) + return; + + /* + * Let's look for any artifacts the saifile lists... + * they're done as globs so the package filenames or + * whatever may contain substrings like git commit hash + * without having to know it in the saifile. + * + * The file search context is ns->inp, which is where + * the instance and platform-specific build takes place. + * + * We get a comma-separated list of globs possibly each + * with a path offset like "build/\*.rpm". Let's parse + * out each in turn, and do an lws_dir at any path + * offset, matching on the remaining glob. + */ + + lws_tokenize_init(&ts, ns->task->artifacts, + LWS_TOKENIZE_F_NO_INTEGERS | + LWS_TOKENIZE_F_NO_FLOATS | + LWS_TOKENIZE_F_SLASH_NONTERM | + LWS_TOKENIZE_F_DOT_NONTERM | + LWS_TOKENIZE_F_MINUS_NONTERM); + + m = 0; + filt[0] = '\0'; + while ((ts.e = lws_tokenize(&ts)) >= 0) { + switch (ts.e) { + case LWS_TOKZE_ENDED: + if (filt[0]) + goto scan; + break; + case LWS_TOKZE_DELIMITER: + if (*ts.token == ',') + goto scan; + else + filt[m++] = *ts.token; + break; + case LWS_TOKZE_TOKEN: + lws_strnncpy(&filt[m], ts.token, ts.token_len, + sizeof(filt) - 1 - m); + m = (int)strlen(filt); + break; + } + if (ts.e == LWS_TOKZE_ENDED) + break; + continue; +scan: + lws_strncpy(scandir, ns->inp, sizeof(scandir)); + m = (int)strlen(scandir); + + /* + * if the filter has a fully-defined subdir, + * append it to the start path + */ + + ps = p1 = p = (uint8_t *)filt; + while (*p) { + if (*p == '/') { + if (lws_ptr_diff(p, p1) + 2 < + (int)sizeof(scandir) - m) { + memcpy(scandir + m, p1, + lws_ptr_diff(p, p1)); + m += lws_ptr_diff(p, p1); + scandir[m] = '\0'; + } else + break; + p1 = p; + ps = p + 1; + } + if (*p == '*') + break; + p++; + } + + lwsl_notice("%s: scan path %s, filter %s\n", __func__, + scandir, (const char *)ps); + + g.filter = (char *)ps; + g.user = (void *)ns; + g.cb = artifact_glob_cb; + + lws_dir(scandir, &g, lws_dir_glob_cb); + + filt[0] = '\0'; + m = 0; + + if (ts.e == LWS_TOKZE_ENDED) + break; + } +} + +static void +saib_sul_task_cancel(struct lws_sorted_usec_list *sul) +{ + struct sai_nspawn *ns = lws_container_of(sul, + struct sai_nspawn, sul_task_cancel); + char s[64]; + int n; + + if (!ns->lsp) + return; + + n = lws_snprintf(s, sizeof(s), ">saib> Cancelling...\n"); + saib_log_chunk_create(ns, s, (size_t)n, 3); + + lws_spawn_piped_kill_child_process(ns->lsp); + if (!--ns->term_budget) + return; + + lws_sul_schedule(ns->builder->context, 0, &ns->sul_task_cancel, + saib_sul_task_cancel, 500 * LWS_US_PER_MS); +} + +int +saib_ws_json_rx_builder(struct sai_plat_master *spm, const void *in, size_t len) +{ + struct lws_threadpool_create_args tca; + struct lws_threadpool_task_args tpa; + sai_plat_t *sp = NULL; + struct sai_nspawn *ns; + struct lejp_ctx ctx; + lws_struct_args_t a; + sai_cancel_t *can; + char url[128], *p; + sai_task_t *task; + int n, m; + + /* + * use the schema name on the incoming JSON to decide what kind of + * structure to instantiate + */ + + memset(&a, 0, sizeof(a)); + a.map_st[0] = lsm_schema_map_m_to_b; + a.map_entries_st[0] = LWS_ARRAY_SIZE(lsm_schema_map_m_to_b); + a.ac_block_size = 512; + +// lwsl_hexdump_warn(in, len); + + lws_struct_json_init_parse(&ctx, NULL, &a); + m = lejp_parse(&ctx, (uint8_t *)in, (int)len); + if (m < 0) { + printf("%.*s\n", (int)len, (const char *)in); + lwsl_notice("%s: builder rx JSON decode failed '%s'\n", + __func__, lejp_error_to_string(m)); + return m; + } + + if (!a.dest) { + lwsac_free(&a.ac); + return 1; + } + + switch (a.top_schema_index) { + case SAIB_RX_TASK_ALLOCATION: + task = (sai_task_t *)a.dest; + task->ac_task_container = a.ac; /* bequeath lwsac responsibility */ + + /* + * Master is requesting that a platform adopt a task... + * + * Multiple platforms may be using this connection to a given + * master so we have to disambiguate which platform he's + * tasking first. + */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + builder.sai_plat_owner.head) { + sp = lws_container_of(d, sai_plat_t, sai_plat_list); + + if (!strcmp(sp->platform, task->platform)) + break; + sp = NULL; + } lws_end_foreach_dll_safe(d, d1); + + if (!sp) { + lwsl_err("%s: can't identify req task plat '%s'\n", + __func__, task->platform); + + return 1; + } + + /* + * store a copy of the toplevel ac used for the deserialization + * into the outer part of the c builder wrapper + */ + + sp->deserialization_ac = a.ac; + + /* + * Look for a spare nspawn... + * + * We may connect to multiple masters and it's asynchronous + * which master may have tasked us first, so it's not that + * unusual to reject a task the master thought we could have + * taken + */ + + n = 0; + ns = NULL; + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + sp->nspawn_owner.head) { + struct sai_nspawn *xns = lws_container_of(d, + struct sai_nspawn, list); + + n++; + if (!xns->task) { + ns = xns; + break; + } + + } lws_end_foreach_dll_safe(d, d1); + + if (!ns) { + + /* + * Full up... reject the task and update every + * master's model of our task load status + */ + + lwsl_notice("%s: plat '%s': no idle nspawn (of %d), " + "plat load %d / %d\n", __func__, sp->name, + n, sp->ongoing, sp->instances); + if (saib_queue_task_status_update(sp, spm, task->uuid)) + return -1; + + return 0; + } + +// lwsl_hexdump_warn(task->build, strlen(task->build)); + + /* create a taskqueue just for preparing this specific spawn */ + + memset(&tpa, 0, sizeof(tpa)); + tca.threads = 1; + tca.max_queue_depth = 1; + ns->tp = lws_threadpool_create(builder.context, &tca, "nsp-%s", + task->uuid); + + lws_strncpy(ns->fsm.distro, task->platform, + sizeof(ns->fsm.distro)); + lws_filename_purify_inplace(ns->fsm.distro); + p = ns->fsm.distro; + while ((p = strchr(p, '/'))) + *p = '_'; + + /* + * unique for remote master name ("warmcat"), + * project name ("libwebsockets") + */ + ns->master_name = spm->name; + ns->project_name = task->repo_name; + if (!strncmp(task->git_ref, "refs/heads/", 11)) + ns->ref = task->git_ref + 11; + else + if (!strncmp(task->git_ref, "refs/tags/", 10)) + ns->ref = task->git_ref + 10; + else + ns->ref = task->git_ref; + ns->hash = task->git_hash; + ns->git_repo_url = task->git_repo_url; + ns->task = task; /* we are owning this nspawn for the duration */ + ns->spm = spm; /* bind this task to the spm the req came in on */ + + { + int ml; + char mb[96]; + + ml = lws_snprintf(mb, sizeof(mb), + ">saib> Sai Builder Version: %s, lws: %s\n", + BUILD_INFO, LWS_BUILD_HASH); + saib_log_chunk_create(ns, mb, ml, 3); + } + saib_set_ns_state(ns, NSSTATE_INIT); + +#if defined(__linux__) + ns->fsm.layers[0] = "base"; + ns->fsm.layers[1] = "env"; +#endif + + lws_snprintf(ns->fsm.ovname, sizeof(ns->fsm.ovname), "%d-%d.%d", + spm->index, ns->sp->index, ns->instance_idx); + + lwsl_notice("%s: master %s\n", __func__, ns->master_name); + lwsl_notice("%s: project %s\n", __func__, ns->project_name); + lwsl_notice("%s: ref %s\n", __func__, ns->ref); + lwsl_notice("%s: hash %s\n", __func__, ns->hash); + lwsl_notice("%s: distro %s\n", __func__, ns->fsm.distro); + lwsl_notice("%s: ovname %s\n", __func__, ns->fsm.ovname); + lwsl_notice("%s: git_repo_url %s\n", __func__, ns->git_repo_url); + lwsl_notice("%s: mountpoint %s\n", __func__, ns->fsm.mp); + + spm->phase = PHASE_BUILDING; + n = lws_snprintf(ns->inp, sizeof(ns->inp), "%s%c", + builder.home, csep); + + n += lws_snprintf(ns->inp + n, sizeof(ns->inp) - n, "jobs%c", + csep); + lws_filename_purify_inplace(ns->inp); + if (mkdir(ns->inp, 0755) && errno != EEXIST) + goto ebail; + + n += lws_snprintf(ns->inp + n, sizeof(ns->inp) - n, "%s%c", + ns->fsm.ovname, csep); + lws_filename_purify_inplace(ns->inp); + if (mkdir(ns->inp, 0755) && errno != EEXIST) + goto ebail; + + n += lws_snprintf(ns->inp + n, sizeof(ns->inp) - n, "%s%c", + ns->project_name, csep); + lws_filename_purify_inplace(ns->inp); + if (mkdir(ns->inp, 0755) && errno != EEXIST) + goto ebail; + + /* + * Create a pending upload dir to mv artifacts into while + * we get on with the next job. + */ + lws_snprintf(ns->inp + n, sizeof(ns->inp) - n, "../.sai-uploads"); + if (mkdir(ns->inp, 0755) && errno != EEXIST) + goto ebail; + /* + * Snip that last bit off so ns->inp is the fully qualified + * builder instance base dir + */ + ns->inp[n] = '\0'; + + /* the git mirror is in the build host's /home/sai, NOT the + * mounted overlayfs */ + + m = lws_snprintf(ns->path, sizeof(ns->path), + "%s%cgit-mirror", builder.home, csep); + + if (mkdir(ns->path, 0755) && errno != EEXIST) + goto ebail; + + lws_strncpy(url, ns->git_repo_url, sizeof(url)); + + /* + * We want to convert the repo url to a unique subdir name + * that's safe, like http___warmcat.com_repo_sai + */ + + lws_filename_purify_inplace(url); + { + char *q = url; + while (*q) { + if (*q == '/') + *q = '_'; + if (*q == '.') + *q = '_'; + q++; + } + } + m += lws_snprintf(ns->path + m, sizeof(ns->path) - m, "%c%s", + csep, url); + if (mkdir(ns->path, 0755) && errno != EEXIST) { + lwsl_err("%s: unable to create %s\n", __func__, + ns->path); + goto bail; + } + lwsl_notice("%s: created %s\n", __func__, ns->path); + + /* manage the mirror dir using build host credentials */ + + saib_set_ns_state(ns, NSSTATE_STARTING_MIRROR); + + memset(&tpa, 0, sizeof(tpa)); + tpa.ss = spm->ss; + tpa.user = ns; + tpa.name = "nsp"; + tpa.task = saib_mirror_task; + + ns->user_cancel = 0; + ns->spins = 0; + + lwsl_warn("%s: enqueuing mirror thread\n", __func__); + + ns->tp_task = lws_threadpool_enqueue(ns->tp, &tpa, "tptask-%s", + task->uuid); + if (!ns->tp_task) { + lwsl_err("%s: threadpool enqueue failed\n", __func__); + goto bail; + } + + /* + * If we successfully set the threadpool task up, then we + * bump our idea of what is ongoing... completing or failing + * after that needs to adjust sp->ongoing accordingly + */ + + lwsl_warn("%s: enqueued mirror thread\n", __func__); + + lwsl_notice("%s: ongoing %d -> %d\n", __func__, sp->ongoing, + sp->ongoing + 1); + sp->ongoing++; + ns->task->told_ongoing = 1; + + /* + * Let the mirror thread get on with things... + * + * When we took on a task, we should inform any masters we're + * connected to about our change in task load status + */ + + if (saib_queue_task_status_update(sp, spm, NULL)) { + goto bail; + } + + break; + + case SAIB_RX_TASK_CANCEL: + + can = (sai_cancel_t *)a.dest; + + lwsl_notice("%s: SAIB_RX_TASK_CANCEL: %s\n", __func__, can->task_uuid); + + lws_start_foreach_dll_safe(struct lws_dll2 *, mp, mp1, + builder.sai_plat_owner.head) { + struct sai_plat *sp = lws_container_of(mp, struct sai_plat, + sai_plat_list); + + lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, + sp->nspawn_owner.head) { + struct sai_nspawn *ns = lws_container_of(p, + struct sai_nspawn, list); + + if (ns->task && + !strcmp(can->task_uuid, ns->task->uuid)) { + lwsl_notice("%s: trying to cancel %s\n", + __func__, can->task_uuid); + + /* + * We're going to send a few signals + * at 500ms intervals + */ + ns->user_cancel = 1; + ns->term_budget = 5; + lws_sul_schedule(ns->builder->context, 0, + &ns->sul_task_cancel, + saib_sul_task_cancel, 1); + } + + } lws_end_foreach_dll_safe(p, p1); + + } lws_end_foreach_dll_safe(mp, mp1); + break; + + default: + break; + } + + return 0; + +ebail: + lwsl_err("%s: unable to create %s\n", __func__, ns->inp); + +bail: + lwsl_notice("%s: failing spawn cleanly\n", __func__); + saib_set_ns_state(ns, NSSTATE_FAILED); + saib_task_grace(ns); + + return 1; +} diff --git a/src/builder/conf.c b/src/builder/conf.c deleted file mode 100644 index deb8588..0000000 --- a/src/builder/conf.c +++ /dev/null @@ -1,340 +0,0 @@ -/* - * sai-builder conf.c - * - * Copyright (C) 2019 Andy Green <andy@warmcat.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation: - * version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#include <libwebsockets.h> -#include <string.h> -#include <signal.h> -#include <time.h> - -#include "private.h" - -static const char * const paths[] = { - "systemd-nspawn.path", - "systemd-nspawn.instances", - "systemd-nspawn.timeout", - - "target[]", - "target[].name", - "target[].timeout", - "target[].consoles[]", - "target[].consoles[].name", - "target[].consoles[].path", - "target[].consoles[].baudrate", - "target[].consoles[].mode", - - "masters[]", - "masters[].url", - "masters[].interface", - "masters[].selfsigned", - "masters[].priority", /* array of builder names */ -}; - -enum enum_paths { - LEJPM_NSPAWN_PATH, - LEJPM_NSPAWN_INSTANCES, - LEJPM_NSPAWN_TIMEOUT, - - LEJPM_TARGET, - LEJPM_TARGET_NAME, - LEJPM_TARGET_TIMEOUT, - LEJPM_TARGET_CONSOLES, - LEJPM_TARGET_CONSOLES_NAME, - LEJPM_TARGET_CONSOLES_PATH, - LEJPM_TARGET_CONSOLES_BAUDRATE, - LEJPM_TARGET_CONSOLES_MODE, - - LEJPM_MASTERS, - LEJPM_MASTERS_URL, - LEJPM_MASTERS_INTERFACE, - LEJPM_MASTERS_SELFSIGNED, - LEJPM_MASTERS_PRIORITY, -}; - -struct jpargs { - struct sai_builder *builder; - - struct sai_nspawn *nspawn; - struct sai_master *master; - struct sai_target *target; - struct sai_console *console; -}; - -#define SAI_BUILDER_CONF_CHUNK 512 -#define SAI_BUILDER_PATH_MAX 128 - -static int -sai_builder_scan_overlays_cb(const char *dirpath, void *user, - struct lws_dir_entry *lde) -{ - struct sai_nspawn *nspawn = (struct sai_nspawn *)user; - size_t s = strlen(lde->name); - struct sai_overlay *overlay; - - if (lde->type != LDOT_DIR) - return 0; - - if (strncmp(lde->name, "env-", 4)) - return 0; - - overlay = lwsac_use(&nspawn->builder->conf_head, sizeof(*overlay), - SAI_BUILDER_CONF_CHUNK); - if (!overlay) - return 1; - - memset(overlay, 0, sizeof(*overlay)); - overlay->name = lwsac_use(&nspawn->builder->conf_head, s + 1, - SAI_BUILDER_CONF_CHUNK); - if (!overlay->name) - return 1; - memcpy(overlay->name, lde->name, s + 1); - - lws_dll_add_front(&overlay->overlay_list, &nspawn->overlay_head); - - return 0; -} - -static int -sai_builder_scan_nspawn_cb(const char *dirpath, void *user, - struct lws_dir_entry *lde) -{ - struct sai_builder *builder = (struct sai_builder *)user; - size_t s = strlen(lde->name); - struct sai_nspawn *nspawn; - char path[256]; - - if (lde->type != LDOT_DIR) - return 0; - - nspawn = lwsac_use(&builder->conf_head, sizeof(*nspawn), - SAI_BUILDER_CONF_CHUNK); - if (!nspawn) - return 1; - - memset(nspawn, 0, sizeof(*nspawn)); - nspawn->builder = builder; - nspawn->name = lwsac_use(&builder->conf_head, s + 1, - SAI_BUILDER_CONF_CHUNK); - if (!nspawn->name) { - free(nspawn); - return 1; - } - memcpy((void *)nspawn->name, lde->name, s + 1); - - lws_dll_add_front(&nspawn->nspawn_list, &builder->nspawn_head); - - lws_snprintf(path, sizeof(path) - 1, "%s/%s", dirpath, lde->name); - - if (lws_dir(path, nspawn, sai_builder_scan_overlays_cb)) { - lwsl_err("%s: unable to scan nspawn dir %s\n", __func__, - builder->nspawn_path); - return 1; - } - - return 0; -} - -static signed char -sai_builder_conf_cb(struct lejp_ctx *ctx, char reason) -{ - struct jpargs *a = (struct jpargs *)ctx->user; - const char **pp; - int n; - -#if 0 - lwsl_notice(" %d: %s (%d)\n", reason, ctx->path, ctx->path_match); - for (n = 0; n < ctx->wildcount; n++) - lwsl_notice(" %d\n", ctx->wild[n]); -#endif - - if (reason == LEJPCB_OBJECT_START) { - switch (ctx->path_match - 1) { - case LEJPM_TARGET: - a->target = lwsac_use_zeroed(&a->builder->conf_head, - sizeof(*a->target), - SAI_BUILDER_CONF_CHUNK); - if (!a->target) - return 1; - lws_dll_add_front(&a->target->target_list, - &a->builder->target_head); - return 0; - - case LEJPM_TARGET_CONSOLES: - a->console = lwsac_use_zeroed(&a->builder->conf_head, - sizeof(*a->console), - SAI_BUILDER_CONF_CHUNK); - if (!a->console) - return 1; - lws_dll_add_front(&a->console->console_list, - &a->target->console_head); - return 0; - - case LEJPM_MASTERS: - a->master = lwsac_use_zeroed(&a->builder->conf_head, - sizeof(*a->master), - SAI_BUILDER_CONF_CHUNK); - if (!a->master) - return 1; - lws_dll_add_front(&a->master->master_list, - &a->builder->master_head); - return 0; - - default: - return 0; - } - } - - /* we only match on the prepared path strings */ - if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) - return 0; - - if (reason == LEJPCB_VAL_NUM_INT) { - n = atoi(ctx->buf); - switch (ctx->path_match - 1) { - case LEJPM_NSPAWN_INSTANCES: - a->builder->nspawn_instances = n; - break; - case LEJPM_NSPAWN_TIMEOUT: - a->builder->nspawn_timeout = n; - break; - case LEJPM_TARGET_TIMEOUT: - a->target->timeout = n; - break; - case LEJPM_TARGET_CONSOLES_BAUDRATE: - a->console->baudrate = n; - break; - case LEJPM_MASTERS_SELFSIGNED: - a->master->accept_selfsigned = n; - break; - case LEJPM_MASTERS_PRIORITY: - a->master->priority = n; - break; - default: - break; - } - - return 0; - } - - if (reason != LEJPCB_VAL_STR_END) - return 0; - - /* only the end part of the string, where we know the length */ - - switch (ctx->path_match - 1) { - case LEJPM_NSPAWN_PATH: - pp = &a->builder->nspawn_path; - break; - - case LEJPM_TARGET_NAME: - pp = &a->target->name; - break; - - case LEJPM_TARGET_CONSOLES_NAME: - pp = &a->console->name; - break; - - case LEJPM_TARGET_CONSOLES_PATH: - pp = &a->console->path; - break; - - case LEJPM_TARGET_CONSOLES_MODE: - pp = &a->console->mode; - break; - - case LEJPM_MASTERS_URL: - pp = &a->master->url; - break; - - case LEJPM_MASTERS_INTERFACE: - pp = &a->master->interface; - break; - - default: - return 0; - } - - *pp = lwsac_use(&a->builder->conf_head, ctx->npos + 1, - SAI_BUILDER_CONF_CHUNK); - if (!*pp) - return 1; - memcpy((char *)(*pp), ctx->buf, ctx->npos); - ((char *)(*pp))[ctx->npos] = '\0'; - - return 0; -} - -int -sai_builder_config(struct sai_builder *builder, const char *d) -{ - unsigned char buf[128]; - struct lejp_ctx ctx; - struct jpargs a; - int n, m, fd; - - memset(&a, 0, sizeof(a)); - a.builder = builder; - - lws_snprintf((char *)buf, sizeof(buf) - 1, "%s/conf", d); - - fd = lws_open((char *)buf, O_RDONLY); - if (fd < 0) { - lwsl_err("Cannot open %s\n", (char *)buf); - return 2; - } - lwsl_info("%s: %s\n", __func__, (char *)buf); - lejp_construct(&ctx, sai_builder_conf_cb, &a, paths, LWS_ARRAY_SIZE(paths)); - - do { - n = read(fd, buf, sizeof(buf)); - if (!n) - break; - - m = (int)(signed char)lejp_parse(&ctx, buf, n); - } while (m == LEJP_CONTINUE); - - close(fd); - n = ctx.line; - lejp_destruct(&ctx); - - if (m < 0) { - lwsl_err("%s/conf(%u): parsing error %d: %s\n", d, n, m, - lejp_error_to_string(m)); - return 2; - } - - /* scan the configured nspawn directory for what we support */ - - if (builder->nspawn_path && - lws_dir(builder->nspawn_path, builder, sai_builder_scan_nspawn_cb)) { - lwsl_err("%s: unable to scan nspawn dir %s\n", __func__, - builder->nspawn_path); - - return 1; - } - - return 0; -} - -void -sai_builder_config_destroy(struct sai_builder *builder) -{ - lwsac_free(&builder->conf_head); -} diff --git a/src/builder/private.h b/src/builder/private.h deleted file mode 100644 index 0bc1347..0000000 --- a/src/builder/private.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Sai builder definitions src/builder/private.h - * - * Copyright (C) 2019 Andy Green <andy@warmcat.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation: - * version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -struct sai_master; -struct sai_builder; - -enum sai_lease_states { - SOS_IDLE, - SOS_RESERVED, - SOS_CONFIRMED -}; - -struct sai_lease { - struct sai_master *lessor; - enum sai_lease_states state; -}; - -struct sai_console { - struct lws_dll console_list; - - const char *name; - const char *path; - const char *mode; - int baudrate; -}; - -struct sai_target { - struct lws_dll target_list; - struct lws_dll console_head; - - const char *name; - - struct sai_lease lease; - - int timeout; -}; - -struct sai_master { - struct lws_dll master_list; - - struct lws *cwsi; - - const char *url; - const char *interface; - int priority; - - unsigned int accept_selfsigned:1; -}; - -struct sai_overlay { - struct lws_dll overlay_list; - - char *name; -}; - -struct sai_nspawn { - struct lws_dll nspawn_list; - struct lws_dll overlay_head; - - struct sai_builder *builder; - - const char *name; - - struct sai_lease lease; -}; - -struct sai_builder { - struct lws_dll master_head; - struct lws_dll target_head; - struct lws_dll nspawn_head; - - struct lwsac *conf_head; - - const char *nspawn_path; - int nspawn_instances; - int nspawn_timeout; -}; - -extern const struct lws_protocols protocol_com_warmcat_sai; -extern int sai_builder_config(struct sai_builder *builder, const char *d); -extern void sai_builder_config_destroy(struct sai_builder *builder); diff --git a/src/builder/protocol.c b/src/builder/protocol.c deleted file mode 100644 index 736b35a..0000000 --- a/src/builder/protocol.c +++ /dev/null @@ -1,344 +0,0 @@ -/* - * sai-builder com-warmcat-sai client protocol implementation - * - * Copyright (C) 2019 Andy Green <andy@warmcat.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation: - * version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#include <libwebsockets.h> -#include <string.h> -#include <signal.h> - -#include "private.h" - -typedef enum { - PHASE_IDLE, - - PFL_FIRST = 128, - - PHASE_START_ATTACH = PFL_FIRST | 1, - PHASE_SUMM_TARGETS = 2, - PHASE_SUMM_NSPAWNS = 3, - PHASE_SUMM_NSPAWNS_OVERLAYS = 4, - -} cursor_phase_t; - -struct pss { - struct sai_target *target; - struct sai_console *console; - - struct sai_nspawn *nspawn; - struct sai_overlay *overlay; - - cursor_phase_t phase; -}; - -struct vhd { - struct lws_context *context; - struct lws_vhost *vhost; - - struct sai_builder builder; - - int *interrupted; - const char **config_dir; -}; - -static int -generate(struct vhd *vhd, struct pss *pss, char *buf, int len) -{ - struct sai_builder *b = &vhd->builder; - char *p = buf, *end = buf + len - 10; - - while (pss->phase != PHASE_IDLE && lws_ptr_diff(end, p) > 100) { - - switch (pss->phase) { - case PHASE_IDLE: - break; - - case PHASE_START_ATTACH: - p += lws_snprintf(p, lws_ptr_diff(end, p), - "{\"schema\":\"com-warmcat-sai-ba\",\n" - " \"hostname\":\"%s\",\n" - " \"nspawn-timeout\":%d,\n" - " \"targets\": [", - lws_canonical_hostname(vhd->context), - b->nspawn_timeout); - pss->target = lws_container_of(b->target_head.next, - struct sai_target, - target_list); - pss->phase = PHASE_SUMM_TARGETS; - break; - - case PHASE_SUMM_TARGETS: - if (pss->target) { - p += lws_snprintf(p, lws_ptr_diff(end, p), - "{\"name\":\"%s\"}", - pss->target->name); - if (pss->target->target_list.next) - *p++ = ','; - *p++ = '\n'; - *p = '\0'; - - pss->target = lws_container_of( - pss->target->target_list.next, - struct sai_target, target_list); - } - if (!pss->target) { - p += lws_snprintf(p, lws_ptr_diff(end, p), - "],\n\"nspawns\": ["); - pss->nspawn = lws_container_of( - b->nspawn_head.next, - struct sai_nspawn, nspawn_list); - pss->phase = PHASE_SUMM_NSPAWNS; - } - break; - - case PHASE_SUMM_NSPAWNS: - if (pss->nspawn) { - *p++ = '{'; - if (pss->nspawn->name) - p += lws_snprintf(p, lws_ptr_diff(end, p), - "\"name\":\"%s\",\n", - pss->nspawn->name); - p += lws_snprintf(p, lws_ptr_diff(end, p), - " \"overlays\": ["); - - pss->overlay = lws_container_of( - pss->nspawn->overlay_head.next, - struct sai_overlay, overlay_list); - if (pss->overlay) { - pss->phase = PHASE_SUMM_NSPAWNS_OVERLAYS; - break; - } -next_nspawn: - *p++ = ']'; - *p++ = '}'; - if (pss->nspawn->nspawn_list.next) - *p++ = ','; - *p++ = '\n'; - *p = '\0'; - pss->nspawn = lws_container_of( - pss->nspawn->nspawn_list.next, - struct sai_nspawn, nspawn_list); - } - if (!pss->nspawn) { - p += lws_snprintf(p, lws_ptr_diff(end, p), "]\n"); - p += lws_snprintf(p, lws_ptr_diff(end, p), "}\n"); - pss->phase = PHASE_IDLE; - } - break; - - case PHASE_SUMM_NSPAWNS_OVERLAYS: - if (pss->overlay) { - p += lws_snprintf(p, lws_ptr_diff(end, p), - "{\"name\":\"%s\"}\n", - pss->overlay->name); - pss->overlay = lws_container_of( - pss->overlay->overlay_list.next, - struct sai_overlay, overlay_list); - if (pss->overlay) { - - } - } - if (!pss->overlay) { - pss->phase = PHASE_SUMM_NSPAWNS; - goto next_nspawn; - } - break; - - default: - break; - } - } - - return lws_ptr_diff(p, buf); -} - -static int -connect_client(struct vhd *vhd, struct sai_master *master) -{ - struct lws_client_connect_info i; - const char *prot; - char url[128], host[64]; - - memset(&i, 0, sizeof(i)); - - strncpy(url, master->url, sizeof(url) - 1); - url[sizeof(url) - 1] = '\0'; - - if (lws_parse_uri(url, &prot, &i.address, &i.port, &i.path)) { - lwsl_err("%s: unable to parse url '%s'\n", __func__, url); - - return 1; - } - - lws_snprintf(host, sizeof(host), "%s:%u", i.address, i.port); - - i.context = vhd->context; - i.host = host; - i.origin = host; - - if (!strcmp(prot, "https") || !strcmp(prot, "wss")) - i.ssl_connection = LCCSCF_USE_SSL; - if (master->accept_selfsigned) - i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; - i.vhost = vhd->vhost; - i.iface = master->interface; - i.pwsi = &master->cwsi; - i.opaque_user_data = (void *)master; - - lwsl_user("connecting to %s://%s:%d/%s\n", prot, i.address, i.port, i.path); - - return !lws_client_connect_via_info(&i); -} - -static void -schedule_callback(struct lws *wsi, int reason, int secs) -{ - lws_timed_callback_vh_protocol(lws_get_vhost(wsi), - lws_get_protocol(wsi), reason, secs); -} - -static int -callback_com_warmcat_sai(struct lws *wsi, enum lws_callback_reasons reason, - void *user, void *in, size_t len) -{ - struct pss *pss = (struct pss *)user; - struct vhd *vhd = (struct vhd *) - lws_protocol_vh_priv_get(lws_get_vhost(wsi), - lws_get_protocol(wsi)); - struct sai_master *master = - (struct sai_master *)lws_get_opaque_user_data(wsi); - uint8_t buf[1024 + LWS_PRE], *start = buf + LWS_PRE, - *end = buf + sizeof(buf) -1; - int n, m, f; - - switch (reason) { - - case LWS_CALLBACK_PROTOCOL_INIT: - vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), - lws_get_protocol(wsi), - sizeof(struct vhd)); - if (!vhd) - return -1; - - vhd->context = lws_get_context(wsi); - vhd->vhost = lws_get_vhost(wsi); - - /* get the pointer to "interrupted" we were passed in pvo */ - vhd->interrupted = (int *)lws_pvo_search( - (const struct lws_protocol_vhost_options *)in, - "interrupted")->value; - vhd->config_dir = (const char **)lws_pvo_search( - (const struct lws_protocol_vhost_options *)in, - "configdir")->value; - - if (sai_builder_config(&vhd->builder, *vhd->config_dir)) { - lwsl_err("%s: config failed\n", __func__); - - return 1; - } - - schedule_callback(wsi, LWS_CALLBACK_USER, 1); - break; - - case LWS_CALLBACK_PROTOCOL_DESTROY: - sai_builder_config_destroy(&vhd->builder); - break; - - case LWS_CALLBACK_CLIENT_ESTABLISHED: - pss->phase = PHASE_START_ATTACH; - lws_callback_on_writable(wsi); - break; - - case LWS_CALLBACK_CLIENT_WRITEABLE: - lwsl_user("LWS_CALLBACK_CLIENT_WRITEABLE\n"); - - if (pss->phase == PHASE_IDLE) - break; - - f = pss->phase & PFL_FIRST; - n = generate(vhd, pss, (char *)start, lws_ptr_diff(end, start)); - - m = lws_write(wsi, start, n, - lws_write_ws_flags(LWS_WRITE_TEXT, f, - pss->phase == PHASE_IDLE)); - if (m < n) { - lwsl_err("ERROR %d writing to ws socket\n", m); - return -1; - } - - if (pss->phase != PHASE_IDLE) - lws_callback_on_writable(wsi); - break; - - case LWS_CALLBACK_CLIENT_RECEIVE: - - break; - - case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: - lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", - in ? (char *)in : "(null)"); - master->cwsi = NULL; - schedule_callback(wsi, LWS_CALLBACK_USER, 1); - break; - - case LWS_CALLBACK_CLIENT_CLOSED: - lwsl_user("LWS_CALLBACK_CLIENT_CLOSED\n"); - master->cwsi = NULL; - schedule_callback(wsi, LWS_CALLBACK_USER, 1); - break; - - /* rate-limited client connect retries */ - - case LWS_CALLBACK_USER: - /* - * let's retry any master connections that aren't either - * ongoing or happy - */ - - m = 0; - lws_start_foreach_dll(struct lws_dll *, mp, - vhd->builder.master_head.next) { - struct sai_master *ma = lws_container_of(mp, - struct sai_master, master_list); - - if (!ma->cwsi && connect_client(vhd, ma)) - m = 1; - } lws_end_foreach_dll(mp); - - /* - * if somebody's already unhappy, schedule a retry. Otherwise - * wait until a connection close or error to do it then. - */ - if (m) - schedule_callback(wsi, LWS_CALLBACK_USER, 1); - break; - - default: - break; - } - - return 0; -} - -const struct lws_protocols protocol_com_warmcat_sai = { - "com-warmcat-sai", - callback_com_warmcat_sai, - sizeof(struct pss), - 1024, 0, NULL, 0 -}; diff --git a/src/builder/sai-builder.c b/src/builder/sai-builder.c deleted file mode 100644 index 1760330..0000000 --- a/src/builder/sai-builder.c +++ /dev/null @@ -1,114 +0,0 @@ -/* - * sai-builder - * - * Copyright (C) 2019 Andy Green <andy@warmcat.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation: - * version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#include <libwebsockets.h> -#include <string.h> -#include <signal.h> - -#include "private.h" - -static struct lws_context *context; -static const char *config_dir = "/etc/sai/builder"; -static int interrupted; - -static const struct lws_protocols *pprotocols[] = { - &protocol_com_warmcat_sai, - NULL -}; - -static const struct lws_protocol_vhost_options pvo_configdir = { - NULL, - NULL, - "configdir", /* pvo name */ - (void *)&config_dir /* pvo value */ -}; - -static const struct lws_protocol_vhost_options pvo_interrupted = { - &pvo_configdir, - NULL, - "interrupted", /* pvo name */ - (void *)&interrupted /* pvo value */ -}; - -static const struct lws_protocol_vhost_options pvo = { - NULL, /* "next" pvo linked-list */ - &pvo_interrupted, /* "child" pvo linked-list */ - "com-warmcat-sai", /* protocol name we belong to on this vhost */ - "" /* ignored */ -}; -static const struct lws_extension extensions[] = { - { - "permessage-deflate", - lws_extension_callback_pm_deflate, - "permessage-deflate" - "; client_no_context_takeover" - "; client_max_window_bits" - }, - { NULL, NULL, NULL /* terminator */ } -}; - -void sigint_handler(int sig) -{ - interrupted = 1; -} - -int main(int argc, const char **argv) -{ - struct lws_context_creation_info info; - const char *p; - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; - - if ((p = lws_cmdline_option(argc, argv, "-d"))) - logs = atoi(p); - - lws_set_log_level(logs, NULL); - lwsl_user("Sai Builder - " - "Copyright (C) 2019 Andy Green <andy@warmcat.com>\n"); - lwsl_user(" sai-builder [-c <config-file>]\n"); - - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - info.port = CONTEXT_PORT_NO_LISTEN; - info.pprotocols = pprotocols; - info.pvo = &pvo; - if (!lws_cmdline_option(argc, argv, "-n")) - info.extensions = extensions; - info.pt_serv_buf_size = 32 * 1024; - info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | - LWS_SERVER_OPTION_VALIDATE_UTF8; - - if (lws_cmdline_option(argc, argv, "--libuv")) - info.options |= LWS_SERVER_OPTION_LIBUV; - else - signal(SIGINT, sigint_handler); - - context = lws_create_context(&info); - if (!context) { - lwsl_err("lws init failed\n"); - return 1; - } - - while (!lws_service(context, 1000) && !interrupted) - ; - - lws_context_destroy(context); - - return 0; -} diff --git a/src/common/include/private.h b/src/common/include/private.h new file mode 100644 index 0000000..f4ec979 --- /dev/null +++ b/src/common/include/private.h @@ -0,0 +1,284 @@ +/* + * Sai - ./src/common/include/private.h + * + * Copyright (C) 2019-2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * structs common to builder and master + */ + +#if defined(__linux__) +#define UDS_PATHNAME_LOGPROXY "@com.warmcat.com.saib.logproxy" +#else +#define UDS_PATHNAME_LOGPROXY "/var/run/com.warmcat.com.saib.logproxy" +#endif + +struct sai_plat; +struct sai_builder; + +typedef enum { + SAIES_WAITING = 0, + SAIES_PASSED_TO_BUILDER = 1, + SAIES_BEING_BUILT = 2, + SAIES_SUCCESS = 3, + SAIES_FAIL = 4, + SAIES_CANCELLED = 5, + SAIES_BEING_BUILT_HAS_FAILURES = 6, +} sai_event_state_t; + +enum { + SAISPRF_TIMEDOUT = 0x1000, + SAISPRF_TERMINATED = 0x2000, + SAISPRF_EXIT = 0x8000, + SAISPRF_SIGNALLED = 0x4000, +}; + +/* + * Builder is indicating he can't take the task and master should free it up + * and try another builder. + */ + +typedef struct sai_rejection { + struct lws_dll2 list; + char host_platform[65]; + char task_uuid[65]; + int ongoing; + int limit; +} sai_rejection_t; + +/* + * Master is broadcasting that builders should stop work on the given task, + * because, eg, the task was reset + */ + +typedef struct sai_cancel { + struct lws_dll2 list; + char task_uuid[65]; +} sai_cancel_t; + + +struct sai_event; + +typedef struct { + lws_dll2_t list; + lws_dll2_t pending_assign_list; + const struct sai_event *one_event; /* event we are associated with */ + char platform[96]; + char build[4096]; /* strsubst and serialized */ + char taskname[96]; + char packages[2048]; + char artifacts[256]; + char prep[4096]; /* only build is serialized */ + char cmake[4096]; /* only build is serialized */ + char builder[65 + 5]; + char event_uuid[65]; + char art_up_nonce[33]; + char art_down_nonce[33]; + char uuid[65]; + char builder_name[96]; + char cpack[128]; + + struct lwsac *ac_task_container; + + const char *master_name; /* used in offer */ + const char *repo_name; /* used in offer */ + const char *git_ref; /* used in offer */ + const char *git_hash; /* used in offer */ + const char *git_repo_url; /* used in offer */ + uint64_t last_updated; + uint64_t started; + uint64_t duration; + int state; + int uid; + + char told_ongoing; +} sai_task_t; + +typedef struct sai_event { + struct lws_dll2 list; + lws_dll2_owner_t task_owner; + char repo_name[65]; + char repo_fetchurl[96]; + char ref[65]; + char hash[65]; + char uuid[65]; + char source_ip[32]; + void *pdb; /* master only, sqlite3 */ + uint64_t created; + uint64_t last_updated; + sai_event_state_t state; + int uid; +} sai_event_t; + +typedef struct { + struct lws_dll2 list; + char task_uuid[65]; + char *log; + uint64_t timestamp; + size_t len; + int finished; + int channel; + int uid; +} sai_log_t; + +typedef struct { + struct lws_dll2 list; + + struct lws_ss_handle *ss; + void *opaque_data; + + char task_uuid[65]; + char artifact_up_nonce[33]; + char artifact_down_nonce[33]; + char blob_filename[65]; + char path[256]; /* for unlink on completion */ + void *blob; + uint64_t ofs; + uint64_t timestamp; + size_t len; + int uid; + int fd; + char sent_json; +} sai_artifact_t; + +struct sai_plat; + +/* + * One SS per unique master the builder connects to; one of these as the SS + * userdata object + * + * May be in use by multiple plats offered by same builder to same master + */ + +typedef struct sai_plat_master { + lws_dll2_t list; + + lws_dll2_owner_t rejection_list; + + const char *url; + const char *name; + + struct lws_ss_handle *ss; + void *opaque_data; + + lws_dll2_t *last_logging_nspawn; + struct sai_plat *last_logging_platform; + + int logs_in_flight; + int phase; + int refcount; + + int index; /* used to create unique build dir path */ + + uint16_t retries; +} sai_plat_master_t; + +struct sai_env { + lws_dll2_t list; + + const char *name; + const char *value; +}; + +typedef struct sai_plat_master_ref { + lws_dll2_t list; + sai_plat_master_t *spm; +} sai_plat_master_ref_t; + +/* + * One of these instantiated per platform instance + * + * It lists a sai_plat_master per master / ss that can use the platform + * + * It contains an nspawn for each platform builder instance + * + * It's also used as the object on master side that represents a builder / + * platform instance and status. + */ + +typedef struct sai_plat { + lws_dll2_t sai_plat_list; + + lws_dll2_owner_t masters; /* list of sai_plat_master_ref_t */ + lws_dll2_owner_t chunk_cache; + + const char *name; + const char *platform; + + struct lejp_ctx ctx; + lws_dll2_owner_t nspawn_owner; + struct lwsac *deserialization_ac; + + struct lws *wsi; /* master side only */ + + lws_dll2_owner_t env_head; + + int instances; + int ongoing; + + int index; /* used to create unique build dir path */ +} sai_plat_t; + +typedef struct sai_plat_owner { + lws_dll2_owner_t plat_owner; +} sai_plat_owner_t; + +typedef struct sai_repo { + char *name; + char *fetch_url; + char *notification_key; +} sai_repo_t; + +typedef enum { + SAILOGA_STARTED, +} sai_log_action_t; + +extern const lws_struct_map_t + lsm_schema_json_map_task[], + lsm_schema_sq3_map_task[], + lsm_schema_sq3_map_event[], + lsm_schema_json_map_log[], + lsm_schema_sq3_map_log[], + lsm_schema_json_map_artifact[1], + lsm_schema_sq3_map_artifact[1], + lsm_schema_map_ta[1], + lsm_schema_map_plat_simple[1], + lsm_event[9], + lsm_task[21], + lsm_log[7], + lsm_artifact[8], + lsm_plat_list[1], + lsm_task_rej[4], + lsm_task_cancel[1], + lsm_schema_json_map_can[1], + lsm_schema_json_map_task[1], + lsm_schema_json_map_event[1] +; + +extern const lws_ss_info_t ssi_said_logproxy; +extern struct lws_ss_handle *ssh[3]; + +typedef void (*saicom_drain_cb)(void *opaque); + +int +saicom_lp_add(struct lws_ss_handle *h, const char *buf, size_t len); + +struct lws_ss_handle * +saicom_lp_ss_from_env(struct lws_context *context, const char *env_name); + +int +saicom_lp_callback_on_drain(saicom_drain_cb cb, void *opaque); diff --git a/src/common/ss-client-logproxy.c b/src/common/ss-client-logproxy.c new file mode 100644 index 0000000..df42962 --- /dev/null +++ b/src/common/ss-client-logproxy.c @@ -0,0 +1,206 @@ +/* + * sai-device ss logproxy link implementation + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#if defined(WIN32) /* complaints about getenv */ +#define _CRT_SECURE_NO_WARNINGS +#define read _read +#define close _close +#endif + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> + +#include "../common/include/private.h" + +typedef struct said_log { + lws_dll2_t list; + size_t pos; + size_t len; +} said_log_t; + +typedef struct said_logproxy { + lws_dll2_owner_t logs; + struct lws_ss_handle *ss; + void *opaque_data; +} said_logproxy_t; + +static saicom_drain_cb cb; +static void *opaque; + +struct lws_ss_handle * +saicom_lp_ss_from_env(struct lws_context *context, const char *env_name) +{ + char *e = getenv(env_name); + struct lws_ss_handle *h; + + if (!e) { + lwsl_notice("%s: no env var %s\n", __func__, env_name); + return NULL; + } + + lwsl_info("%s: connecting to %s\n", __func__, e); + + if (lws_ss_create(context, 0, &ssi_said_logproxy, 0, &h, NULL, NULL)) { + lwsl_err("%s: failed to create SS for %s\n", __func__, env_name); + return NULL; + } + + lws_ss_set_metadata(h, "sockpath", e, strlen(e)); + lws_ss_client_connect(h); + + return h; +} + +int +saicom_lp_add(struct lws_ss_handle *h, const char *buf, size_t len) +{ + said_logproxy_t *lp = lws_ss_to_user_object(h); + said_log_t *log = malloc(sizeof(*log) + len); + + if (!log) + return 1; + memset(log, 0, sizeof(*log)); + memcpy(&log[1], buf, len); + + log->len = len; + lws_dll2_add_tail(&log->list, &lp->logs); + lws_ss_request_tx(h); + + return 0; +} + +void +check_drained(void) +{ + int n, m = 0; + + if (!cb) + return; + + for (n = 0; n < (int)LWS_ARRAY_SIZE(ssh); n++) { + + said_logproxy_t *lp = lws_ss_to_user_object(ssh[n]); + + if (lp->logs.head) + m = 1; + } + + if (!m) { + cb(opaque); + cb = NULL; + } +} + +int +saicom_lp_callback_on_drain(saicom_drain_cb _cb, void *_opaque) +{ + cb = _cb; + opaque = _opaque; + + check_drained(); + + return 0; +} + +static int +saicom_lp_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ + return 0; +} + +static int +saicom_lp_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, + int *flags) +{ + said_logproxy_t *lp = (said_logproxy_t *)userobj; + said_log_t *log = lws_container_of(lp->logs.head, said_log_t, list); + + if (!lp->logs.head) + return 1; /* nothing to send */ + + if (!log->pos) + *flags = LWSSS_FLAG_SOM; + + if (*len > log->len - log->pos) + *len = log->len - log-> pos; + + memcpy(buf, ((char *)&log[1]) + log->pos, *len); + log->pos += *len; + if (log->pos == log->len) { + lws_dll2_remove(&log->list); + free(log); + *flags |= LWSSS_FLAG_EOM; + } + + if (lp->logs.head) { + lws_ss_request_tx(lp->ss); + + return 0; + } + + check_drained(); + + return 0; +} + +static int +saicom_lp_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + said_logproxy_t *lp = (said_logproxy_t *)userobj; + + lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state), + (unsigned int)ack); + + switch (state) { + case LWSSSCS_DESTROYING: + break; + + case LWSSSCS_CONNECTED: + lws_ss_request_tx(lp->ss); + break; + + case LWSSSCS_DISCONNECTED: + break; + + case LWSSSCS_ALL_RETRIES_FAILED: + break; + + case LWSSSCS_QOS_ACK_REMOTE: + break; + + default: + break; + } + + return 0; +} + +const lws_ss_info_t ssi_said_logproxy = { + .handle_offset = offsetof(said_logproxy_t, ss), + .opaque_user_data_offset = offsetof(said_logproxy_t, opaque_data), + .rx = saicom_lp_rx, + .tx = saicom_lp_tx, + .state = saicom_lp_state, + .user_alloc = sizeof(said_logproxy_t), + .streamtype = "logproxy" +}; diff --git a/src/common/struct-metadata.c b/src/common/struct-metadata.c new file mode 100644 index 0000000..8bfbdde --- /dev/null +++ b/src/common/struct-metadata.c @@ -0,0 +1,192 @@ +/* + * Sai master - ./src/master/struct-metadata.c + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * lws_struct metadata for structs common to builder and master + */ + +static const lws_struct_map_t lsm_plat[] = { + LSM_STRING_PTR (sai_plat_t, name, "name"), + LSM_UNSIGNED (sai_plat_t, ongoing, "ongoing"), + LSM_UNSIGNED (sai_plat_t, instances, "instances"), + LSM_STRING_PTR (sai_plat_t, platform, "platform"), +}; + +const lws_struct_map_t lsm_schema_map_plat_simple[] = { + LSM_SCHEMA (sai_plat_t, NULL, lsm_plat, "com-warmcat-sai-ba"), +}; + +const lws_struct_map_t lsm_plat_list[] = { + LSM_LIST (sai_plat_owner_t, plat_owner, sai_plat_t, + sai_plat_list, NULL, lsm_plat, "platforms"), +}; + +const lws_struct_map_t lsm_schema_map_plat[] = { + LSM_SCHEMA_DLL2 (sai_plat_owner_t, plat_owner, NULL, lsm_plat_list, + "com-warmcat-sai-ba"), +}; + +const lws_struct_map_t lsm_event[] = { + LSM_UNSIGNED (sai_event_t, uid, "uid"), + LSM_CARRAY (sai_event_t, repo_name, "repo_name"), + LSM_CARRAY (sai_event_t, ref, "ref"), + LSM_CARRAY (sai_event_t, hash, "hash"), + LSM_CARRAY (sai_event_t, uuid, "uuid"), + LSM_CARRAY (sai_event_t, source_ip, "source_ip"), + LSM_UNSIGNED (sai_event_t, created, "created"), + LSM_UNSIGNED (sai_event_t, state, "state"), + LSM_UNSIGNED (sai_event_t, last_updated, "last_updated"), +}; + +const lws_struct_map_t lsm_schema_json_map_event[] = { + LSM_SCHEMA_DLL2 (sai_event_t, list, NULL, lsm_event, + "com.warmcat.sai.events"), +}; + +const lws_struct_map_t lsm_schema_sq3_map_event[] = { + LSM_SCHEMA_DLL2 (sai_event_t, list, NULL, lsm_event, "events"), +}; + +/* + * tasks are bound to an event, many tasks may be generated from one event. + * + * platform string subst is already done by the time we elaborate the saifile + * into tasks + */ + +const lws_struct_map_t lsm_task[] = { + LSM_UNSIGNED (sai_task_t, uid, "uid"), + LSM_UNSIGNED (sai_task_t, state, "state"), + LSM_UNSIGNED (sai_task_t, last_updated, "last_updated"), + LSM_UNSIGNED (sai_task_t, started, "started"), + LSM_UNSIGNED (sai_task_t, duration, "duration"), + LSM_CARRAY (sai_task_t, platform, "platform"), + LSM_CARRAY (sai_task_t, build, "build"), + LSM_CARRAY (sai_task_t, taskname, "taskname"), + LSM_CARRAY (sai_task_t, packages, "packages"), + LSM_CARRAY (sai_task_t, builder, "builder"), + LSM_CARRAY (sai_task_t, artifacts, "artifacts"), + LSM_CARRAY (sai_task_t, art_up_nonce, "art_up_nonce"), + LSM_CARRAY (sai_task_t, art_down_nonce, "art_down_nonce"), + LSM_CARRAY (sai_task_t, event_uuid, "event_uuid"), + LSM_CARRAY (sai_task_t, uuid, "uuid"), + LSM_CARRAY (sai_task_t, builder_name, "builder_name"), + LSM_STRING_PTR (sai_task_t, master_name, "master_name"), + LSM_STRING_PTR (sai_task_t, repo_name, "repo_name"), + LSM_STRING_PTR (sai_task_t, git_ref, "git_ref"), + LSM_STRING_PTR (sai_task_t, git_hash, "git_hash"), + LSM_STRING_PTR (sai_task_t, git_repo_url, "git_repo_url"), +}; + +const lws_struct_map_t lsm_schema_json_map_task[] = { + LSM_SCHEMA_DLL2 (sai_task_t, list, NULL, lsm_task, + "com.warmcat.sai.tasks"), +}; + +const lws_struct_map_t lsm_schema_sq3_map_task[] = { + LSM_SCHEMA_DLL2 (sai_task_t, list, NULL, lsm_task, "tasks"), +}; + +/* builder -> master */ + +const lws_struct_map_t lsm_task_rej[] = { + LSM_CARRAY (sai_rejection_t, host_platform, "host_platform"), + LSM_CARRAY (sai_rejection_t, task_uuid, "task_uuid"), + LSM_UNSIGNED (sai_rejection_t, ongoing, "ongoing"), + LSM_UNSIGNED (sai_rejection_t, limit, "limit"), +}; + +const lws_struct_map_t lsm_schema_json_task_rej[] = { + LSM_SCHEMA (sai_event_t, NULL, lsm_task_rej, + "com.warmcat.sai.taskrej") +}; + +/* master -> builder */ + +const lws_struct_map_t lsm_task_cancel[] = { + LSM_CARRAY (sai_cancel_t, task_uuid, "task_uuid"), +}; + +const lws_struct_map_t lsm_schema_json_map_can[] = { + LSM_SCHEMA (sai_cancel_t, NULL, lsm_task_cancel, + "com.warmcat.sai.taskcan") +}; + +/* + * logs are bound to a task... many log entries will typically belong to one + * task, one is created each time the builder reads something on stdout, stderr + * or an auxiliary logging channel. + * + * This requires more storage than squashing it into a simple string, but allows + * individual timestamping and log channel source for each part to be shown in + * the UI. + */ + +const lws_struct_map_t lsm_log[] = { + LSM_UNSIGNED (sai_log_t, uid, "uid"), + LSM_UNSIGNED (sai_log_t, len, "len"), + LSM_UNSIGNED (sai_log_t, timestamp, "timestamp"), + LSM_UNSIGNED (sai_log_t, channel, "channel"), + LSM_UNSIGNED (sai_log_t, finished, "finished"), + LSM_CARRAY (sai_log_t, task_uuid, "task_uuid"), + LSM_STRING_PTR (sai_log_t, log, "log"), +}; + +const lws_struct_map_t lsm_schema_json_map_log[] = { + LSM_SCHEMA_DLL2 (sai_log_t, list, NULL, lsm_log, "com-warmcat-sai-logs"), +}; + +const lws_struct_map_t lsm_schema_sq3_map_log[] = { + LSM_SCHEMA_DLL2 (sai_log_t, list, NULL, lsm_log, "logs"), +}; + +/* + * Artifacts live in their own table in the event-specific db, and refer back to + * a task uuid. The build decides how many artifacts exist for a task (the + * saifile may give globs like "sai-*.rpm"), the exact filenames and lengths, + * and uploads them after the task completes. + * + * Notice the LSM_BLOB_PTR is just there to ensure the column exists in the + * table schema. The bulk data for that is not serialized and deserialized + * as part of lws_struct like the other types, since its size is unbounded and + * it may not make sense to base64 encode it inside JSON or even take it all + * out of the database in one hit at all. + */ + +const lws_struct_map_t lsm_artifact[] = { + LSM_UNSIGNED (sai_artifact_t, uid, "uid"), + LSM_CARRAY (sai_artifact_t, task_uuid, "task_uuid"), + LSM_CARRAY (sai_artifact_t, blob_filename, "blob_filename"), + /* created master-side, sent back with valid upload, checked at master */ + LSM_CARRAY (sai_artifact_t, artifact_up_nonce, "artifact_up_nonce"), + /* created master-side (not sent to builder), used in artifact links we generate */ + LSM_CARRAY (sai_artifact_t, artifact_down_nonce, "artifact_down_nonce"), + LSM_BLOB_PTR (sai_artifact_t, blob, "blob"), + LSM_UNSIGNED (sai_artifact_t, timestamp, "timestamp"), + LSM_UNSIGNED (sai_artifact_t, len, "len"), +}; + +const lws_struct_map_t lsm_schema_json_map_artifact[] = { + LSM_SCHEMA_DLL2 (sai_artifact_t, list, NULL, lsm_artifact, + "com-warmcat-sai-artifact"), +}; + +const lws_struct_map_t lsm_schema_sq3_map_artifact[] = { + LSM_SCHEMA_DLL2 (sai_artifact_t, list, NULL, lsm_artifact, "artifacts"), +}; diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt new file mode 100644 index 0000000..fd2391e --- /dev/null +++ b/src/device/CMakeLists.txt @@ -0,0 +1,44 @@ +set(CPACK_DEBIAN_BUILDER_PACKAGE_NAME "sai-device") + +set(SRCS + d-sai.c + d-conf.c + ../common/ss-client-logproxy.c +) + +set(requirements 1) +require_lws_config(LWS_WITH_CLIENT 1 requirements) +require_lws_config(LWS_WITH_SPAWN 1 requirements) +require_lws_config(LWS_WITH_STRUCT_JSON 1 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) +require_lws_config(LWS_WITH_DIR 1 requirements) + +if (requirements) + + add_executable("sai-device" ${SRCS}) + if (APPLE) + set_property(TARGET sai-device PROPERTY MACOSX_RPATH YES) + endif() + + target_link_libraries("sai-device" ${SAI_LWS_LIB_PATH}) + + if (LWS_OPENSSL_LIBRARIES) + target_link_libraries("sai-device" ${LWS_OPENSSL_LIBRARIES}) + endif() + + if (SAI_EXT_PTHREAD_LIBRARIES) + target_link_libraries("sai-device" ${SAI_EXT_PTHREAD_LIBRARIES}) + endif() + if (HAS_LIBCAP) + target_link_libraries("sai-device" ${CAP_LIB_PATH}) + endif() + + if (MSVC OR WIN32) + target_link_libraries("sai-device" ws2_32.lib userenv.lib psapi.lib iphlpapi.lib) + endif() + + install(TARGETS "sai-device" + RUNTIME DESTINATION "${BIN_DIR}" COMPONENT device) + +endif(requirements) +include(CPack) diff --git a/src/device/d-conf.c b/src/device/d-conf.c new file mode 100644 index 0000000..078a25c --- /dev/null +++ b/src/device/d-conf.c @@ -0,0 +1,246 @@ +/* + * sai-device conf.c + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#if defined(WIN32) /* complaints about getenv */ +#define _CRT_SECURE_NO_WARNINGS +#define read _read +#define close _close +#endif + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <time.h> +#include <fcntl.h> + +#include "d-private.h" + +/* global part */ + +static const char * const paths_global[] = { + "home", + + "devices[].name", + "devices[].type", + "devices[].compatible", + "devices[].description", + "devices[].ttys[].name", + "devices[].ttys[].tty", + "devices[].ttys[].rate", + "devices[].ttys[].initial_monitor", + "devices[].ttys[]", + "devices[]", +}; + +enum enum_paths_global { + LEJPM_HOME, + + LEJPM_DEVICES__NAME, + LEJPM_DEVICES__TYPE, + LEJPM_DEVICES__COMPATIBLE, + LEJPM_DEVICES__DESCRIPTION, + LEJPM_DEVICES__TTYS__NAME, + LEJPM_DEVICES__TTYS__TTY, + LEJPM_DEVICES__TTYS__RATE, + LEJPM_DEVICES__TTYS__INITIAL_MONITOR, + LEJPM_DEVICES__TTYS, + LEJPM_DEVICES, + +}; + + +struct jpargs { + sai_devices_t *devices; + + sai_device_t *dev; + sai_tty_t *tty; +}; + + +static signed char +said_conf_global_cb(struct lejp_ctx *ctx, char reason) +{ + struct jpargs *a = (struct jpargs *)ctx->user; + const char **pp; +#if 0 + int n; + + lwsl_notice("%s: reason: %d, path: %s, match %d\n", __func__, + reason, ctx->path, ctx->path_match); + for (n = 0; n < ctx->wildcount; n++) + lwsl_notice(" %d\n", ctx->wild[n]); +#endif + + if (reason == LEJPCB_OBJECT_START && + ctx->path_match - 1 == LEJPM_DEVICES) { + + /* + * We're starting on defining a new device + */ + + a->dev = lwsac_use_zero(&a->devices->conf_head, sizeof(*a->dev), 512); + if (!a->dev) + return -1; + lws_dll2_add_tail(&a->dev->list, &a->devices->devices_owner); + a->tty = NULL; + return 0; + } + + if (reason == LEJPCB_OBJECT_START && + ctx->path_match - 1 == LEJPM_DEVICES__TTYS) { + + /* + * We're starting on defining a new tty for our device + */ + + a->tty = lwsac_use_zero(&a->devices->conf_head, sizeof(*a->tty), 512); + if (!a->tty) + return -1; + + a->tty->initial_monitor = 1; + a->tty->index = a->dev->ttys_owner.count; + lws_dll2_add_tail(&a->tty->list, &a->dev->ttys_owner); + return 0; + } + + /* we only match on the prepared path strings */ + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + if (reason == LEJPCB_VAL_NUM_INT && + ctx->path_match - 1 == LEJPM_DEVICES__TTYS__RATE) { + a->tty->rate = atoi(ctx->buf); + return 0; + } + + if (reason == LEJPCB_VAL_FALSE && + ctx->path_match - 1 == LEJPM_DEVICES__TTYS__INITIAL_MONITOR) { + a->tty->initial_monitor = 0; + return 0; + } + + if (reason != LEJPCB_VAL_STR_END) + return 0; + + /* only the end part of the string, where we know the length */ + + switch (ctx->path_match - 1) { + case LEJPM_HOME: + pp = &a->devices->home; + break; + + case LEJPM_DEVICES__NAME: + pp = &a->dev->name; + break; + case LEJPM_DEVICES__TYPE: + pp = &a->dev->type; + break; + case LEJPM_DEVICES__COMPATIBLE: + pp = &a->dev->compatible; + break; + case LEJPM_DEVICES__DESCRIPTION: + pp = &a->dev->description; + break; + case LEJPM_DEVICES__TTYS__NAME: + pp = &a->tty->name; + break; + case LEJPM_DEVICES__TTYS__TTY: + pp = &a->tty->tty_path; + break; + + default: + return 0; + } + + *pp = lwsac_use(&a->devices->conf_head, ctx->npos + 1, 512); + if (!*pp) + return 1; + memcpy((char *)(*pp), ctx->buf, ctx->npos); + ((char *)(*pp))[ctx->npos] = '\0'; + + return 0; +} + +int +said_config_global(struct sai_devices *devices, const char *d) +{ + unsigned char buf[128]; + struct lejp_ctx ctx; + int n, m, fd; + struct jpargs a; + + memset(&a, 0, sizeof(a)); + a.devices = devices; + +#if defined(WIN32) + lws_snprintf((char *)buf, sizeof(buf) - 1, "%s\\conf", d); +#else + lws_snprintf((char *)buf, sizeof(buf) - 1, "%s/conf", d); +#endif + + fd = lws_open((char *)buf, O_RDONLY); + if (fd < 0) { + lwsl_err("Cannot open %s\n", (char *)buf); + return 2; + } + lwsl_info("%s: %s\n", __func__, (char *)buf); + lejp_construct(&ctx, said_conf_global_cb, &a, + paths_global, LWS_ARRAY_SIZE(paths_global)); + + do { + n = read(fd, buf, sizeof(buf)); + if (!n) + break; + + m = lejp_parse(&ctx, buf, n); + } while (m == LEJP_CONTINUE); + + close(fd); + n = ctx.line; + lejp_destruct(&ctx); + + lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, + devices->devices_owner.head) { + sai_device_t *d = lws_container_of(p, sai_device_t, list); + + lwsl_info("%s: device '%s', %s, %s, %s\n", __func__, + d->name, d->type, d->compatible, d->description); + + lws_start_foreach_dll_safe(struct lws_dll2 *, q, q1, + d->ttys_owner.head) { + sai_tty_t *t = lws_container_of(q, sai_tty_t, list); + + lwsl_info("%s: tty %s\n", __func__, + t->tty_path); + + } lws_end_foreach_dll_safe(q, q1); + + } lws_end_foreach_dll_safe(p, p1); + + return 0; +} + + +void +said_config_destroy(struct sai_devices *devices) +{ + lwsac_free(&devices->conf_head); +} diff --git a/src/device/d-private.h b/src/device/d-private.h new file mode 100644 index 0000000..232b4d7 --- /dev/null +++ b/src/device/d-private.h @@ -0,0 +1,68 @@ +/* + * Sai device definitions src/device/d-private.h + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "../common/include/private.h" + +typedef struct sai_tty { + lws_dll2_t list; + + const char *name; + const char *tty_path; + struct lws *wsi; + lws_sorted_usec_list_t sul_serial_acq; + int rate; + int initial_monitor; + int index; + +} sai_tty_t; /* owned by sai_device_t's ttys_owner */ + +typedef struct sai_device { + lws_dll2_t list; + + lws_dll2_owner_t ttys_owner; /* sai_tty_t */ + + const char * name; + const char * type; + const char * compatible; + const char * description; +} sai_device_t; /* owned by sai_devices devices_owner */ + +typedef struct sai_devices { + + lws_dll2_owner_t devices_owner; + + struct lws_context *context; + struct lws_vhost *vhost; + + struct lwsac *conf_head; + + const char *home; + +} sai_devices_t; + + +extern struct lws_protocols protocol_serial; + +int +said_config_global(struct sai_devices *devices, const char *d); + +void +said_config_destroy(struct sai_devices *devices); diff --git a/src/device/d-sai.c b/src/device/d-sai.c new file mode 100644 index 0000000..80d1b38 --- /dev/null +++ b/src/device/d-sai.c @@ -0,0 +1,434 @@ +/* + * sai-device + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * + * sai-device simply tries to acquire exclusive file locks on ttys belonging + * to a given type of device, sets up some env vars representing what it locked + * eg, + * + * SAI_DEVICE_TTY0=/dev/serial/by-path/pci-0000:03:00.3-usb-0:2:1.0-port0 + * + * and then spawns a given argument. + * + * It doesn't do any network IO or tty IO, although it holds an fd on the ttys + * as part of locking them until sai-device terminates. + */ + +#if defined(WIN32) /* complaints about getenv */ +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <fcntl.h> + +#if defined(WIN32) +#include <initguid.h> +#include <KnownFolders.h> +#include <Shlobj.h> +#else +#include <sys/file.h> +#include <unistd.h> +#endif + +#if defined(__linux__) +#include <sys/prctl.h> +#endif + +#include "d-private.h" + +static const char *config_dir = "/etc/sai/devices"; +static const char * _argv[9], *alias; +struct lws_ss_handle *ssh[3]; +static struct lws_context *context; +static lws_sorted_usec_list_t sul; +static lws_state_notify_link_t nl; +static int interrupted, xarg = 2; +struct sai_devices devices; +static const char *devtype; +static pid_t pid; + +static const char * const default_ss_policy = + "{" + "\"s\": [" + /* + * Unix Domain Socket connections to sai-builder logging + */ + "{\"logproxy\": {" + "\"endpoint\":" "\"+${sockpath}\"," + "\"protocol\":" "\"raw\"," + "\"metadata\": [" + "{\"sockpath\": \"\"}" + "]" + "}}" + "]}" +; + + +void sigint_handler(int sig) +{ + interrupted = 1; +} + +static void +said_check_device(lws_sorted_usec_list_t *sul) +{ + int n; + + if (pid) { + int w; + + /* + * We already locked the device and ran the child process + */ + + if (waitpid(pid, &w, WNOHANG) == pid) { + + if (WIFEXITED(w)) + exit(WEXITSTATUS(w)); + if (WIFSIGNALED(w)) + exit(1); + } + + goto again; + } + + lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, + devices.devices_owner.head) { + sai_device_t *d = lws_container_of(p, sai_device_t, list); + struct lws_tokenize ts; + + lwsl_debug("%s: device '%s', %s, %s, %s\n", __func__, + d->name, d->type, d->compatible, d->description); + + /* + * is this device compatible with what we were asked for? + */ + lws_tokenize_init(&ts, d->compatible, + LWS_TOKENIZE_F_COMMA_SEP_LIST | + LWS_TOKENIZE_F_MINUS_NONTERM); + ts.len = (int)strlen(d->compatible); + + do { + ts.e = lws_tokenize(&ts); + switch (ts.e) { + case LWS_TOKZE_TOKEN: + if (!strncmp(ts.token, devtype, ts.token_len)) + ts.e = LWS_TOKZE_ENDED; + break; + + default: /* includes ENDED found by the tokenizer itself */ + ts.e = -99; + break; + } + } while (ts.e > 0); + + if (ts.e == LWS_TOKZE_ENDED) { + char ename[1024], *p = ename; + int try = 0, locked[8], fd; + + /* + * this matches what he was asking for + */ + lws_start_foreach_dll_safe(struct lws_dll2 *, q, q1, + d->ttys_owner.head) { + sai_tty_t *t = lws_container_of(q, + sai_tty_t, list); + + if (try >= (int)LWS_ARRAY_SIZE(locked)) + break; + try++; + + fd = lws_open(t->tty_path, O_RDWR); + if (fd == -1) { + try--; + goto nope; + } + + locked[try - 1] = fd; + + /* + * We need to insist on exclusive use of it, not + * just for the obvious reason we want to see + * all the RX and not have someone else's TX + * disrupting us, but also because we can expect + * other processes to want to use this to flash + * the same device. + * + * We need to lock it for the whole time between + * wanting to flash and sai-expect using it + * opportunistically for tty IO, so we just open + * it for locking and don't do anything with it + * ourselves. + */ + + if (flock(fd, LOCK_EX | LOCK_NB) == -1) { + close(fd); + try--; + + goto nope; + } + + } lws_end_foreach_dll_safe(q, q1); /* each tty */ + + /* + * if we made it here, we locked them all and we can go + * on and set the env and spawn the payload + */ + + try = 0; + lws_start_foreach_dll_safe(struct lws_dll2 *, q, q1, + d->ttys_owner.head) { + sai_tty_t *t = lws_container_of(q, + sai_tty_t, list); + char *p1 = p; + + p += lws_snprintf(p, sizeof(ename) - + lws_ptr_diff(p, ename), + "SAI_DEVICE_TTY%d", + try); + setenv(p1, t->tty_path, 1); + + if (alias) { + p1 = strchr(alias, '='); + if (p1 && atoi(&p1[1]) == try) { + *p1 = '\0'; + setenv(alias, t->tty_path, 1); + } + } + + p++; + try++; + + } lws_end_foreach_dll_safe(q, q1); /* each tty */ + + pid = fork(); + if (pid) { + char log[128]; + n = lws_snprintf(log, sizeof(log), + "sai-device: acquired device '%s' (%s)\n", + d->name, d->type); + saicom_lp_add(ssh[0], log, n); + /* + * We'll check in the background if the child + * exited, in the meanwhile we can send any + * logs + */ + goto again; + } + + /* + * We're the child process + */ + + if (execvp(_argv[0], (char * const *)_argv)) { + lwsl_err("%s: spawn %s failed: errno %d\n", + __func__, _argv[0], errno); + return; + } + + /* unreachable */ + +nope: + while (try--) { + flock(locked[try], LOCK_UN); + close(locked[try]); + } + + } /* device matches type */ + + } lws_end_foreach_dll_safe(p, p1); /* each device */ + +again: + lws_sul_schedule(context, 0, sul, said_check_device, + 100 * LWS_US_PER_MS); +} + +static int +app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, + int current, int target) +{ + struct lws_context *context = lws_system_context_from_system_mgr(mgr); + char log[128]; + int n; + + switch (target) { + + case LWS_SYSTATE_OPERATIONAL: + if (current != LWS_SYSTATE_OPERATIONAL) + break; + + n = lws_snprintf(log, sizeof(log), + "sai-device: queueing for device compatible with '%s'\n", + devtype); + saicom_lp_add(ssh[0], log, n); + + lws_sul_schedule(context, 0, &sul, said_check_device, 1); + break; + } + + return 0; +} + + +static lws_state_notify_link_t * const app_notifier_list[] = { + &nl, NULL +}; + +int main(int argc, const char **argv) +{ + int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct lws_context_creation_info info; +#if defined(WIN32) + char temp[256], stg_config_dir[256]; +#endif + const char *p; + int n; + + if (argc < 2) { + lwsl_notice("usage: %s <device type> [ENVNAME=<ttyidx>] <app to exec> [args...]\n", + argv[0]); + + return 1; + } + +#if defined(__linux__) + n = prctl(PR_SET_PDEATHSIG, SIGTERM); + if (n == -1) { + perror(0); + return 1; + } + + if (getppid() == 1) + /* parent process already dead */ + return 1; +#endif + + devtype = argv[1]; + + if (strchr(argv[2], '=')) { + alias = argv[2]; + xarg++; + } + + _argv[0] = argv[xarg]; + for (n = 1; n < argc && + n + xarg < (int)LWS_ARRAY_SIZE(_argv); n++) + _argv[n] = argv[xarg + n]; + _argv[n] = NULL; + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + +#if defined(WIN32) + { + PWSTR wdi = NULL; + + if (SHGetKnownFolderPath(&FOLDERID_ProgramData, + 0, NULL, &wdi) != S_OK) { + lwsl_err("%s: unable to get config dir\n", __func__); + return 1; + } + + if (WideCharToMultiByte(CP_ACP, 0, wdi, -1, temp, + sizeof(temp), 0, NULL) <= 0) { + lwsl_err("%s: problem with string encoding\n", __func__); + return 1; + } + + lws_snprintf(stg_config_dir, sizeof(stg_config_dir), + "%s\\sai\\devices\\", temp); + + config_dir = stg_config_dir; + CoTaskMemFree(wdi); + } +#endif + + /* + * Let's parse the global bits out of the config + */ + + lwsl_info("%s: config dir %s\n", __func__, config_dir); + if (said_config_global(&devices, config_dir)) { + lwsl_err("%s: global config failed\n", __func__); + + return 1; + } + + lwsl_info("Sai Devices - " + "Copyright (C) 2019-2020 Andy Green <andy@warmcat.com>\n"); + lwsl_info(" sai-devices devtype payload ...\n"); + + + memset(&info, 0, sizeof info); + info.port = CONTEXT_PORT_NO_LISTEN; + info.pt_serv_buf_size = 32 * 1024; + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | + LWS_SERVER_OPTION_VALIDATE_UTF8 | + LWS_SERVER_OPTION_EXPLICIT_VHOSTS; + + signal(SIGINT, sigint_handler); + + info.pss_policies_json = default_ss_policy; + info.fd_limit_per_thread = 1 + 128 + 1; + + /* hook up our lws_system state notifier */ + + nl.name = "sai-devices"; + nl.notify_cb = app_system_state_nf; + info.register_notifier_list = app_notifier_list; + + /* create the lws context */ + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* ... and our vhost... */ + + if (!lws_create_vhost(context, &info)) { + lwsl_err("Failed to create tls vhost\n"); + goto bail; + } + + ssh[0] = saicom_lp_ss_from_env(context, "SAI_LOGPROXY"); + if (!ssh[0]) + goto bail; + ssh[1] = saicom_lp_ss_from_env(context, "SAI_LOGPROXY_TTY0"); + if (!ssh[1]) + goto bail; + ssh[2] = saicom_lp_ss_from_env(context, "SAI_LOGPROXY_TTY1"); + if (!ssh[2]) + goto bail; + + while (!lws_service(context, 0) && !interrupted) + ; + +bail: + lws_context_destroy(context); + said_config_destroy(&devices); + + return 0; +} diff --git a/src/expect/CMakeLists.txt b/src/expect/CMakeLists.txt new file mode 100644 index 0000000..4c146f8 --- /dev/null +++ b/src/expect/CMakeLists.txt @@ -0,0 +1,51 @@ +set(CPACK_DEBIAN_BUILDER_PACKAGE_NAME "sai-expect") + +set(SRCS + e-sai.c + e-serial.c + ../common/ss-client-logproxy.c +) + +set(requirements 1) +require_lws_config(LWS_WITH_CLIENT 1 requirements) +require_lws_config(LWS_WITH_SPAWN 1 requirements) +require_lws_config(LWS_WITH_STRUCT_JSON 1 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) +require_lws_config(LWS_WITH_DIR 1 requirements) + +if (requirements AND NOT MSVC) + + CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint + main(void) {\n#if defined(LWS_HAVE_LIBCAP)\n return + 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_LIBCAP) + if (HAS_LIBCAP) + find_library(CAP_LIB_PATH NAMES "cap") + endif() + + add_executable("sai-expect" ${SRCS}) + if (APPLE) + set_property(TARGET sai-expect PROPERTY MACOSX_RPATH YES) + endif() + + target_link_libraries("sai-expect" ${SAI_LWS_LIB_PATH}) + + if (LWS_OPENSSL_LIBRARIES) + target_link_libraries("sai-expect" ${LWS_OPENSSL_LIBRARIES}) + endif() + + if (SAI_EXT_PTHREAD_LIBRARIES AND NOT MSVC) + target_link_libraries("sai-expect" ${SAI_EXT_PTHREAD_LIBRARIES}) + endif() + if (HAS_LIBCAP) + target_link_libraries("sai-expect" ${CAP_LIB_PATH}) + endif() + + if (MSVC OR WIN32) + target_link_libraries("sai-expect" ws2_32.lib userenv.lib psapi.lib iphlpapi.lib) + endif() + + install(TARGETS "sai-expect" + RUNTIME DESTINATION "${BIN_DIR}" COMPONENT expect) + +endif(requirements AND NOT MSVC) +include(CPack) diff --git a/src/expect/e-private.h b/src/expect/e-private.h new file mode 100644 index 0000000..1d1c92d --- /dev/null +++ b/src/expect/e-private.h @@ -0,0 +1,28 @@ +/* + * Sai expect definitions src/expect/e-private.h + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "../common/include/private.h" + +extern struct lws_protocols protocol_serial; +extern int interrupted, bad; + +struct lws * +sai_serial_try_open(struct lws_vhost *vh, const char *devpath, int rate, int i); diff --git a/src/expect/e-sai.c b/src/expect/e-sai.c new file mode 100644 index 0000000..56585a9 --- /dev/null +++ b/src/expect/e-sai.c @@ -0,0 +1,179 @@ +/* + * sai-expect + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * sai-builder : exports logproxy unix domain paths in SAI_LOGPROXY_TTY0/1 + * sai-device : exports raw tty node path in SAI_DEVICE_TTY0/1 + * sai-expect : proxies SAI_DEVICE_TTYx to SAI_LOGPROXY_TTYx, performs TX, + * waits for and assesses RX + * + * Sai-expect opportunistically opens ttys exported by sai-device in the + * environment, proxies logs to sai-builder (which forwards them to the sai- + * master that is storing them with the task), and parses the received tty + * data and emits strings as requested by its arguments. + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> + +#include "e-private.h" + +struct lws_ss_handle *ssh[3]; +static lws_state_notify_link_t nl; +static const char *serpath[2]; +static struct lws_vhost *vh; +int interrupted, bad = 1; + +static const char * const default_ss_policy = + "{" + "\"s\": [" + /* + * Unix Domain Socket connections to sai-builder logging + */ + "{\"logproxy\": {" + "\"endpoint\":" "\"+${sockpath}\"," + "\"protocol\":" "\"raw\"," + "\"metadata\": [" + "{\"sockpath\": \"\"}" + "]" + "}}" + "]}" +; + +static const struct lws_protocols *pprotocols[] = { + &protocol_serial, + NULL +}; + +static lws_state_notify_link_t * const app_notifier_list[] = { + &nl, NULL +}; + +static int +app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, + int current, int target) +{ + struct lws_context *context = lws_system_context_from_system_mgr(mgr); + char log[128]; + int n; + + switch (target) { + + case LWS_SYSTATE_OPERATIONAL: + if (current != LWS_SYSTATE_OPERATIONAL) + break; + + ssh[0] = saicom_lp_ss_from_env(context, "SAI_LOGPROXY"); + if (!ssh[0]) + goto bail; + ssh[1] = saicom_lp_ss_from_env(context, "SAI_LOGPROXY_TTY0"); + if (!ssh[1]) + goto bail; + ssh[2] = saicom_lp_ss_from_env(context, "SAI_LOGPROXY_TTY1"); + if (!ssh[2]) + goto bail; + + if (!sai_serial_try_open(vh, serpath[0], 115200, 0)) + goto bail; + if (serpath[1]) + if (!sai_serial_try_open(vh, serpath[1], 115200, 1)) + goto bail; + + n = lws_snprintf(log, sizeof(log), "sai-expect: starting\n"); + saicom_lp_add(ssh[0], log, n); + break; + } + + return 0; + +bail: + return 1; +} + +void sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct lws_context_creation_info info; + struct lws_context *context; + const char *p; + + serpath[0] = getenv("SAI_DEVICE_TTY0"); + serpath[1] = getenv("SAI_DEVICE_TTY1"); + + if (!serpath[0]) { + lwsl_err("%s: need at least SAI_DEVICE_TTY0\n", __func__); + return 1; + } + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + + lwsl_user("Sai Expect - " + "Copyright (C) 2019-2020 Andy Green <andy@warmcat.com>\n"); + + memset(&info, 0, sizeof info); + info.port = CONTEXT_PORT_NO_LISTEN; + info.pprotocols = pprotocols; + + info.pss_policies_json = default_ss_policy; + info.pt_serv_buf_size = 8 * 1024; + info.options = LWS_SERVER_OPTION_VALIDATE_UTF8 | + LWS_SERVER_OPTION_EXPLICIT_VHOSTS; + + signal(SIGINT, sigint_handler); + info.fd_limit_per_thread = 1 + 8 + 1; + + /* hook up our lws_system state notifier */ + + nl.name = "sai-devices"; + nl.notify_cb = app_system_state_nf; + info.register_notifier_list = app_notifier_list; + + /* create the lws context */ + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* ... and our vhost... */ + + vh = lws_create_vhost(context, &info); + if (!vh) { + lwsl_err("%s: Failed to create vhost\n", __func__); + goto bail1; + } + + while (!lws_service(context, 0) && !interrupted) + ; + +bail1: + lws_context_destroy(context); + + return bad; +} diff --git a/src/expect/e-serial.c b/src/expect/e-serial.c new file mode 100644 index 0000000..2de2443 --- /dev/null +++ b/src/expect/e-serial.c @@ -0,0 +1,246 @@ +/* + * sai-device tty monitoring + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Logging / parsing of device ttys + */ + +#include <libwebsockets.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <termios.h> +#include <string.h> +#include <signal.h> +#include <fcntl.h> + +#include "e-private.h" + +static const char *pass = "Completed: PASS"; + +struct pss { + struct lws_context *context; + lws_sorted_usec_list_t sul; + char collation[128]; + lws_usec_t earliest; + int pos; + int i; + + int pass_match; + char draining; + char match; +}; + +struct lws * +sai_serial_try_open(struct lws_vhost *vh, const char *devpath, int _rate, int i) +{ + lws_sock_file_fd_type u; + struct termios tio; + struct lws *wsi; + int fd, rate; + + switch (_rate) { + case 115200: + rate = B115200; + break; + default: + lwsl_err("%s: unknown rate %d\n", __func__, _rate); + + return NULL; + } + + /* enforce suitable tty state */ + + memset(&tio, 0, sizeof tio); + tio.c_cflag = CS8 | CREAD | CLOCAL; +// tio.c_cc[VMIN] = 1; +// tio.c_cc[VTIME] = 5; + + fd = lws_open(devpath, O_RDWR); + if (fd == -1) { + /* + * Even when it's locked, we ought to be able to open it + */ + lwsl_err("%s: Unable to open %s\n", __func__, devpath); + + return NULL; + } + + cfsetospeed(&tio, rate); + cfsetispeed(&tio, rate); + + tcsetattr(fd, TCSANOW, &tio); + tcflush(fd, TCIOFLUSH); + + u.filefd = (lws_filefd_type)(long long)fd; + wsi = lws_adopt_descriptor_vhost(vh, LWS_ADOPT_RAW_FILE_DESC, u, + "sai-serial", NULL); + if (!wsi) { + lwsl_err("%s: Failed to adopt fifo descriptor\n", __func__); + close(fd); + + return NULL; + } + + lws_set_opaque_user_data(wsi, (void *)(intptr_t)i); + + return wsi; +} + +static void +finished_cb(void *opaque) +{ + interrupted = 1; + bad = 0; +} + +static void +spill(struct pss *pss) +{ + saicom_lp_add(ssh[pss->i + 1], pss->collation, pss->pos); + pss->pos = 0; + pss->earliest = 0; + lws_sul_cancel(&pss->sul); + + /* + * We have seen the pass string + */ + if (pss->match) { + saicom_lp_add(ssh[0], "sai-expect: completed\n", 22); + pss->match = 0; + pss->draining = 1; + saicom_lp_callback_on_drain(finished_cb, NULL); + } +} + +static void +sul_cb(lws_sorted_usec_list_t *sul) +{ + struct pss *pss = lws_container_of(sul, struct pss, sul); + + spill(pss); +} + +static int +callback_serial(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct pss *pss = (struct pss *)user; + lws_usec_t now = lws_now_usecs(); + uint8_t buf[128]; + int n, m; + + switch (reason) { + + case LWS_CALLBACK_RAW_ADOPT_FILE: + pss->i = (int)(intptr_t)lws_get_opaque_user_data(wsi); + pss->context = lws_get_context(wsi); + break; + + case LWS_CALLBACK_RAW_RX_FILE: + /* + * Typically we can read this much faster than it gets new data, + * 115200 is only 12KB/sec. If we don't do something about + * collation locally, we will end up generating and sending a + * huge number of discrete log fragments just a few chars long + * each. + * + * Let's collate them up to 128 bytes, with a 150ms limit to + * holding pieces + */ + n = read((int)(intptr_t)lws_get_socket_fd(wsi), buf, sizeof(buf)); + if (n < 1) { + lwsl_debug("%s: read on stdwsi failed\n", __func__); + return -1; + } + + if (pss->draining) + break; + + for (m = 0; m < n; m++) { + pss->collation[pss->pos++] = buf[m]; + + if (buf[m] == pass[pss->pass_match]) { + pss->pass_match++; + if (pss->pass_match == (int)strlen(pass)) { + /* + * We have seen the EOT match string, + * start that process when we spill + * this line so we can log it + */ + pss->match = 1; + pss->pass_match = 0; + } + } else + pss->pass_match = 0; + + /* + * Let's spill on line boundaries too, if the line + * was long enough to take 2ms (24 x 115Kbps octets) to + * collect + */ + if ((buf[m] == '\n' && pss->pos > 1 && pss->earliest && + (now - pss->earliest) > 2 * LWS_US_PER_MS) || + pss->pos == sizeof(pss->collation)) + /* + * spill due to fill collation buffer + */ + spill(pss); + } + + if (pss->pos && !pss->earliest) + pss->earliest = now; + + if (now - pss->earliest > 150 * LWS_US_PER_MS) + /* + * Spill because the earliest is stale + */ + spill(pss); + + else + /* + * if we are holding on to something, and nothing else + * happens, spill 150ms after earliest + */ + if (pss->pos) + lws_sul_schedule(pss->context, 0, &pss->sul, + sul_cb, (150 * LWS_US_PER_MS) - + (now - pss->earliest)); + break; + + case LWS_CALLBACK_RAW_CLOSE_FILE: + break; + + case LWS_CALLBACK_RAW_WRITEABLE_FILE: +// lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE_FILE\n"); +// if (lws_write(wsi, (uint8_t *)"hello-this-is-written-every-couple-of-seconds\r\n", 47, LWS_WRITE_RAW) != 47) +// return -1; + break; + + default: + break; + } + + return 0; +} + +struct lws_protocols protocol_serial = { + "sai-serial", callback_serial, sizeof(struct pss), 0, +}; diff --git a/src/master/CMakeLists.txt b/src/master/CMakeLists.txt new file mode 100644 index 0000000..5c41dc7 --- /dev/null +++ b/src/master/CMakeLists.txt @@ -0,0 +1,141 @@ +set(SUB "sai-master") + +set(CPACK_DEBIAN_MASTER_PACKAGE_NAME ${SUB}) + +set(SRCS + m-sai.c + m-conf.c + m-notification.c + m-comms.c + m-ws-builder.c + m-task.c + m-central.c + m-artifact.c + m-overview.c + m-ws-browser.c +) + +set(requirements 1) +require_lws_config(LWS_WITH_STRUCT_SQLITE3 1 requirements) +require_lws_config(LWS_WITH_SERVER 1 requirements) +require_lws_config(LWS_WITH_GENCRYPTO 1 requirements) +require_lws_config(LWS_WITH_UNIX_SOCK 1 requirements) + +if (requirements) + add_executable(${SUB} ${SRCS}) + if (APPLE) + set_property(TARGET sai-master PROPERTY MACOSX_RPATH YES) + endif() + + # + # sqlite3 paths (master) + # + if (SAI_MASTER) + find_path( SQLITE3_INC_PATH NAMES "sqlite3.h") + find_library(SQLITE3_LIB_PATH NAMES "sqlite3") + + if (SQLITE3_INC_PATH AND SQLITE3_LIB_PATH) + include_directories(BEFORE "${SQLITE3_INC_PATH}") + else() + message(FATAL_ERROR " Unable to find sqlite3") + endif() + + target_link_libraries(${SUB} ${SQLITE3_LIB_PATH}) + endif() + + include_directories(BEFORE "${SAI_LWS_INC_PATH}") + + CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint + main(void) {\n#if defined(LWS_HAVE_LIBCAP)\n return + 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_LIBCAP) + if (HAS_LIBCAP) + find_library(CAP_LIB_PATH NAMES "cap") + endif() + + target_link_libraries(${SUB} ${SAI_LWS_LIB_PATH}) + + if (LWS_OPENSSL_LIBRARIES) + target_link_libraries(${SUB} ${LWS_OPENSSL_LIBRARIES}) + endif() + + if (SAI_EXT_PTHREAD_LIBRARIES) + target_link_libraries(${SUB} ${SAI_EXT_PTHREAD_LIBRARIES}) + endif() + + if (HAS_LIBCAP) + target_link_libraries(${SUB} ${CAP_LIB_PATH}) + endif() + + if (MSVC) + target_link_libraries(${SUB} ws2_32.lib userenv.lib psapi.lib iphlpapi.lib) + endif() + + install(TARGETS ${SUB} + RUNTIME DESTINATION "${BIN_DIR}" COMPONENT master) + install( + FILES ../../assets/index.html + ../../assets/OpenSans-Light.ttf + ../../assets/rebuild.png + ../../assets/delete.png + ../../assets/passed.svg + ../../assets/failed.svg + ../../assets/sai.css + ../../assets/sai.js + ../../assets/sai.svg + ../../assets/sai-icon.svg + ../../assets/builder.png + ../../assets/favicon.ico + ../../assets/strict-csp.svg + ../../assets/lws-common.js + ../../assets/gs/lwsgs.js + ../../assets/gs/lwsgs.css + ../../assets/arch-aarch64-a72-bcm2711-rpi4.svg + ../../assets/arch-aarch64.svg + ../../assets/arch-arm32-m4-mt7697-usi.svg + ../../assets/arch-arm32.svg + ../../assets/arch-freertos.svg + ../../assets/arch-riscv64-virt.svg + ../../assets/arch-riscv.svg + ../../assets/arch-x86_64-amd.svg + ../../assets/arch-x86_64-intel-i3.svg + ../../assets/arch-x86_64-intel.svg + ../../assets/arch-xl6-esp32.svg + ../../assets/artifact.svg + ../../assets/builder.svg + ../../assets/decal-2.svg + ../../assets/decal-3.svg + ../../assets/decal-4.svg + ../../assets/decal-5.svg + ../../assets/decal-6.svg + ../../assets/freertos-espidf.svg + ../../assets/freertos-linkit.svg + ../../assets/git.svg + ../../assets/jsplease.svg + ../../assets/linux-android.svg + ../../assets/linux-centos-8.svg + ../../assets/linux-fedora-32-riscv.svg + ../../assets/linux-fedora-32.svg + ../../assets/linux-gentoo.svg + ../../assets/linux-ubuntu-1804.svg + ../../assets/linux-ubuntu-2004.svg + ../../assets/linux-ubuntu-xenial-amd64.svg + ../../assets/netbsd-iOS.svg + ../../assets/netbsd-OSX-catalina.svg + ../../assets/sai-event.svg + ../../assets/sai-icon.svg + ../../assets/sai.svg + ../../assets/stop.svg + ../../assets/strict-csp.svg + ../../assets/tc-gcc.svg + ../../assets/tc-llvm.svg + ../../assets/tc-mingw32.svg + ../../assets/tc-mingw64.svg + ../../assets/tc-msvc.svg + ../../assets/ubuntu-focal-aarch64.svg + ../../assets/virt-qemu.svg + ../../assets/windows-10.svg + + DESTINATION "${DATA_DIR}/sai/assets" + COMPONENT master) + +endif(requirements) diff --git a/src/master/conf.c b/src/master/conf.c deleted file mode 100644 index 6aacd4d..0000000 --- a/src/master/conf.c +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Sai master src/master/conf.c - * - * Copyright (C) 2019 Andy Green <andy@warmcat.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation: - * version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ -#include <libwebsockets.h> -#include <string.h> -#include <signal.h> -#include <time.h> - -#define SAI_CONFIG_STRING_SIZE (16 * 1024) - -struct lws_context * -sai_lws_context_from_json(const char *config_dir, - const struct lws_protocols **pprotocols) -{ - int cs_len = SAI_CONFIG_STRING_SIZE - 1; - struct lws_context_creation_info info; - struct lws_context *context; - char *cs, *config_strings; - - cs = config_strings = malloc(SAI_CONFIG_STRING_SIZE); - if (!config_strings) { - lwsl_err("Unable to allocate config strings heap\n"); - - return NULL; - } - - memset(&info, 0, sizeof(info)); - - info.external_baggage_free_on_destroy = config_strings; - info.pt_serv_buf_size = 8192; - info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | - LWS_SERVER_OPTION_EXPLICIT_VHOSTS | - LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE | - LWS_SERVER_OPTION_VALIDATE_UTF8; - - lwsl_notice("Using config dir: \"%s\"\n", config_dir); - - /* - * first go through the config for creating the outer context - */ - if (lwsws_get_config_globals(&info, config_dir, &cs, &cs_len)) - goto init_failed; - - context = lws_create_context(&info); - if (context == NULL) { - lwsl_err("lws init failed\n"); - /* config_strings freed as 'external baggage' */ - return NULL; - } - - info.pprotocols = pprotocols; - - if (lwsws_get_config_vhosts(context, &info, config_dir, &cs, &cs_len)) { - lws_context_destroy(context); - - return NULL; - } - - return context; - -init_failed: - free(config_strings); - - return NULL; -} diff --git a/src/master/m-artifact.c b/src/master/m-artifact.c new file mode 100644 index 0000000..527abc2 --- /dev/null +++ b/src/master/m-artifact.c @@ -0,0 +1,144 @@ +/* + * Sai master + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <time.h> + +#include "m-private.h" + +int +saim_get_blob(struct vhd *vhd, const char *url, sqlite3 **pdb, + sqlite3_blob **blob, uint64_t *length) +{ + char task_uuid[66], event_uuid[34], nonce[34], qu[200], + esc[66], esc1[34]; + struct lwsac *ac = NULL; + lws_dll2_owner_t o; + sai_artifact_t *a; + const char *p; + uint64_t rid = 0; + int n; + + /* + * We get passed the RHS of a URL string like + * + * <task_uuid>/<down_nonce>/filename + * + * filename is not used for matching, but make sure the client saves it + * using the name generated along with the link. + * + * Extract the pieces from the URL + */ + + n = 0; + p = url; + while (*p && *p != '/' && n < (int)sizeof(task_uuid) - 1) + task_uuid[n++] = *p++; + + if (n != sizeof(task_uuid) - 2 || *p != '/') { + lwsl_notice("%s: url layout 1: %d %s\n", __func__, n, url); + return -1; + } + + task_uuid[n] = '\0'; + p++; /* skip the / */ + + n = 0; + while (*p && *p != '/' && n < (int)sizeof(nonce) - 1) + nonce[n++] = *p++; + + if (n != sizeof(nonce) - 2 || *p != '/') { + lwsl_notice("%s: url layout 2\n", __func__); + return -1; + } + + sai_task_uuid_to_event_uuid(event_uuid, task_uuid); + + /* open the event-specific database object */ + + if (saim_event_db_ensure_open(vhd, event_uuid, 0, pdb)) { + lwsl_info("%s: unable to open event-specific database\n", + __func__); + + return -1; + } + + /* + * Query the artifact he's asking for + */ + + lws_sql_purify(esc, task_uuid, sizeof(esc)); + lws_sql_purify(esc1, nonce, sizeof(esc1)); + lws_snprintf(qu, sizeof(qu), + " and task_uuid='%s' and artifact_down_nonce='%s'", + esc, esc1); + n = lws_struct_sq3_deserialize(*pdb, qu, NULL, + lsm_schema_sq3_map_artifact, + &o, &ac, 0, 1); + if (n < 0 || !o.head) { + lwsl_err("%s: no result from %s\n", __func__, qu); + goto fail; + } + + a = (sai_artifact_t *)o.head; + + *length = a->len; + + /* + * recover the rowid the blob api requires + */ + + lws_snprintf(qu, sizeof(qu), "select rowid from artifacts " + "where artifact_down_nonce='%s'", esc1); + + if (sqlite3_exec(*pdb, qu, sai_sql3_get_uint64_cb, &rid, NULL) != SQLITE_OK) { + lwsl_err("%s: %s: %s: fail\n", __func__, qu, sqlite3_errmsg(*pdb)); + goto fail; + } + + /* + * Get a read-only handle on the blob + */ + + if (sqlite3_blob_open(*pdb, "main", "artifacts", "blob", rid, 0, blob) != SQLITE_OK) { + lwsl_err("%s: unable to open blob, rid %d\n", __func__, (int)rid); + goto fail; + } + + lwsac_free(&ac); /* drop the lwsac holding that result */ + + /* + * It was successful, *length was set, *pdb and *blob are live handles + * to the db and the blob itself. + */ + + return 0; + +fail: + lwsac_free(&ac); + saim_event_db_close(vhd, pdb); + + lwsl_notice("%s: couldn't find blob %s\n", __func__, url); + + return -1; +} diff --git a/src/master/m-central.c b/src/master/m-central.c new file mode 100644 index 0000000..c967625 --- /dev/null +++ b/src/master/m-central.c @@ -0,0 +1,159 @@ +/* + * Sai master + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * + * Central dispatcher for jobs from events that made it into the database. This + * is done in an event-driven way in m-task.c, but management of it also has to + * be done in the background for when there are no events coming, + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <time.h> + +#include "m-private.h" + +extern struct lws_context *context; + +static void +saim_central_clean_abandoned(struct vhd *vhd) +{ + saim_sqlite_cache_t *sc; + struct lwsac *ac = NULL; + lws_usec_t now = lws_now_usecs(); + lws_dll2_owner_t o; + char s[160]; + int n, nzr; + + /* + * Sqlite cache cleaning + */ + + nzr = 0; + lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, + vhd->sqlite3_cache.head) { + sc = lws_container_of(p, saim_sqlite_cache_t, list); + + if (!sc->refcount && + (now - sc->idle_since) > (60 * LWS_USEC_PER_SEC)) { + lwsl_notice("%s: delayed db pool clean %s\n", __func__, + sc->uuid); + lws_struct_sq3_close(&sc->pdb); + lws_dll2_remove(&sc->list); + free(sc); + } else + if (sc->refcount) + nzr++; + + } lws_end_foreach_dll_safe(p, p1); + + if (vhd->sqlite3_cache.count) + lwsl_notice("%s: db pool items: in-use: %d, total: %d\n", + __func__, nzr, vhd->sqlite3_cache.count); + + /* + * Collect the most recent <=10 events that still feel they're + * incomplete and may be running something + */ + + n = lws_struct_sq3_deserialize(vhd->master.pdb, + " and (state != 3 and state != 4 and state != 5)", + NULL, lsm_schema_sq3_map_event, &o, + &ac, 0, 10); + if (n < 0 || !o.head) + return; + + /* + * For each of those events, look for tasks that have been running + * "too long", eg, builder restarted or lost connection etc + */ + + lws_start_foreach_dll(struct lws_dll2 *, p, o.head) { + sai_event_t *e = lws_container_of(p, sai_event_t, list); + sqlite3 *pdb = NULL; + + if (!saim_event_db_ensure_open(vhd, e->uuid, 0, &pdb)) { + /* + * Such tasks should go into CANCELLED + */ + + lws_snprintf(s, sizeof(s), + "update tasks set state=%d where " + "(state=1 OR state=2) and started < %llu", + SAIES_CANCELLED, (unsigned long long) + (lws_now_secs() - (30 * 60))); + + if (sqlite3_exec(pdb, s, NULL, NULL, NULL) != + SQLITE_OK) + lwsl_err("%s: %s: %s: fail\n", __func__, s, + sqlite3_errmsg(pdb)); + + saim_event_db_close(vhd, &pdb); + } + + } lws_end_foreach_dll(p); + + lwsac_free(&ac); +} + +void +saim_central_cb(lws_sorted_usec_list_t *sul) +{ + struct vhd *vhd = lws_container_of(sul, struct vhd, sul_central); + + /* + * For each builder connected to us, see if it can handle a new task, + * and if so, try to select one matching its supported platforms + */ + + lws_start_foreach_dll(struct lws_dll2 *, p, + vhd->master.builder_owner.head) { + sai_plat_t *cb = lws_container_of(p, sai_plat_t, sai_plat_list); + + lwsl_debug("%s: checking tasks %s %d %d %p\n", __func__, + cb->name, cb->ongoing, cb->instances, cb->wsi); + + if (cb->wsi && lws_wsi_user(cb->wsi) && + cb->ongoing < cb->instances) + saim_allocate_task(vhd, + (struct pss *)lws_wsi_user(cb->wsi), + cb, cb->platform); + + } lws_end_foreach_dll(p); + + /* + * Need to globally check for abandoned tasks periodically + */ + + if (!vhd->last_check_abandoned_tasks || + lws_now_usecs() > (vhd->last_check_abandoned_tasks + + (20 * LWS_USEC_PER_SEC))) { + + saim_central_clean_abandoned(vhd); + + vhd->last_check_abandoned_tasks = lws_now_usecs(); + } + + /* check again in 1s */ + + lws_sul_schedule(context, 0, &vhd->sul_central, saim_central_cb, + 1 * LWS_US_PER_SEC); +} diff --git a/src/master/m-comms.c b/src/master/m-comms.c new file mode 100644 index 0000000..3a8eef8 --- /dev/null +++ b/src/master/m-comms.c @@ -0,0 +1,917 @@ +/* + * Sai master + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * The same ws interface is connected-to by builders (on path /builder), and + * provides the query transport for browsers (on path /browse). + * + * There's a single master slite3 database containing events, and a separate + * sqlite3 database file for each event, it only contains tasks and logs for + * the event and can be deleted when the event record associated with it is + * deleted. This is to keep is scalable when there may be thousands of events + * and related tasks and logs stored. + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <time.h> + +#include "m-private.h" + +#include "../common/struct-metadata.c" + +typedef enum { + SJS_CLONING, + SJS_ASSIGNING, + SJS_WAITING, + SJS_DONE +} sai_job_state_t; + +typedef struct sai_job { + struct lws_dll2 jobs_list; + char reponame[64]; + char ref[64]; + char head[64]; + + time_t requested; + + sai_job_state_t state; + +} sai_job_t; + +const lws_struct_map_t lsm_schema_map_ta[] = { + LSM_SCHEMA (sai_task_t, NULL, lsm_task, "com-warmcat-sai-ta"), +}; + +extern const lws_struct_map_t lsm_schema_sq3_map_event[]; + + +static int +sai_destroy_builder(struct lws_dll2 *d, void *user) +{ +// saib_t *b = lws_container_of(d, saib_t, c.builder_list); + + lws_dll2_remove(d); + + return 0; +} + +static void +saim_master_destroy(saim_t *master) +{ + lws_dll2_foreach_safe(&master->builder_owner, NULL, sai_destroy_builder); + + lws_struct_sq3_close(&master->pdb); +} + +static char *hexch = "0123456789abcdef"; + +/* len is typically 16 (event uuid is 32 chars + NUL) + * But eg, task uuid is concatenated 32-char eventid and 32-char taskid + */ + +int +sai_uuid16_create(struct lws_context *context, char *dest33) +{ + uint8_t *r = ((uint8_t *)dest33) + 33 - 16; + size_t n = 16; + + if (lws_get_random(context, r, n) != n) + return 1; + + while (n--) { + *dest33++ = hexch[(*r) >> 4]; + *dest33++ = hexch[(*r++) & 0xf]; + } + + *dest33 = '\0'; + + return 0; +} + +int +sai_sqlite3_statement(sqlite3 *pdb, const char *cmd, const char *desc) +{ + sqlite3_stmt *sm; + int n; + + if (sqlite3_prepare_v2(pdb, cmd, -1, &sm, NULL) != SQLITE_OK) { + lwsl_err("%s: Unable to %s: %s\n", + __func__, desc, sqlite3_errmsg(pdb)); + + return 1; + } + + n = sqlite3_step(sm); + sqlite3_reset(sm); + sqlite3_finalize(sm); + if (n != SQLITE_DONE) { + lwsl_err("%s: %d: Unable to perform \"%s\": %s\n", __func__, + sqlite3_extended_errcode(pdb), desc, + sqlite3_errmsg(pdb)); + puts(cmd); + + return 1; + } + + return 0; +} + +int +saim_event_db_ensure_open(struct vhd *vhd, const char *event_uuid, + char create_if_needed, sqlite3 **ppdb) +{ + char filepath[256], saf[33]; + saim_sqlite_cache_t *sc; + + if (*ppdb) + return 0; + + /* do we have this guy cached? */ + + lws_start_foreach_dll(struct lws_dll2 *, p, vhd->sqlite3_cache.head) { + sc = lws_container_of(p, saim_sqlite_cache_t, list); + + if (!strcmp(event_uuid, sc->uuid)) { + sc->refcount++; + *ppdb = sc->pdb; + return 0; + } + + } lws_end_foreach_dll(p); + + /* ... nope, well, let's open and cache him then... */ + + lws_strncpy(saf, event_uuid, sizeof(saf)); + lws_filename_purify_inplace(saf); + + lws_snprintf(filepath, sizeof(filepath), "%s-event-%s.sqlite3", + vhd->sqlite3_path_lhs, saf); + + if (lws_struct_sq3_open(vhd->context, filepath, create_if_needed, ppdb)) { + lwsl_err("%s: Unable to open db %s: %s\n", __func__, + filepath, sqlite3_errmsg(*ppdb)); + + return 1; + } + + /* create / add to the schema for the tables we will have in here */ + + if (lws_struct_sq3_create_table(*ppdb, lsm_schema_sq3_map_task)) + return 1; + + sai_sqlite3_statement(*ppdb, "PRAGMA journal_mode=WAL;", "set WAL"); + + if (lws_struct_sq3_create_table(*ppdb, lsm_schema_sq3_map_log)) + return 1; + + if (lws_struct_sq3_create_table(*ppdb, lsm_schema_sq3_map_artifact)) + return 1; + + sc = malloc(sizeof(*sc)); + memset(sc, 0, sizeof(*sc)); + if (!sc) { + lws_struct_sq3_close(ppdb); + *ppdb = NULL; + return 1; + } + + lws_strncpy(sc->uuid, event_uuid, sizeof(sc->uuid)); + sc->refcount = 1; + sc->pdb = *ppdb; + lws_dll2_add_tail(&sc->list, &vhd->sqlite3_cache); + + return 0; +} + +void +saim_event_db_close(struct vhd *vhd, sqlite3 **ppdb) +{ + saim_sqlite_cache_t *sc; + + if (!*ppdb) + return; + + /* look for him in the cache */ + + lws_start_foreach_dll(struct lws_dll2 *, p, vhd->sqlite3_cache.head) { + sc = lws_container_of(p, saim_sqlite_cache_t, list); + + if (sc->pdb == *ppdb) { + *ppdb = NULL; + if (--sc->refcount) { + lwsl_notice("%s: zero refcount to idle\n", __func__); + /* + * He's not currently in use then... don't + * close him immediately, m-central.c has a + * timer that closes and removes sqlite3 + * cache entries idle for longer than 60s + */ + sc->idle_since = lws_now_usecs(); + } + + return; + } + + } lws_end_foreach_dll(p); + + lws_struct_sq3_close(ppdb); + *ppdb = NULL; +} + +int +saim_event_db_delete_database(struct vhd *vhd, const char *event_uuid) +{ + char filepath[256], saf[33]; + + lws_strncpy(saf, event_uuid, sizeof(saf)); + lws_filename_purify_inplace(saf); + + lws_snprintf(filepath, sizeof(filepath), "%s-event-%s.sqlite3", + vhd->sqlite3_path_lhs, saf); + + return unlink(filepath); +} + + +#if 0 +static void +saim_all_browser_on_writable(struct vhd *vhd) +{ + lws_start_foreach_dll(struct lws_dll2 *, mp, vhd->browsers.head) { + struct pss *pss = lws_container_of(mp, struct pss, same); + + lws_callback_on_writable(pss->wsi); + } lws_end_foreach_dll(mp); +} +#endif + +typedef enum { + SHMUT_NONE = -1, + SHMUT_HOOK, + SHMUT_BROWSE, + SHMUT_STATUS, + SHMUT_ARTIFACTS, +} sai_http_murl_t; + +static const char * const well_known[] = { + "/update-hook", + "/sai/browse", + "/status", + "/artifacts/" /* HTTP api for accessing build artifacts */ +}; + +static const char *hmac_names[] = { + "sai sha256=", + "sai sha384=", + "sai sha512=" +}; + +void +mark_pending(struct pss *pss, ws_state state) +{ + pss->pending |= 1 << state; + lws_callback_on_writable(pss->wsi); +} + +static int +browser_upd(struct lws_dll2 *d, void *user) +{ + struct pss *pss = lws_container_of(d, struct pss, same); + ws_state state = (ws_state)(intptr_t)user; + + mark_pending(pss, state); + + return 0; +} + +int +sai_get_head_status(struct vhd *vhd, const char *projname) +{ + struct lwsac *ac = NULL; + lws_dll2_owner_t o; + sai_event_t *e; + int state; + + if (lws_struct_sq3_deserialize(vhd->master.pdb, NULL, "created ", + lsm_schema_sq3_map_event, &o, &ac, 0, -1)) + return -1; + + if (!o.head) + return -1; + + e = lws_container_of(o.head, sai_event_t, list); + state = e->state; + + lwsac_free(&ac); + + return state; +} + + +static int +callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, + void *in, size_t len) +{ + struct vhd *vhd = (struct vhd *)lws_protocol_vh_priv_get( + lws_get_vhost(wsi), lws_get_protocol(wsi)); + uint8_t buf[LWS_PRE + 6144], *start = &buf[LWS_PRE], *p = start, + *end = &buf[sizeof(buf) - LWS_PRE - 1]; + struct pss *pss = (struct pss *)user; + sai_http_murl_t mu = SHMUT_NONE; + char projname[64]; + int n, resp, r; + const char *cp; + + (void)end; + (void)p; + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), + lws_get_protocol(wsi), + sizeof(struct vhd)); + if (!vhd) + return -1; + + vhd->context = lws_get_context(wsi); + vhd->vhost = lws_get_vhost(wsi); + + if (lws_pvo_get_str(in, "notification-key", + &vhd->notification_key)) { + lwsl_err("%s: notification_key pvo required\n", __func__); + return -1; + } + + if (lws_pvo_get_str(in, "database", &vhd->sqlite3_path_lhs)) { + lwsl_err("%s: database pvo required\n", __func__); + return -1; + } + + lws_snprintf((char *)buf, sizeof(buf), "%s-events.sqlite3", + vhd->sqlite3_path_lhs); + + if (lws_struct_sq3_open(vhd->context, (char *)buf, 1, + &vhd->master.pdb)) { + lwsl_err("%s: Unable to open session db %s: %s\n", + __func__, vhd->sqlite3_path_lhs, sqlite3_errmsg( + vhd->master.pdb)); + + return -1; + } + + sai_sqlite3_statement(vhd->master.pdb, + "PRAGMA journal_mode=WAL;", "set WAL"); + + if (lws_struct_sq3_create_table(vhd->master.pdb, + lsm_schema_sq3_map_event)) { + lwsl_err("%s: unable to create event table\n", __func__); + return -1; + } + + lws_sul_schedule(vhd->context, 0, &vhd->sul_central, + saim_central_cb, 500 * LWS_US_PER_MS); + + break; + + case LWS_CALLBACK_PROTOCOL_DESTROY: + saim_master_destroy(&vhd->master); + goto passthru; + + /* + * receive http hook notifications + */ + + case LWS_CALLBACK_HTTP: + + resp = HTTP_STATUS_FORBIDDEN; + pss->vhd = vhd; + + for (n = 0; n < (int)LWS_ARRAY_SIZE(well_known); n++) + if (!strncmp((const char *)in, well_known[n], + strlen(well_known[n]))) { + mu = n; + break; + } + + pss->our_form = 0; + + lwsl_notice("%s: HTTP: xmu = %d\n", __func__, n); + + switch (mu) { + + case SHMUT_NONE: + goto passthru; + + case SHMUT_HOOK: + pss->our_form = 1; + lwsl_notice("LWS_CALLBACK_HTTP: sees hook\n"); + return 0; + + case SHMUT_STATUS: + /* + * in is a string like /libwebsockets/status.svg + */ + cp = ((const char *)in) + 7; + while (*cp == '/') + cp++; + n = 0; + while (*cp != '/' && *cp && (size_t)n < sizeof(projname) - 1) + projname[n++] = *cp++; + projname[n] = '\0'; + + lwsl_notice("%s: status %s\n", __func__, projname); + + r = sai_get_head_status(vhd, projname); + if (r < 2) + r = 2; + n = lws_snprintf(projname, sizeof(projname), + "../decal-%d.svg", r); + + if (lws_http_redirect(wsi, 307, + (unsigned char *)projname, n, + &p, end) < 0) + return -1; + + goto passthru; + + case SHMUT_ARTIFACTS: + /* + * HTTP Bulk GET interface for artifact download + * + * /artifacts/<taskhash>/<down_nonce>/filename + */ + lwsl_notice("%s: SHMUT_ARTIFACTS\n", __func__); + pss->artifact_offset = 0; + if (saim_get_blob(vhd, (const char *)in + 11, + &pss->pdb_artifact, + &pss->blob_artifact, + &pss->artifact_length)) { + lwsl_notice("%s: get_blob failed\n", __func__); + resp = 404; + goto http_resp; + } + + /* + * Well, it seems what he wanted exists.. + */ + + if (lws_add_http_header_status(wsi, 200, &p, end)) + goto bail; + if (lws_add_http_header_content_length(wsi, + (unsigned long)pss->artifact_length, + &p, end)) + goto bail; + + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_CONTENT_TYPE, + (uint8_t *)"application/octet-stream", + 24, &p, end)) + goto bail; + if (lws_finalize_write_http_header(wsi, start, &p, end)) + goto bail; + + lwsl_notice("%s: started artifact transaction %d\n", __func__, + (int)pss->artifact_length); + + lws_callback_on_writable(wsi); + return 0; + + default: + lwsl_notice("%s: DEFAULT!!!\n", __func__); + return 0; + } + + resp = HTTP_STATUS_OK; + + /* faillthru */ + +http_resp: + if (lws_add_http_header_status(wsi, resp, &p, end)) + goto bail; + if (lws_add_http_header_content_length(wsi, 0, &p, end)) + goto bail; + if (lws_finalize_write_http_header(wsi, start, &p, end)) + goto bail; + goto try_to_reuse; + + + case LWS_CALLBACK_HTTP_WRITEABLE: + + lwsl_notice("%s: HTTP_WRITEABLE\n", __func__); + + if (!pss || !pss->blob_artifact) + break; + + n = lws_ptr_diff(end, start); + if ((int)(pss->artifact_length - pss->artifact_offset) < n) + n = (int)(pss->artifact_length - pss->artifact_offset); + + if (sqlite3_blob_read(pss->blob_artifact, start, n, + pss->artifact_offset)) { + lwsl_err("%s: blob read failed\n", __func__); + return -1; + } + + pss->artifact_offset += n; + + if (lws_write(wsi, start, n, + pss->artifact_offset != pss->artifact_length ? + LWS_WRITE_HTTP : LWS_WRITE_HTTP_FINAL) != n) + return -1; + + if (pss->artifact_offset != pss->artifact_length) + lws_callback_on_writable(wsi); + + break; + + /* + * Notifcation POSTs + */ + + case LWS_CALLBACK_HTTP_BODY: + + if (!pss->our_form) { + lwsl_notice("%s: not our form\n", __func__); + goto passthru; + } + + lwsl_user("LWS_CALLBACK_HTTP_BODY: %d\n", (int)len); + /* create the POST argument parser if not already existing */ + + if (!pss->spa) { + pss->wsi = wsi; + if (lws_hdr_copy(wsi, pss->notification_sig, + sizeof(pss->notification_sig), + WSI_TOKEN_HTTP_AUTHORIZATION) < 0) { + lwsl_err("%s: failed to get signature hdr\n", + __func__); + return -1; + } + + if (lws_hdr_copy(wsi, pss->sn.e.source_ip, + sizeof(pss->sn.e.source_ip), + WSI_TOKEN_X_FORWARDED_FOR) < 0) + lws_get_peer_simple(wsi, pss->sn.e.source_ip, + sizeof(pss->sn.e.source_ip)); + + pss->spa = lws_spa_create(wsi, NULL, 0, 1024, + sai_notification_file_upload_cb, pss); + if (!pss->spa) { + lwsl_err("failed to create spa\n"); + return -1; + } + + /* find out the hmac used to sign it */ + + pss->hmac_type = LWS_GENHMAC_TYPE_UNKNOWN; + for (n = 0; n < (int)LWS_ARRAY_SIZE(hmac_names); n++) + if (!strncmp(pss->notification_sig, + hmac_names[n], + strlen(hmac_names[n]))) { + pss->hmac_type = n + 1; + break; + } + + if (pss->hmac_type == LWS_GENHMAC_TYPE_UNKNOWN) { + lwsl_notice("%s: unknown sig hash type\n", + __func__); + return -1; + } + + /* convert it to binary */ + + n = lws_hex_to_byte_array( + pss->notification_sig + strlen(hmac_names[n]), + (uint8_t *)pss->notification_sig, 64); + + if (n != (int)lws_genhmac_size(pss->hmac_type)) { + lwsl_notice("%s: notifcation hash bad length\n", + __func__); + + return -1; + } + } + + /* let it parse the POST data */ + + if (!pss->spa_failed && + lws_spa_process(pss->spa, in, (int)len)) + /* + * mark it as failed, and continue taking body until + * completion, and return error there + */ + pss->spa_failed = 1; + + break; + + case LWS_CALLBACK_HTTP_BODY_COMPLETION: + lwsl_user("%s: LWS_CALLBACK_HTTP_BODY_COMPLETION: %d\n", + __func__, (int)len); + + if (!pss->our_form) { + lwsl_user("%s: no sai form\n", __func__); + goto passthru; + } + + + /* inform the spa no more payload data coming */ + lws_spa_finalize(pss->spa); + if (pss->spa) { + lws_spa_destroy(pss->spa); + pss->spa = NULL; + } + + if (pss->spa_failed) + lwsl_notice("%s: notification failed\n", __func__); + else { + lwsl_notice("%s: notification: %d %s %s %s\n", __func__, + pss->sn.action, pss->sn.e.hash, + pss->sn.e.ref, pss->sn.e.repo_name); + + /* update connected browsers */ + + lws_dll2_foreach_safe(&vhd->browsers, + (void *)WSS_PREPARE_OVERVIEW, browser_upd); + } + + if (lws_return_http_status(wsi, + pss->spa_failed ? HTTP_STATUS_FORBIDDEN : + HTTP_STATUS_OK, + NULL) < 0) + return -1; + break; + + /* + * ws connections from builders and browsers + */ + + case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: + n = lws_hdr_copy(wsi, (char *)buf, sizeof(buf) - 1, + WSI_TOKEN_GET_URI); + if (!n) + buf[0] = '\0'; + //lwsl_notice("%s: checking with lwsgs for ws conn: %s\n", + // __func__, (const char *)buf); + + /* + * Builders don't authenticate using sessions... + */ + + if (n >= 8 && !strncmp((const char *)buf + n - 8, + "/builder", 8)) + return 0; + + return 0; +#if 0 + /* but everything else does */ + + car_args.max_len = LWSGS_AUTH_LOGGED_IN | LWSGS_AUTH_VERIFIED; + car_args.final = 0; + car_args.chunked = 1; /* ie, we are ws */ + + in = &car_args; + goto passthru; +#endif + + case LWS_CALLBACK_ESTABLISHED: + + // lwsl_notice("%s: wsi %p: ESTABLISHED\n", __func__, wsi); +#if 0 + vhd->gsp->callback(wsi, LWS_CALLBACK_SESSION_INFO, + pss->pss_gs, &pss->sinfo, 0); + if (!pss->sinfo.username[0]) { + lwsl_notice("sai ws attempt with no session\n"); + + return -1; + } +#endif + + pss->wsi = wsi; + pss->vhd = vhd; + pss->alang[0] = '\0'; + lws_hdr_copy(wsi, pss->alang, sizeof(pss->alang), + WSI_TOKEN_HTTP_ACCEPT_LANGUAGE); + + if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) { + if (lws_hdr_copy(wsi, (char *)start, 64, + WSI_TOKEN_GET_URI) < 0) + return -1; + } +#if defined(LWS_ROLE_H2) + else + if (lws_hdr_copy(wsi, (char *)start, 64, + WSI_TOKEN_HTTP_COLON_PATH) < 0) + return -1; +#endif + + if (!memcmp((char *)start, "/sai", 4)) + start += 4; + + if (!strncmp((char *)start, "/browse/specific", 16)) { + const char *spe; + + lwsl_info("%s: ESTABLISHED: browser (specific)\n", __func__); + pss->type = SWT_BROWSE; + pss->wsi = wsi; + pss->specific_project[0] = '\0'; + spe = (const char *)start + 16; + while(*spe == '/') + spe++; + n = 0; + while(*spe && *spe != '/' && + (size_t)n < sizeof(pss->specific_project) - 2) + pss->specific_project[n++] = *spe++; + + pss->specific_project[n] = '\0'; + + strncpy(pss->specific, "refs/heads/", + sizeof(pss->specific)); + if (lws_get_urlarg_by_name(wsi, "h", pss->specific + 9, + sizeof(pss->specific) - 9)) { + memcpy(pss->specific, "refs/heads/", 11); + pss->specificity = SAIM_SPECIFIC_H; + } else + if (lws_get_urlarg_by_name(wsi, "id", pss->specific + 8, + sizeof(pss->specific))) { + memcpy(pss->specific, "refs/heads/", 11); + pss->specificity = SAIM_SPECIFIC_ID; + } else { + pss->specificity = SAIM_SPECIFIC_H; + strncpy(pss->specific, "refs/heads/master", + sizeof(pss->specific)); + } + break; + } + + if (!strcmp((char *)start, "/browse")) { + lwsl_info("%s: ESTABLISHED: browser\n", __func__); + pss->type = SWT_BROWSE; + pss->wsi = wsi; + break; + } + if (!strcmp((char *)start, "/builder")) { + lwsl_info("%s: ESTABLISHED: builder\n", __func__); + pss->type = SWT_BUILDER; + pss->wsi = wsi; + /* + * this adds our pss part, but not the logical builder + * yet, until we get the ws rx + */ + lws_dll2_add_head(&pss->same, &vhd->builders); + break; + } + + lwsl_err("%s: unknown URL '%s'\n", __func__, start); + + return -1; + + case LWS_CALLBACK_CLOSED: + lwsac_free(&pss->query_ac); + switch (pss->type) { + case SWT_BROWSE: + lwsl_info("%s: CLOSED browse conn\n", __func__); + lws_dll2_remove(&pss->same); + lws_dll2_remove(&pss->subs_list); + lwsac_free(&pss->task_ac); + lwsac_free(&pss->logs_ac); + break; + + case SWT_BUILDER: + lwsl_user("%s: CLOSED builder conn\n", __func__); + /* remove pss from vhd->builders */ + lws_dll2_remove(&pss->same); + + /* + * Destroy any the builder-tracking objects that + * were using this departing connection + */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, + vhd->master.builder_owner.head) { + sai_plat_t *cb = lws_container_of(p, sai_plat_t, + sai_plat_list); + + if (cb->wsi == wsi) { + /* remove builder object itself from master list */ + cb->wsi = NULL; + lws_dll2_remove(&cb->sai_plat_list); + /* + * free the deserialized builder object, + * everything he pointed to was overallocated + * when his deep copy was made + */ + free(cb); + } + + } lws_end_foreach_dll_safe(p, p1); + + if (pss->blob_artifact) { + sqlite3_blob_close(pss->blob_artifact); + pss->blob_artifact = NULL; + } + + if (pss->pdb_artifact) { + saim_event_db_close(pss->vhd, &pss->pdb_artifact); + pss->pdb_artifact = NULL; + } + + /* update the browsers about the builder removal */ + lws_dll2_foreach_safe(&vhd->browsers, + (void *)WSS_PREPARE_BUILDER_SUMMARY, browser_upd); + break; + } + break; + + case LWS_CALLBACK_RECEIVE: + + if (!pss->vhd) + pss->vhd = vhd; + + switch (pss->type) { + case SWT_BROWSE: + // lwsl_user("SWT_BROWSE RX: %d\n", (int)len); + /* + * Browser UI sent us something on websockets + */ + if (saim_ws_json_rx_browser(vhd, pss, in, len)) + return -1; + break; + + case SWT_BUILDER: + lwsl_info("SWT_BUILDER RX: %d\n", (int)len); + /* + * Builder sent us something on websockets + */ + pss->wsi = wsi; + if (saim_ws_json_rx_builder(vhd, pss, in, len)) + return -1; + if (!pss->announced) { + lws_dll2_foreach_safe(&vhd->browsers, + (void *)WSS_PREPARE_BUILDER_SUMMARY, + browser_upd); + pss->announced = 1; + } + break; + default: + break; + } + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + if (!vhd) { + lwsl_notice("%s: no vhd\n", __func__); + break; + } + switch (pss->type) { + case SWT_BROWSE: + return saim_ws_json_tx_browser(vhd, pss, buf, sizeof(buf)); + break; + + case SWT_BUILDER: + return saim_ws_json_tx_builder(vhd, pss, buf, sizeof(buf)); + break; + default: + break; + } + break; + + default: +passthru: + // if (!pss || !vhd) + break; + + // return vhd->gsp->callback(wsi, reason, pss->pss_gs, in, len); + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); + +bail: + return 1; + +try_to_reuse: + if (lws_http_transaction_completed(wsi)) + return -1; + + return 0; +} + +const struct lws_protocols protocol_ws = + { "com-warmcat-sai", callback_ws, sizeof(struct pss), 0 }; diff --git a/src/master/m-conf.c b/src/master/m-conf.c new file mode 100644 index 0000000..498378f --- /dev/null +++ b/src/master/m-conf.c @@ -0,0 +1,82 @@ +/* + * Sai master src/master/conf.c + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <time.h> + +#define SAI_CONFIG_STRING_SIZE (16 * 1024) + +struct lws_context * +sai_lws_context_from_json(const char *config_dir, + struct lws_context_creation_info *info, + const struct lws_protocols **pprotocols) +{ + int cs_len = SAI_CONFIG_STRING_SIZE - 1; + struct lws_context *context; + char *cs, *config_strings; + + cs = config_strings = malloc(SAI_CONFIG_STRING_SIZE); + if (!config_strings) { + lwsl_err("Unable to allocate config strings heap\n"); + + return NULL; + } + + memset(info, 0, sizeof(*info)); + + info->external_baggage_free_on_destroy = config_strings; + info->pt_serv_buf_size = 8192; + info->options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | + LWS_SERVER_OPTION_EXPLICIT_VHOSTS | + LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE | + LWS_SERVER_OPTION_VALIDATE_UTF8; + + lwsl_notice("Using config dir: \"%s\"\n", config_dir); + + /* + * first go through the config for creating the outer context + */ + if (lwsws_get_config_globals(info, config_dir, &cs, &cs_len)) + goto init_failed; + + context = lws_create_context(info); + if (context == NULL) { + /* config_strings freed as 'external baggage' */ + return NULL; + } + + info->pprotocols = pprotocols; + + if (lwsws_get_config_vhosts(context, info, config_dir, &cs, &cs_len)) { + lwsl_err("%s: sai_lws_context_from_json failed\n", __func__); + lws_context_destroy(context); + + return NULL; + } + + return context; + +init_failed: + free(config_strings); + + return NULL; +} diff --git a/src/master/m-notification.c b/src/master/m-notification.c new file mode 100644 index 0000000..2412054 --- /dev/null +++ b/src/master/m-notification.c @@ -0,0 +1,938 @@ +/* + * Sai master - src/master/notification.c + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * The same ws interface is connected-to by builders (on path /builder), and + * provides the query transport for browsers (on path /browse). + */ + +#include <libwebsockets.h> +#include "m-private.h" + +/* starts from +1 of sai_notification_action_t */ + +static const char *notifaction_action_names[] = { + "repo-update" +}; + +static const char * const paths[] = { + "schema", + "action", + "repository.name", + "repository.fetchurl", + "ref", + "hash", + "nonce", + "saifile_len", + "saifile", +}; + +enum enum_paths { + LEJPN_SCHEMA, + LEJPN_ACTION, + LEJPN_REPOSITORY_NAME, + LEJPN_REPOSITORY_FETCHURL, + LEJPN_REF, + LEJPN_HASH, + LEJPN_NONCE, + LEJPN_SAIFILE_LEN, + LEJPN_SAIFILE, +}; + +/* + * Saifile parser + */ + +static const char * const saifile_paths[] = { + "schema", + "platforms.*.build", + "platforms.*.default", + "platforms.*", + "configurations.*.prep", + "configurations.*.cmake", + "configurations.*.deps", + "configurations.*.platforms", + "configurations.*.artifacts", + "configurations.*.cpack", + "configurations.*", +}; + +enum enum_saifile_paths { + LEJPNSAIF_SCHEMA, + LEJPNSAIF_PLAT_BUILD, + LEJPNSAIF_PLAT_DEFAULT, + LEJPNSAIF_PLAT_NAME, + LEJPNSAIF_CONFIGURATIONS_PREP, + LEJPNSAIF_CONFIGURATIONS_CMAKE, + LEJPNSAIF_CONFIGURATIONS_DEPS, + LEJPNSAIF_CONFIGURATIONS_PLATFORMS, + LEJPNSAIF_CONFIGURATIONS_ARTIFACTS, + LEJPNSAIF_CONFIGURATIONS_CPACK, + LEJPNSAIF_CONFIGURATIONS_NAME, +}; + +/* do the string subst for ${cmake} etc */ + +static int +exp_cmake(void *priv, const char *name, char *out, size_t *pos, size_t olen, + size_t *exp_ofs) +{ + sai_notification_t *sn = (sai_notification_t *)priv; + const char *replace = NULL; + size_t replace_len, rem_out; + + if (!strcmp(name, "prep")) { + replace = sn->t.prep; + replace_len = strlen(sn->t.prep); + goto expand; + } + + if (!strcmp(name, "cmake")) { + replace = sn->t.cmake; + replace_len = strlen(sn->t.cmake); + goto expand; + } + + if (!strcmp(name, "cpack")) { + replace = sn->t.cpack; + replace_len = strlen(sn->t.cpack); + goto expand; + } + + return LSTRX_FATAL_NAME_UNKNOWN; + +expand: + rem_out = olen - *pos; /* remaining rhs */ + replace_len -= *exp_ofs; + if (replace_len < rem_out) + rem_out = replace_len; + + memcpy(out + *pos, replace + (*exp_ofs), rem_out); + *exp_ofs += rem_out; + *pos += rem_out; + + if (rem_out == replace_len) + return LSTRX_DONE; + + return LSTRX_FILLED_OUT; +} + +static int +arg_to_bool(const char *s) +{ + static const char * const on[] = { "on", "yes", "true" }; + int n = atoi(s); + + if (n) + return 1; + + for (n = 0; n < (int)LWS_ARRAY_SIZE(on); n++) + if (!strcasecmp(s, on[n])) + return 1; + + return 0; +} + +static int +sai_tuple_element_compare(const char *e1, const char *e2) +{ + char *p; + + // lwsl_notice("%s: comp '%s' '%s'\n", __func__, e1, e2); + + if (strlen(e1) > strlen(e2)) + /* can't match if req is longer than plat */ + return -1; + + p = strchr(e2, '/'); + if (p && lws_ptr_diff(p, e2) < (int)strlen(e1)) + /* e2 contains a / section terminal inside e1 scope */ + return 1; + + return strncmp(e1, e2, strlen(e1)); +} + +static int +sai_tuple_compare(const char *req, int req_len, const char *plat) +{ + const char *pos = req; + char e1[96]; + int n; + + do { + n = 0; + while (n < (int)sizeof(e1) - 1 && req_len && *pos != '/') { + e1[n++] = *pos++; + req_len--; + } + e1[n] = '\0'; + if (*pos == '/' && req_len) { + req_len--; + pos++; + } + + if (n == sizeof(e1) - 1) { + // lwsl_notice("%s: NOMATCH 3 '%s' '%s'\n", __func__, req, plat); + /* element too long */ + return 1; + } + + if (n && /* let it pass if empty match section, eg, linux//gcc */ + sai_tuple_element_compare(e1, plat)) { + // lwsl_notice("%s: NOMATCH 2 '%s' '%s'\n", __func__, req, plat); + /* section does not match */ + return 1; + } + + while (*plat && *plat != '/') + plat++; + if (*plat == '/') + plat++; + + if (!*plat && req_len) { + // lwsl_notice("%s: NOMATCH 1 '%s' '%s'\n", __func__, req, plat); + /* FAIL: req had more than we had */ + return 1; + } + + if (!req_len) { + // lwsl_notice("%s: MATCH '%s' '%s'\n", __func__, req, plat); + /* MATCH: we matched at least as far as we had */ + return 0; + } + + } while (1); + + return 1; +} + +/* + * We parse the saifile JSON + */ + +static signed char +sai_saifile_lejp_cb(struct lejp_ctx *ctx, char reason) +{ + struct pss *pss = (struct pss *)ctx->user; + sai_notification_t *sn = (sai_notification_t *)&pss->sn; + sqlite3 *pdb = NULL; + size_t n; + + // lwsl_notice("%s: reason %d, %s\n", __func__, reason, ctx->path); + + if (reason == LEJPCB_COMPLETE) { + + if (!pss->dry) + lwsl_notice("%s: saifile decode completed\n", __func__); + + return 0; + } + + if (reason == LEJPCB_OBJECT_START && + ctx->path_match - 1 == LEJPNSAIF_CONFIGURATIONS_NAME) { + /* + * We're at the testname part of "testname" : { } + */ + lws_strncpy(sn->t.taskname, &ctx->path[15], + sizeof(sn->t.taskname)); + sn->t.prep[0] = '\0'; + sn->t.packages[0] = '\0'; + sn->t.cmake[0] = '\0'; + sn->t.artifacts[0] = '\0'; + sn->explicit_platforms[0] = '\0'; + return 0; + } + + if (reason == LEJPCB_OBJECT_END && + ctx->path_match - 1 == LEJPNSAIF_CONFIGURATIONS_NAME && + sn->t.taskname[0]) { + lws_dll2_owner_t owner; + + /* + * We're at the testname part of "testname" : { } + */ + if (pss->dry) { + sn->t.taskname[0] = '\0'; + + return 0; + } + + /* + * Iterate through each platform creating task entries for this + * configuration customized for each platform... first time just + * check the string subst for all of them is OK... + */ + + lwsl_notice("%s: Creating task entries for notification\n", __func__); + + lws_start_foreach_dll(struct lws_dll2 *, p, + pss->platform_owner.head) { + sai_platform_t *pl = + lws_container_of(p, sai_platform_t, list); + size_t used_in, used_out; + struct lws_tokenize ts; + int n, match = 0; + lws_strexp_t sx; + + if (!pl->nondefault) + /* + * If it's a default platform, then match + * by default + */ + match = 1; + + if (sn->explicit_platforms[0]) { + char not = 0; + + /* + * If the configuration gives explicit + * platforms, we have to filter the platform + * against the comma-separated list. + * + * "none" - don't match any plats just because + * they are default + * + * "not plat" - disallow specific plat "plat", + * implies you're not using "none" + * + * "plat" - allow specific plat "plat" + */ + memset(&ts, 0, sizeof(ts)); + ts.start = (char *)sn->explicit_platforms; + ts.len = strlen(ts.start); + ts.flags = LWS_TOKENIZE_F_DOT_NONTERM | + LWS_TOKENIZE_F_SLASH_NONTERM | + LWS_TOKENIZE_F_MINUS_NONTERM; + do { + ts.e = lws_tokenize(&ts); + if (ts.e != LWS_TOKZE_TOKEN) + continue; + + lwsl_notice("%s: check %.*s\n", __func__, + (int)ts.token_len, ts.token); + + if (!strncmp(ts.token, "none", + ts.token_len)) { + not = match = 0; + continue; + } + if (!strncmp(ts.token, "not", + ts.token_len)) { + not = 1; + continue; + } + /* + * We need to check with per-slash + * minimal matching, ie, "linux" + * matches "linux-ubuntu/x86_64/gcc" + */ + if (!sai_tuple_compare(ts.token, + ts.token_len, pl->name)) { + match = !not; + if (match) + break; + } + not = 0; + } while (ts.e > 0); + } + + if (match) { + + /* + * The platform build string (pl->build) is the + * base, with optional entries like ${cmake} + * that are filled in with the corresponding + * info from the specific configuration's + * "cmake" entry (sn->t.cmake)... + * + * sn->t.build becomes the string-substituted + * copy that is serialized + */ + + lws_strexp_init(&sx, sn, exp_cmake, sn->t.build, + sizeof(sn->t.build)); + + n = lws_strexp_expand(&sx, pl->build, + strlen(pl->build), + &used_in, &used_out); + if (n != LSTRX_DONE) { + lwsl_notice("%s: strsubst fail n=%d %s %s\n", + __func__, n, pl->build, + sn->t.cmake); + return -1; + } + } + } lws_end_foreach_dll(p); + + /* + * ...now we know the string handling will go well, create the + * event database file and create task entries in there for this + * configuration's tasks for each platform + */ + + if (saim_event_db_ensure_open(pss->vhd, pss->sn.e.uuid, 1, &pdb)) { + lwsl_err("%s: unable to open event-specific db\n", __func__); + return -1; + } + + lws_start_foreach_dll(struct lws_dll2 *, p, + pss->platform_owner.head) { + sai_platform_t *pl = + lws_container_of(p, sai_platform_t, list); + size_t used_in, used_out; + struct lws_tokenize ts; + int n, match = 0; + lws_strexp_t sx; + + if (!pl->nondefault) + /* + * If it's a nondefault platform, then match + * by default + */ + match = 1; + + if (sn->explicit_platforms[0]) { + char not = 0; + + /* + * If the configuration gives explicit + * platforms, we have to filter the platform + * against the comma-separated list. + * + * "none" - don't match any plats just because + * they are default + * + * "not plat" - disallow specific plat "plat", + * implies you're not using "none" + * + * "plat" - allow specific plat "plat" + */ + + memset(&ts, 0, sizeof(ts)); + ts.start = (char *)sn->explicit_platforms; + ts.len = strlen(ts.start); + ts.flags = LWS_TOKENIZE_F_DOT_NONTERM | + LWS_TOKENIZE_F_SLASH_NONTERM | + LWS_TOKENIZE_F_MINUS_NONTERM; + + do { + ts.e = lws_tokenize(&ts); + if (ts.e != LWS_TOKZE_TOKEN) + continue; + + lwsl_notice("%s: check %.*s\n", __func__, + (int)ts.token_len, ts.token); + + if (!strncmp(ts.token, "none", + ts.token_len)) { + not = match = 0; + continue; + } + if (!strncmp(ts.token, "not", + ts.token_len)) { + not = 1; + continue; + } + if (!sai_tuple_compare(ts.token, + ts.token_len, pl->name)) { + match = !not; + if (match) + break; + } + not = 0; + } while (ts.e > 0); + } + + if (match) { + /* + * For this platform, we want to create a task + * associated with this event. Tasks and logs + * associated with an event go in an event- + * specific database file for scalability. + */ + + lws_strexp_init(&sx, sn, exp_cmake, sn->t.build, + sizeof(sn->t.build)); + + n = lws_strexp_expand(&sx, pl->build, + strlen(pl->build), + &used_in, &used_out); + if (n != LSTRX_DONE) { + lwsl_notice("%s: strsubst failed %s %s\n", + __func__, pl->build, + sn->t.cmake); + saim_event_db_close(pss->vhd, &pdb); + return -1; + } + + /* + * Prepare a struct of the task object... + * task uuid is the event uuid and another + * random 32 chars, so you can always recover + * the related event uuid from the task uuid + */ + + memcpy(pss->sn.t.uuid, pss->sn.e.uuid, 32); + sai_uuid16_create(lws_get_context(pss->wsi), + pss->sn.t.uuid + 32); + strcpy(pss->sn.t.event_uuid, pss->sn.e.uuid); + pss->sn.t.uid = pss->sn.event_task_index++; + + /* + * This is basically a secret that anything + * trying to upload an artifact for the task + * must provide to authenticate. + */ + sai_uuid16_create(lws_get_context(pss->wsi), + pss->sn.t.art_up_nonce); + /* + * An unrelated secret that anything + * trying to download an artifact for the task + * must provide to identify it. + */ + sai_uuid16_create(lws_get_context(pss->wsi), + pss->sn.t.art_down_nonce); + + pss->sn.t.git_repo_url = + pss->sn.e.repo_fetchurl; + pss->sn.e.last_updated = + (unsigned long long)lws_now_secs(); + pss->sn.e.state = 0; + lws_strncpy(pss->sn.t.platform, pl->name, + sizeof(pss->sn.t.platform)); + + memset(&pss->sn.t.list, 0, sizeof(pss->sn.t.list)); + lws_dll2_owner_clear(&owner); + lws_dll2_add_head(&pss->sn.t.list, &owner); + + /* + * Create the task in event-specific database + */ + + lws_struct_sq3_serialize(pdb, + lsm_schema_sq3_map_task, + &owner, pss->sn.t.uid); + } + + } lws_end_foreach_dll(p); + + saim_event_db_close(pss->vhd, &pdb); + + lwsl_notice("%s: New test '%s', '%s', '%s'\n", __func__, + sn->t.taskname, sn->t.cmake, sn->t.packages); + + sn->t.taskname[0] = '\0'; + return 0; + } + + if (reason == LEJPCB_OBJECT_START && + ctx->path_match - 1 == LEJPNSAIF_PLAT_NAME) { + /* + * We're at the platformname part of "platformname" : { } + */ + lws_strncpy(sn->platname, &ctx->path[10], sizeof(sn->platname)); + sn->t.prep[0] = '\0'; + sn->t.cmake[0] = '\0'; + sn->t.cpack[0] = '\0'; + sn->platbuild[0] = '\0'; + sn->nondefault = 0; + return 0; + } + + if (reason == LEJPCB_OBJECT_END && + ctx->path_match - 1 == LEJPNSAIF_PLAT_NAME && + sn->platname[0] && sn->platbuild[0]) { + sai_platform_t *pl; + uint8_t *plb; + size_t pnl; + + if (pss->dry) { + sn->t.platform[0] = '\0'; + return 0; + } + + /* + * We create a platform object with space for its strings after + */ + + pnl = strlen(sn->platname); + pl = (sai_platform_t *)malloc(sizeof(*pl) + pnl + + strlen(sn->platbuild) + 2); + if (!pl) + return -1; + + memset(pl, 0, sizeof(*pl)); + plb = (uint8_t *)&pl[1]; + + /* + * Platforms are malloc'd up and added to pss + * .platform_owner + */ + + pl->name = (const char *)plb; + memcpy(plb, sn->platname, pnl + 1); + plb += pnl + 1; + + pl->build = (const char *)plb; + memcpy(plb, sn->platbuild, strlen(sn->platbuild) + 1); + + pl->nondefault = sn->nondefault; + + lws_dll2_add_head(&pl->list, &pss->platform_owner); + + lwsl_notice("%s: New platform '%s', build '%s', notdefault %d\n", + __func__, pl->name, pl->build, pl->nondefault); + + sn->platbuild[0] = '\0'; + return 0; + } + + /* we only match on the prepared path strings */ + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + +// if (reason != LEJPCB_VAL_STR_END) +// return 0; + + /* only the end part of the string, where we know the length */ + + switch (ctx->path_match - 1) { + case LEJPNSAIF_SCHEMA: + sn->event_task_index = 0; + return 0; + + case LEJPNSAIF_CONFIGURATIONS_PREP: + /* the additional cmake options for this test configuration */ + n = strlen(sn->t.prep); + if (n < sizeof(sn->t.prep) - 2) + lws_strnncpy(sn->t.prep + n, ctx->buf, ctx->npos, + sizeof(sn->t.prep) - n); + break; + + case LEJPNSAIF_CONFIGURATIONS_CMAKE: + /* the additional cmake options for this test configuration */ + n = strlen(sn->t.cmake); + if (n < sizeof(sn->t.cmake) - 2) + lws_strnncpy(sn->t.cmake + n, ctx->buf, ctx->npos, + sizeof(sn->t.cmake) - n); + break; + + case LEJPNSAIF_CONFIGURATIONS_DEPS: + /* the necessary dependent package strings */ + n = strlen(sn->t.packages); + if (n < sizeof(sn->t.packages) - 2) { + if (n) + sn->t.packages[n++] = ','; + lws_strncpy(sn->t.packages + n, ctx->buf, + sizeof(sn->t.packages) - n); + } + break; + + case LEJPNSAIF_CONFIGURATIONS_PLATFORMS: + /* the necessary dependent package strings */ + lws_strncpy(sn->explicit_platforms, ctx->buf, + sizeof(sn->explicit_platforms)); + break; + + case LEJPNSAIF_CONFIGURATIONS_ARTIFACTS: + lws_strncpy(sn->t.artifacts, ctx->buf, sizeof(sn->t.artifacts)); + break; + + case LEJPNSAIF_CONFIGURATIONS_CPACK: + lws_strncpy(sn->t.cpack, ctx->buf, sizeof(sn->t.cpack)); + break; + + case LEJPNSAIF_PLAT_BUILD: + /* + * The overall build script for this platform + * is appended into the temp sn.platbuild + */ + n = strlen(sn->platbuild); + if (n < sizeof(sn->platbuild) - 2) + lws_strnncpy(sn->platbuild + n, ctx->buf, ctx->npos, + sizeof(sn->platbuild) - n); + break; + + case LEJPNSAIF_PLAT_DEFAULT: + sn->nondefault = !arg_to_bool(ctx->buf); + break; + + default: + return 0; + } + + return 0; +} + +static signed char +sai_notification_lejp_cb(struct lejp_ctx *ctx, char reason) +{ + struct pss *pss = (struct pss *)ctx->user; + sai_notification_t *sn = (sai_notification_t *)&pss->sn; + size_t ile, ole; + int n; + + /* we only match on the prepared path strings */ + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + +// if (reason != LEJPCB_VAL_STR_END) +// return 0; + + /* only the end part of the string, where we know the length */ + + switch (ctx->path_match - 1) { + case LEJPN_SCHEMA: + if (strcmp(ctx->buf, "com-warmcat-sai-notification")) { + lwsl_err("%s: unknown schema '%s'\n", __func__, ctx->buf); + return -1; + } + return 0; + + case LEJPN_ACTION: + for (n = 0; n < (int)LWS_ARRAY_SIZE(notifaction_action_names);n++) + if (!strcmp(ctx->buf, notifaction_action_names[n])) { + sn->action = n + 1; + + return 0; + } + lwsl_notice("%s: unknown action '%s' ignored\n", + __func__, ctx->buf); + return -1; + + case LEJPN_REPOSITORY_NAME: + lws_strncpy(sn->e.repo_name, ctx->buf, sizeof(sn->e.repo_name)); + break; + + case LEJPN_REPOSITORY_FETCHURL: + lws_strncpy(sn->e.repo_fetchurl, ctx->buf, sizeof(sn->e.repo_fetchurl)); + break; + + case LEJPN_REF: + lws_strncpy(sn->e.ref, ctx->buf, sizeof(sn->e.ref)); + break; + + case LEJPN_HASH: + lws_strncpy(sn->e.hash, ctx->buf, sizeof(sn->e.hash)); + break; + + case LEJPN_NONCE: + break; + + case LEJPN_SAIFILE_LEN: + sn->saifile_in_len = atoi(ctx->buf); + /* only accept sane base64 size */ + if (sn->saifile_in_len < 8 || sn->saifile_in_len > 65536) { + lwsl_err("%s: bad saifile_len %u\n", __func__, + (unsigned int)sn->saifile_in_len); + + return -1; + } + sn->saifile_out_len = (sn->saifile_in_len * 3) / 4 + 4; + sn->saifile = malloc(sn->saifile_out_len); + if (!sn->saifile) { + lwsl_err("%s: OOM\n", __func__); + return -1; + } + sn->saifile_in_seen = sn->saifile_out_pos = 0; + lws_b64_decode_state_init(&sn->b64); + break; + + case LEJPN_SAIFILE: + /* + * Base64 encoding of the commit's .sai.json file contents... we + * have to treat this with caution since it can be anything, + * including unparsable JSON. + * + * Let's base64-decode it and collect it into an buffer first, + * before sn test parse and then sn second parse to extract the + * tasks + */ + + sn->saifile_in_seen += ctx->npos; + if (sn->saifile_in_seen > sn->saifile_in_len) { + lwsl_err("%s: SAIFILE: too large in %u vs %u\n", __func__, + (unsigned int)sn->saifile_in_seen, + (unsigned int)sn->saifile_in_len); + return -1; + } + ile = ctx->npos; + ole = sn->saifile_out_len - sn->saifile_out_pos; + lws_b64_decode_stateful(&sn->b64, ctx->buf, &ile, + (uint8_t *)sn->saifile + sn->saifile_out_pos, &ole, + sn->saifile_in_seen == sn->saifile_in_len); + + sn->saifile_out_pos += ole; + break; + + default: + return 0; + } + + return 0; +} + +int +sai_notification_file_upload_cb(void *data, const char *name, + const char *filename, char *buf, int len, + enum lws_spa_fileupload_states state) +{ + struct pss *pss = (struct pss *)data; + struct lejp_ctx saictx; + lws_dll2_owner_t owner; + uint8_t result[64]; + int m; + + switch (state) { + case LWS_UFS_OPEN: + lwsl_notice("%s: LWS_UFS_OPEN\n", __func__); + lejp_construct(&pss->ctx, sai_notification_lejp_cb, + pss, paths, LWS_ARRAY_SIZE(paths)); + + if (lws_genhmac_init(&pss->hmac, pss->hmac_type, + (uint8_t *)pss->vhd->notification_key, + strlen(pss->vhd->notification_key))) { + lwsl_err("%s: failed to init hmac\n", __func__); + + return -1; + } + break; + case LWS_UFS_FINAL_CONTENT: + case LWS_UFS_CONTENT: + lwsl_notice("%s: LWS_UFS_[]CONTENT: %p %p, \n", __func__, pss, buf); + if (len && lws_genhmac_update(&pss->hmac, buf, len)) + return -1; + + printf("%.*s", (int)len, buf); + + m = lejp_parse(&pss->ctx, (uint8_t *)buf, len); + if (m < 0 && m != LEJP_CONTINUE) { + lwsl_notice("%s: notif JSON decode failed '%s' (%d)\n", + __func__, lejp_error_to_string(m), m); + return m; + } + + if (m != 1) + break; + + lws_genhmac_destroy(&pss->hmac, result); + + if (memcmp(result, pss->notification_sig, + lws_genhmac_size(pss->hmac_type))) { + lwsl_err("%s: hmac mismatch\n", __func__); + + return -1; + } + lwsl_notice("%s: hmac OK\n", __func__); + + if (!pss->sn.saifile) + return -1; + + /* + * We processed the notification JSON, but we only decoded the + * base64'd copy of the .sai.json so far... now's the time we + * want to process that and break it down into tasks. + * + * We don't trust it since it's controlled by the guy who pushed + * the commit, there can be anything at all in there. We made + * sure he can't attack us until now by base64-ing it at the + * server hook, so he's just dumb payload. + * + * Let's try to parse it in two passes, first without acting on + * the content to confirm it's going to succeed... + */ + + sai_uuid16_create(lws_get_context(pss->wsi), pss->sn.e.uuid); + if (saim_event_db_ensure_open(pss->vhd, pss->sn.e.uuid, 1, + (sqlite3 **)&pss->sn.e.pdb)) { + lwsl_err("%s: unable to open event-specific database\n", + __func__); + + return -1; + } + + pss->dry = 1; + lejp_construct(&saictx, sai_saifile_lejp_cb, pss, saifile_paths, + LWS_ARRAY_SIZE(saifile_paths)); + m = lejp_parse(&saictx, (uint8_t *)pss->sn.saifile, + pss->sn.saifile_out_pos); + saim_event_db_close(pss->vhd, (sqlite3 **)&pss->sn.e.pdb); + if (m < 0) { + lwsl_notice("%s: saifile JSON decode failed '%s' (%d)\n", + __func__, lejp_error_to_string(m), m); + return m; + } + + /* ... then add the 32-char event object in the database ... */ + + pss->sn.e.created = (unsigned long long)lws_now_secs(); + pss->sn.e.state = SAIES_WAITING; + + memset(&pss->sn.e.list, 0, sizeof(pss->sn.e.list)); + lws_dll2_owner_clear(&owner); + lws_dll2_add_head(&pss->sn.e.list, &owner); + + /* + * This is our new event going into the event database... + */ + + lws_struct_sq3_serialize(pss->vhd->master.pdb, + lsm_schema_sq3_map_event, &owner, 0); + + /* + * ... process the saifile JSON again creating tasks for each + * entry in the saifile, for each platform, against that event + * object... + */ + + pss->dry = 0; + lejp_construct(&saictx, sai_saifile_lejp_cb, pss, saifile_paths, + LWS_ARRAY_SIZE(saifile_paths)); + m = lejp_parse(&saictx, (uint8_t *)pss->sn.saifile, + pss->sn.saifile_out_pos); + if (m < 0) { + lwsl_notice("%s: saifile JSON decode failed '%s' (%d)\n", + __func__, lejp_error_to_string(m), m); + return m; + } + + free(pss->sn.saifile); + pss->sn.saifile = NULL; + + lwsl_notice("%s: notification inserted into db\n", __func__); + + /* + * Reassess now if there's a builder we can match to a pending task + */ + + lws_sul_schedule(pss->vhd->context, 0, &pss->vhd->sul_central, + saim_central_cb, 1); + + return 0; + + + case LWS_UFS_CLOSE: + // lwsl_info("%s: LWS_UFS_CLOSE\n", __func__); + lejp_destruct(&pss->ctx); + break; + } + + return 0; +} + diff --git a/src/master/m-overview.c b/src/master/m-overview.c new file mode 100644 index 0000000..e650c19 --- /dev/null +++ b/src/master/m-overview.c @@ -0,0 +1,28 @@ +/* + * Sai master - src/master/overview.c + * + * Copyright (C) 2019 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * The same ws interface is connected-to by builders (on path /builder), and + * provides the query transport for browsers (on path /browse). + */ + +#include <libwebsockets.h> +#include "m-private.h" + + diff --git a/src/master/m-private.h b/src/master/m-private.h new file mode 100644 index 0000000..de1883d --- /dev/null +++ b/src/master/m-private.h @@ -0,0 +1,286 @@ +/* + * Sai master definitions src/master/private.h + * + * Copyright (C) 2019 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "../common/include/private.h" +#include <sqlite3.h> +#include <sys/stat.h> + +struct sai_plat; + +typedef struct sai_platm { + struct lws_dll2_owner builder_owner; + struct lws_dll2_owner subs_owner; + + sqlite3 *pdb; +} saim_t; + +typedef struct sai_platform { + struct lws_dll2 list; + + const char *name; + const char *build; + + uint8_t nondefault; + + /* build and name over-allocated here */ +} sai_platform_t; + +typedef enum { + SAIN_ACTION_INVALID, + SAIN_ACTION_REPO_UPDATED +} sai_notification_action_t; + +typedef struct { + + sai_event_t e; + sai_task_t t; + + char platbuild[4096]; + char platname[96]; + char explicit_platforms[2048]; + + int event_task_index; + + struct lws_b64state b64; + char *saifile; + uint64_t when; + size_t saifile_in_len; + size_t saifile_out_len; + size_t saifile_out_pos; + size_t saifile_in_seen; + sai_notification_action_t action; + + uint8_t nondefault; +} sai_notification_t; + +typedef enum { + SWT_BUILDER, + SWT_BROWSE +} ws_type; + +typedef enum { + WSS_IDLE, + WSS_PREPARE_OVERVIEW, + WSS_SEND_OVERVIEW, + WSS_PREPARE_BUILDER_SUMMARY, + WSS_SEND_BUILDER_SUMMARY, + + WSS_PREPARE_TASKINFO, + WSS_SEND_ARTIFACT_INFO, + + WSS_PREPARE_EVENTINFO, + WSS_SEND_EVENTINFO, +} ws_state; + +typedef struct sai_builder { + saim_t c; +} saib_t; + +struct vhd; + +enum { + SAIM_NOT_SPECIFIC, + SAIM_SPECIFIC_H, + SAIM_SPECIFIC_ID +}; + +struct pss { + struct vhd *vhd; + struct lws *wsi; + + struct lws_spa *spa; + struct lejp_ctx ctx; + sai_notification_t sn; + struct lws_dll2 same; /* owner: vhd.builders */ + + struct lws_dll2 subs_list; + + uint64_t sub_timestamp; + char sub_task_uuid[65]; + char specific[65]; + char specific_project[96]; + + sqlite3 *pdb_artifact; + sqlite3_blob *blob_artifact; + + lws_dll2_owner_t platform_owner; /* sai_platform_t builder offers */ + lws_dll2_owner_t task_cancel_owner; /* sai_platform_t builder offers */ + lws_dll2_owner_t aft_owner; /* for statefully spooling artifact info */ + lws_struct_args_t a; + + union { + sai_plat_t *b; + sai_plat_owner_t *o; + } u; + const char *master_name; + + struct lwsac *query_ac; + struct lwsac *task_ac; /* tasks for an event */ + struct lwsac *logs_ac; + lws_dll2_owner_t issue_task_owner; /* list of sai_task_t */ + const sai_task_t *one_task; /* only for browser */ + const sai_event_t *one_event; + lws_dll2_owner_t query_owner; + lws_dll2_t *walk; + + int task_index; + int log_cache_index; + int log_cache_size; + int authorized; + int specificity; + + /* notification hmac information */ + char notification_sig[128]; + char alang[128]; + struct lws_genhmac_ctx hmac; + enum lws_genhmac_types hmac_type; + char our_form; + + uint64_t first_log_timestamp; + uint64_t artifact_offset; + uint64_t artifact_length; + + ws_type type; + ws_state send_state; + + uint32_t pending; /* bitmap of things that need sending */ + + unsigned int spa_failed:1; + unsigned int subsequent:1; /* for individual JSON */ + unsigned int dry:1; + unsigned int query_already_done:1; + unsigned int frag:1; + unsigned int mark_started:1; + unsigned int wants_event_updates:1; + unsigned int announced:1; + unsigned int bulk_binary_data:1; + + uint8_t ovstate; /* SOS_ substate when doing overview */ +}; + +typedef struct saim_sqlite_cache { + lws_dll2_t list; + char uuid[65]; + sqlite3 *pdb; + lws_usec_t idle_since; + int refcount; +} saim_sqlite_cache_t; + +struct vhd { + struct lws_context *context; + struct lws_vhost *vhost; + + /* pss lists */ + struct lws_dll2_owner browsers; + struct lws_dll2_owner builders; + + const char *sqlite3_path_lhs; + + lws_dll2_owner_t sqlite3_cache; /* saim_sqlite_cache_t */ + lws_dll2_owner_t tasklog_cache; + lws_sorted_usec_list_t sul_logcache; + lws_sorted_usec_list_t sul_central; /* background task allocation sul */ + + lws_usec_t last_check_abandoned_tasks; + + const char *notification_key; + + saim_t master; +}; + +extern struct lws_context * +sai_lws_context_from_json(const char *config_dir, + struct lws_context_creation_info *info, + const struct lws_protocols **pprotocols); +extern const struct lws_protocols protocol_ws; + +int +sai_notification_file_upload_cb(void *data, const char *name, + const char *filename, char *buf, int len, + enum lws_spa_fileupload_states state); + +int +sai_sqlite3_statement(sqlite3 *pdb, const char *cmd, const char *desc); + +int +sai_uuid16_create(struct lws_context *context, char *dest33); + +int +saim_event_db_ensure_open(struct vhd *vhd, const char *event_uuid, char can_create, sqlite3 **ppdb); + +void +saim_event_db_close(struct vhd *vhd, sqlite3 **ppdb); + +int +saim_event_db_delete_database(struct vhd *vhd, const char *event_uuid); + +int +sai_sq3_event_lookup(sqlite3 *pdb, uint64_t start, lws_struct_args_cb cb, void *ca); + +int +sai_sql3_get_uint64_cb(void *user, int cols, char **values, char **name); + +int +saim_ws_json_tx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf, size_t bl); + +int +saim_ws_json_rx_builder(struct vhd *vhd, struct pss *pss, uint8_t *buf, size_t bl); + +int +lws_struct_map_set(const lws_struct_map_t *map, char *u); + +int +saim_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, + uint8_t *buf, size_t bl); + +void +sai_task_uuid_to_event_uuid(char *event_uuid33, const char *task_uuid65); + +int +saim_ws_json_tx_builder(struct vhd *vhd, struct pss *pss, uint8_t *buf, size_t bl); + +void +mark_pending(struct pss *pss, ws_state state); + +int +saim_subs_request_writeable(struct vhd *vhd, const char *task_uuid); + +void +saim_central_cb(lws_sorted_usec_list_t *sul); + +int +saim_task_reset(struct vhd *vhd, const char *task_uuid); + +int +saim_task_cancel(struct vhd *vhd, const char *task_uuid); + +int +saim_allocate_task(struct vhd *vhd, struct pss *pss, sai_plat_t *cb, + const char *cns_name); + +int +saim_set_task_state(struct vhd *vhd, const char *builder_name, + const char *builder_uuid, const char *task_uuid, int state, + uint64_t started, uint64_t duration); + +int +saim_get_blob(struct vhd *vhd, const char *url, sqlite3 **pdb, + sqlite3_blob **blob, uint64_t *length); diff --git a/src/master/m-sai.c b/src/master/m-sai.c new file mode 100644 index 0000000..5e753d6 --- /dev/null +++ b/src/master/m-sai.c @@ -0,0 +1,69 @@ +/* + * Sai master + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <time.h> + +#include "m-private.h" + +static int interrupted; +struct lws_context *context; + +static const struct lws_protocols + *pprotocols[] = { &protocol_ws, NULL }; + +static void sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + const char *p, *conf = "/etc/sai/master"; + struct lws_context_creation_info info; + + signal(SIGINT, sigint_handler); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("Sai Master - Copyright (C) 2019-2020 Andy Green <andy@warmcat.com>\n"); + + if ((p = lws_cmdline_option(argc, argv, "-c"))) + conf = p; + + context = sai_lws_context_from_json(conf, &info, pprotocols); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + while (!lws_service(context, 0) && !interrupted) + ; + + lws_context_destroy(context); + + return 0; +} diff --git a/src/master/m-task.c b/src/master/m-task.c new file mode 100644 index 0000000..76ff2ae --- /dev/null +++ b/src/master/m-task.c @@ -0,0 +1,467 @@ +/* + * Sai master + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <time.h> +#include <assert.h> + +#include "m-private.h" + +static int +browser_upd(struct lws_dll2 *d, void *user) +{ + struct pss *pss = lws_container_of(d, struct pss, same); + ws_state state = (ws_state)(intptr_t)user; + + mark_pending(pss, state); + + return 0; +} + +static int +sql3_get_integer_cb(void *user, int cols, char **values, char **name) +{ + unsigned int *pui = (unsigned int *)user; + + lwsl_warn("%s: values[0] '%s'\n", __func__, values[0]); + *pui = atoi(values[0]); + + return 0; +} + +void +sai_task_uuid_to_event_uuid(char *event_uuid33, const char *task_uuid65) +{ + memcpy(event_uuid33, task_uuid65, 32); + event_uuid33[32] = '\0'; +} + +int +saim_set_task_state(struct vhd *vhd, const char *builder_name, + const char *builder_uuid, const char *task_uuid, int state, + uint64_t started, uint64_t duration) +{ + char update[384], esc[96], esc1[96], esc2[96], esc3[32], esc4[32], + event_uuid[33]; + unsigned int count = 0, count_good = 0, count_bad = 0; + sai_event_state_t oes, sta; + struct lwsac *ac = NULL; + sai_event_t *e = NULL; + lws_dll2_owner_t o; + int n; + + /* + * Extract the event uuid from the task uuid + */ + + sai_task_uuid_to_event_uuid(event_uuid, task_uuid); + + /* + * Look up the task's event in the event database... + */ + + lws_dll2_owner_clear(&o); + lws_sql_purify(esc1, event_uuid, sizeof(esc1)); + lws_snprintf(esc2, sizeof(esc2), " and uuid='%s'", esc1); + n = lws_struct_sq3_deserialize(vhd->master.pdb, esc2, NULL, + lsm_schema_sq3_map_event, &o, &ac, 0, 1); + if (n < 0 || !o.head) { + lwsl_err("%s: failed to get task_uuid %s\n", __func__, esc1); + goto bail; + } + + e = lws_container_of(o.head, sai_event_t, list); + oes = e->state; + + /* + * Open the event-specific database on the temporary event object + */ + + if (saim_event_db_ensure_open(vhd, event_uuid, 0, (sqlite3 **)&e->pdb)) { + lwsl_err("%s: unable to open event-specific database\n", + __func__); + + return -1; + } + + if (builder_name) + lws_sql_purify(esc, builder_name, sizeof(esc)); + else + esc[0] = '\0'; + + if (builder_uuid) + lws_sql_purify(esc1, builder_uuid, sizeof(esc1)); + else + esc1[0] = '\0'; + lws_sql_purify(esc2, task_uuid, sizeof(esc2)); + + esc3[0] = esc4[0] = '\0'; + + if (started) { + if (started == 1) + started = 0; + lws_snprintf(esc3, sizeof(esc3), ",started=%llu", + (unsigned long long)started); + } + if (duration) { + if (duration == 1) + duration = 0; + lws_snprintf(esc4, sizeof(esc4), ",duration=%llu", + (unsigned long long)duration); + } + + /* + * Update the task by uuid, in the event-specific database + */ + + lws_snprintf(update, sizeof(update), + "update tasks set state=%d%s%s%s%s%s%s%s%s where uuid='%s'", + state, builder_uuid ? ",builder='": "", + builder_uuid ? esc1 : "", + builder_uuid ? "'" : "", + builder_name ? ",builder_name='" : "", + builder_name ? esc : "", + builder_name ? "'" : "", + esc3, esc4, esc2); + + if (sqlite3_exec((sqlite3 *)e->pdb, update, NULL, NULL, NULL) != SQLITE_OK) { + lwsl_err("%s: %s: %s: fail\n", __func__, update, + sqlite3_errmsg(vhd->master.pdb)); + goto bail; + } + + /* + * So, how many tasks for this event? + */ + + if (sqlite3_exec((sqlite3 *)e->pdb, "select count(state) from tasks", + sql3_get_integer_cb, &count, NULL) != SQLITE_OK) { + lwsl_err("%s: %s: %s: fail\n", __func__, update, + sqlite3_errmsg(vhd->master.pdb)); + goto bail; + } + + /* + * ... how many completed well? + */ + + if (sqlite3_exec((sqlite3 *)e->pdb, "select count(state) from tasks where state == 3", + sql3_get_integer_cb, &count_good, NULL) != SQLITE_OK) { + lwsl_err("%s: %s: %s: fail\n", __func__, update, + sqlite3_errmsg(vhd->master.pdb)); + goto bail; + } + + /* + * ... how many failed? + */ + + if (sqlite3_exec((sqlite3 *)e->pdb, "select count(state) from tasks where state == 4", + sql3_get_integer_cb, &count_bad, NULL) != SQLITE_OK) { + lwsl_err("%s: %s: %s: fail\n", __func__, update, + sqlite3_errmsg(vhd->master.pdb)); + goto bail; + } + + /* + * Decide how to set the event state based on that + */ + + lwsl_warn("%s: ev %s, count %u, count_good %u, count_bad %u\n", + __func__, event_uuid, count, count_good, count_bad); + + sta = SAIES_BEING_BUILT; + + if (count) { + if (count == count_good) + sta = SAIES_SUCCESS; + else + if (count == count_bad) + sta = SAIES_FAIL; + else + if (count_bad) + sta = SAIES_BEING_BUILT_HAS_FAILURES; + } + + if (sta != oes) { + lwsl_notice("%s: event state changed\n", __func__); + } + + /* + * Update the event + */ + + lws_sql_purify(esc1, event_uuid, sizeof(esc1)); + lws_snprintf(update, sizeof(update), + "update events set state=%d where uuid='%s'", sta, esc1); + + if (sqlite3_exec(vhd->master.pdb, update, NULL, NULL, NULL) != SQLITE_OK) { + lwsl_err("%s: %s: %s: fail\n", __func__, update, + sqlite3_errmsg(vhd->master.pdb)); + goto bail; + } + + saim_event_db_close(vhd, (sqlite3 **)&e->pdb); + + /* + * We need to schedule sending an event info update to anyone + * subscribing to the task... + */ + + lws_dll2_foreach_safe(&vhd->browsers, + (void *)WSS_PREPARE_OVERVIEW, browser_upd); + + lwsac_free(&ac); + + return 0; + +bail: + if (e) + saim_event_db_close(vhd, (sqlite3 **)&e->pdb); + lwsac_free(&ac); + + return 1; +} + +/* + * Find the most recent task that still needs doing for platform, on any event + * + * If any, the task pointed-to lives inside *pac, along with its strings etc + */ + +static const sai_task_t * +saim_task_pending(struct vhd *vhd, struct lwsac **pac, const char *platform) +{ + struct lwsac *ac = NULL; + char esc[96], pf[96]; + lws_dll2_owner_t o; + int n; + + lws_sql_purify(esc, platform, sizeof(esc)); + + assert(platform); + assert(pac); + + /* + * Collect a list of events that still have any open tasks + * + * We don't put this list in the pac since we can dispose of it in this + * scope whether we find something or not + */ + + n = lws_struct_sq3_deserialize(vhd->master.pdb, + " and (state != 3 and state != 4 and state != 5)", + "created desc ", lsm_schema_sq3_map_event, &o, + &ac, 0, 10); + if (n < 0 || !o.head) { + // lwsl_notice("%s: all events complete\n", __func__); + /* error, or there are no events that aren't complete */ + goto bail; + } + + /* + * Iterate through the events looking at his event-specific database + * for tasks on the specified platform... + */ + + lws_start_foreach_dll(struct lws_dll2 *, p, o.head) { + sai_event_t *e = lws_container_of(p, sai_event_t, list); + lws_dll2_owner_t ot; + sqlite3 *pdb = NULL; + + if (!saim_event_db_ensure_open(vhd, e->uuid, 0, &pdb)) { + lws_snprintf(pf, sizeof(pf), + " and state=0 and platform='%s'", esc); + n = lws_struct_sq3_deserialize(pdb, pf, NULL, + lsm_schema_sq3_map_task, + &ot, pac, 0, 1); + saim_event_db_close(vhd, &pdb); + if (ot.count) { + + lwsl_notice("%s: found task for %s\n", __func__, esc); + + lwsac_free(&ac); + + return lws_container_of(ot.head, + sai_task_t, list); + } + } + + } lws_end_foreach_dll(p); + + lwsl_debug("%s: no free tasks matching '%s'\n", __func__, esc); + +bail: + lwsac_free(&ac); + + return NULL; +} + +int +saim_task_cancel(struct vhd *vhd, const char *task_uuid) +{ + sai_cancel_t *can; + + /* + * For every pss that we have from builders... + */ + lws_start_foreach_dll(struct lws_dll2 *, p, vhd->builders.head) { + struct pss *pss = lws_container_of(p, struct pss, same); + + + /* + * ... queue the task cancel message + */ + can = malloc(sizeof *can); + if (!can) + return -1; + memset(can, 0, sizeof(*can)); + + strncpy(can->task_uuid, task_uuid, sizeof(can->task_uuid)); + + lws_dll2_add_tail(&can->list, &pss->task_cancel_owner); + + lws_callback_on_writable(pss->wsi); + + } lws_end_foreach_dll(p); + + return 0; +} + +/* + * Keep the task record itself, but remove all logs and artifacts related to + * it and reset the task state back to WAITING. + */ + +int +saim_task_reset(struct vhd *vhd, const char *task_uuid) +{ + char esc[96], cmd[256], event_uuid[33]; + sqlite3 *pdb = NULL; + + if (!task_uuid[0]) + return 0; + + lwsl_notice("%s: received request to reset task %s\n", __func__, task_uuid); + + sai_task_uuid_to_event_uuid(event_uuid, task_uuid); + + if (saim_event_db_ensure_open(vhd, event_uuid, 0, &pdb)) { + lwsl_err("%s: unable to open event-specific database\n", + __func__); + + return -1; + } + + lws_sql_purify(esc, task_uuid, sizeof(esc)); + lws_snprintf(cmd, sizeof(cmd), "delete from logs where task_uuid='%s'", + esc); + + if (sqlite3_exec(pdb, cmd, NULL, NULL, NULL) != SQLITE_OK) { + saim_event_db_close(vhd, &pdb); + lwsl_err("%s: %s: %s: fail\n", __func__, cmd, + sqlite3_errmsg(vhd->master.pdb)); + return 1; + } + lws_snprintf(cmd, sizeof(cmd), "delete from artifacts where task_uuid='%s'", + esc); + + sqlite3_exec(pdb, cmd, NULL, NULL, NULL); + saim_event_db_close(vhd, &pdb); + + saim_set_task_state(vhd, NULL, NULL, task_uuid, SAIES_WAITING, 1, 1); + + saim_task_cancel(vhd, task_uuid); + + /* + * Reassess now if there's a builder we can match to a pending task + */ + + lws_sul_schedule(vhd->context, 0, &vhd->sul_central, saim_central_cb, 1); + + return 0; +} + +/* + * Look for any task on any event that needs building on platform_name + */ + +int +saim_allocate_task(struct vhd *vhd, struct pss *pss, sai_plat_t *cb, + const char *platform_name) +{ + char esc1[96], esc2[96]; + lws_dll2_owner_t o; + sai_task_t *task; + int n; + + if (cb->ongoing >= cb->instances) + return 1; + + /* + * Look for a task for this platform, on any event that needs building + */ + + task = (sai_task_t *)saim_task_pending(vhd, &pss->a.ac, platform_name); + + if (!task) + return 1; + + lwsl_notice("%s: %s: task found %s\n", __func__, platform_name, cb->name); + + /* yes, we will offer it to him */ + + if (saim_set_task_state(vhd, cb->name, cb->name, task->uuid, + SAIES_PASSED_TO_BUILDER, lws_now_secs(), 0)) + return -1; + + /* advance the task state first time we get logs */ + pss->mark_started = 1; + + /* let's get ahold of his event as well */ + + lws_sql_purify(esc1, task->event_uuid, sizeof(esc1)); + lws_snprintf(esc2, sizeof(esc2), " and uuid='%s'", esc1); + n = lws_struct_sq3_deserialize(vhd->master.pdb, esc2, NULL, + lsm_schema_sq3_map_event, &o, &pss->a.ac, + 0, 1); + if (n < 0 || !o.head) + return -1; + + task->one_event = lws_container_of(o.head, sai_event_t, list); + + task->master_name = pss->master_name; + task->repo_name = task->one_event->repo_name; + task->git_ref = task->one_event->ref; + task->git_hash = task->one_event->hash; + + /* + * add to master's estimate of builder's ongoing tasks... + */ + cb->ongoing++; + + lws_dll2_add_tail(&task->pending_assign_list, &pss->issue_task_owner); + lws_callback_on_writable(pss->wsi); + + return 0; +} diff --git a/src/master/m-ws-browser.c b/src/master/m-ws-browser.c new file mode 100644 index 0000000..cf16293 --- /dev/null +++ b/src/master/m-ws-browser.c @@ -0,0 +1,1077 @@ +/* + * Sai master - ./src/master/m-ws-browser.c + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * These are ws rx and tx handlers related to browser ws connections, on + * /broswe URLs. + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <assert.h> +#include <time.h> + +#include "m-private.h" + +/* + * For decoding specific event data request from browser + */ + + +typedef struct sai_browse_rx_evinfo { + char event_hash[65]; +} sai_browse_rx_evinfo_t; + +static lws_struct_map_t lsm_browser_evinfo[] = { + LSM_CARRAY (sai_browse_rx_evinfo_t, event_hash, "event_hash"), +}; + + +/* + * For decoding specific task data request from browser + */ + +typedef struct sai_browse_rx_taskinfo { + char task_hash[65]; + unsigned int log_start; + uint8_t logs; +} sai_browse_rx_taskinfo_t; + +static lws_struct_map_t lsm_browser_taskinfo[] = { + LSM_CARRAY (sai_browse_rx_taskinfo_t, task_hash, "task_hash"), + LSM_UNSIGNED (sai_browse_rx_taskinfo_t, logs, "logs"), +}; + +static lws_struct_map_t lsm_browser_taskreset[] = { + LSM_CARRAY (sai_browse_rx_evinfo_t, event_hash, "uuid"), + +}; + +/* + * Schema list so lws_struct can pick the right object to create based on the + * incoming schema name + */ + +static const lws_struct_map_t lsm_schema_json_map_bwsrx[] = { + LSM_SCHEMA (sai_browse_rx_taskinfo_t, NULL, lsm_browser_taskinfo, + "com.warmcat.sai.taskinfo"), + LSM_SCHEMA (sai_browse_rx_evinfo_t, NULL, lsm_browser_evinfo, + "com.warmcat.sai.eventinfo"), + LSM_SCHEMA (sai_browse_rx_evinfo_t, NULL, lsm_browser_taskreset, + /* shares struct */ "com.warmcat.sai.taskreset"), + LSM_SCHEMA (sai_browse_rx_evinfo_t, NULL, lsm_browser_taskreset, + /* shares struct */ "com.warmcat.sai.eventreset"), + LSM_SCHEMA (sai_browse_rx_evinfo_t, NULL, lsm_browser_taskreset, + /* shares struct */ "com.warmcat.sai.eventdelete"), + LSM_SCHEMA (sai_cancel_t, NULL, lsm_task_cancel, + "com.warmcat.sai.taskcan"), +}; + +enum { + SAIM_WS_BROWSER_RX_TASKINFO, + SAIM_WS_BROWSER_RX_EVENTINFO, + SAIM_WS_BROWSER_RX_TASKRESET, + SAIM_WS_BROWSER_RX_EVENTRESET, + SAIM_WS_BROWSER_RX_EVENTDELETE, + SAIM_WS_BROWSER_RX_TASKCANCEL +}; + + +/* + * For issuing combined task and event data back to browser + */ + +typedef struct sai_browse_taskreply { + const sai_event_t *event; + const sai_task_t *task; +} sai_browse_taskreply_t; + +static lws_struct_map_t lsm_taskreply[] = { + LSM_CHILD_PTR (sai_browse_taskreply_t, event, sai_event_t, NULL, + lsm_event, "e"), + LSM_CHILD_PTR (sai_browse_taskreply_t, task, sai_task_t, NULL, + lsm_task, "t"), +}; + +const lws_struct_map_t lsm_schema_json_map_taskreply[] = { + LSM_SCHEMA (sai_browse_taskreply_t, NULL, lsm_taskreply, + "com.warmcat.sai.taskinfo"), +}; + +enum sai_overview_state { + SOS_EVENT, + SOS_TASKS, +}; + +/* + * Ask for writeable cb on all browser connections subscribed to a particular + * task (so we can send them some more logs) + */ + +int +saim_subs_request_writeable(struct vhd *vhd, const char *task_uuid) +{ + lws_start_foreach_dll(struct lws_dll2 *, p, + vhd->master.subs_owner.head) { + struct pss *pss = lws_container_of(p, struct pss, subs_list); + + if (!strcmp(pss->sub_task_uuid, task_uuid)) + lws_callback_on_writable(pss->wsi); + + } lws_end_foreach_dll(p); + + return 0; +} + +static int +saim_pss_schedule_eventinfo(struct pss *pss, const char *event_uuid) +{ + char qu[80], esc[66]; + int n; + + /* + * Just collect the event struct into pss->query_owner to dump + */ + + lws_sql_purify(esc, event_uuid, sizeof(esc)); + lws_snprintf(qu, sizeof(qu), " and uuid='%s'", esc); + n = lws_struct_sq3_deserialize(pss->vhd->master.pdb, qu, NULL, + lsm_schema_sq3_map_event, + &pss->query_owner, &pss->query_ac, 0, 1); + if (n < 0 || !pss->query_owner.head) + return 1; + + pss->query_already_done = 1; + + mark_pending(pss, WSS_PREPARE_OVERVIEW); + mark_pending(pss, WSS_PREPARE_BUILDER_SUMMARY); + + return 0; +} + +static int +saim_pss_schedule_taskinfo(struct pss *pss, const char *task_uuid, int logsub) +{ + char qu[80], esc[66], event_uuid[33]; + sqlite3 *pdb = NULL; + lws_dll2_owner_t o; + int n; + + sai_task_uuid_to_event_uuid(event_uuid, task_uuid); + + /* open the event-specific database object */ + + if (saim_event_db_ensure_open(pss->vhd, event_uuid, 0, &pdb)) + /* no longer exists, nothing to do */ + return 0; + + /* + * get the related task object into its own ac... there might + * be a lot of related data, so we hold the ac in the pss for + * as long as needed to send it out + */ + + lws_sql_purify(esc, task_uuid, sizeof(esc)); + lws_snprintf(qu, sizeof(qu), " and uuid='%s'", esc); + n = lws_struct_sq3_deserialize(pdb, qu, NULL, lsm_schema_sq3_map_task, + &o, &pss->query_ac, 0, 1); + saim_event_db_close(pss->vhd, &pdb); + lwsl_info("%s: n %d, o.head %p\n", __func__, n, o.head); + if (n < 0 || !o.head) + return 1; + + pss->one_task = lws_container_of(o.head, sai_task_t, list); + + lwsl_info("%s: browser ws asked for task hash: %s, plat %s\n", + __func__, task_uuid, pss->one_task->platform); + + /* let the pss take over the task info ac and schedule sending */ + + lws_dll2_remove((struct lws_dll2 *)&pss->one_task->list); + + /* + * let's also get the event object the task relates to into + * its own event struct + */ + + lws_sql_purify(esc, event_uuid, sizeof(esc)); + lws_snprintf(qu, sizeof(qu), " and uuid='%s'", esc); + n = lws_struct_sq3_deserialize(pss->vhd->master.pdb, qu, NULL, + lsm_schema_sq3_map_event, &o, + &pss->query_ac, 0, 1); + if (n < 0 || !o.head) + return 1; + + /* does he want to subscribe to logs? */ + if (logsub) { + strcpy(pss->sub_task_uuid, pss->one_task->uuid); + lws_dll2_add_head(&pss->subs_list, + &pss->vhd->master.subs_owner); + pss->sub_timestamp = 0; /* where we got up to */ + lws_callback_on_writable(pss->wsi); + lwsl_info("%s: subscribed to logs for %s\n", __func__, + pss->sub_task_uuid); + } + + pss->one_event = lws_container_of(o.head, sai_event_t, list); + + mark_pending(pss, WSS_PREPARE_TASKINFO); + mark_pending(pss, WSS_PREPARE_BUILDER_SUMMARY); + + return 0; +} + +/* + * We need to schedule re-sending out task and event state to anyone subscribed + * to the task that changed or its associated event + */ + +int +saim_subs_task_state_change(struct vhd *vhd, const char *task_uuid) +{ + lws_start_foreach_dll(struct lws_dll2 *, p, + vhd->master.subs_owner.head) { + struct pss *pss = lws_container_of(p, struct pss, subs_list); + + if (!strcmp(pss->sub_task_uuid, task_uuid)) + saim_pss_schedule_taskinfo(pss, task_uuid, 0); + + } lws_end_foreach_dll(p); + + return 0; +} + + +/* + * browser has sent us a request for either overview, or data on a specific + * task + */ + +int +saim_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf, + size_t bl) +{ + sai_browse_rx_taskinfo_t *ti; + char qu[128], esc[96], *err; + sai_browse_rx_evinfo_t *ei; + sqlite3 *pdb = NULL; + lws_struct_args_t a; + lws_dll2_owner_t o; + sai_cancel_t *can; + int m, ret = -1; + + memset(&a, 0, sizeof(a)); + a.map_st[0] = lsm_schema_json_map_bwsrx; + a.map_entries_st[0] = LWS_ARRAY_SIZE(lsm_schema_json_map_bwsrx); + a.map_entries_st[1] = LWS_ARRAY_SIZE(lsm_schema_json_map_bwsrx); + a.ac_block_size = 128; + + lws_struct_json_init_parse(&pss->ctx, NULL, &a); + m = lejp_parse(&pss->ctx, (uint8_t *)buf, bl); + if (m < 0 || !a.dest) { + lwsl_hexdump_notice(buf, bl); + lwsl_notice("%s: notification JSON decode failed '%s'\n", + __func__, lejp_error_to_string(m)); + return m; + } + + /* + * Which object we ended up with depends on the schema that came in... + * a.top_schema_index is the index in lsm_schema_json_map_bwsrx it + * matched on + */ + + switch (a.top_schema_index) { + + case SAIM_WS_BROWSER_RX_TASKINFO: + ti = (sai_browse_rx_taskinfo_t *)a.dest; + + lwsl_info("%s: schema index %d, task hash %s\n", __func__, + a.top_schema_index, ti->task_hash); + + if (!ti->task_hash[0]) { + /* + * he's asking for the overview schema + */ + lws_dll2_add_head(&pss->same, &vhd->browsers); + mark_pending(pss, WSS_PREPARE_OVERVIEW); + mark_pending(pss, WSS_PREPARE_BUILDER_SUMMARY); + ret = 0; + goto bail; + } + + /* + * get the related task object into its own ac... there might + * be a lot of related data, so we hold the ac in the pss for + * as long as needed to send it out + */ + + if (saim_pss_schedule_taskinfo(pss, ti->task_hash, !!ti->logs)) + goto soft_error; + + return 0; + + case SAIM_WS_BROWSER_RX_EVENTINFO: + + ei = (sai_browse_rx_evinfo_t *)a.dest; + + if (saim_pss_schedule_eventinfo(pss, ei->event_hash)) + goto soft_error; + + lwsac_free(&a.ac); + + return 0; + + case SAIM_WS_BROWSER_RX_TASKRESET: + + /* + * User is asking us to reset / rebuild this task + */ + + ei = (sai_browse_rx_evinfo_t *)a.dest; + + if (saim_task_reset(vhd, ei->event_hash)) + goto soft_error; + + if (saim_pss_schedule_taskinfo(pss, ei->event_hash, 0)) + goto soft_error; + + lwsac_free(&a.ac); + + return 0; + + case SAIM_WS_BROWSER_RX_EVENTRESET: + + /* + * User is asking us to reset / rebuild every task in the event + */ + + ei = (sai_browse_rx_evinfo_t *)a.dest; + + lwsl_notice("%s: received request to reset event %s\n", + __func__, ei->event_hash); + + /* open the event-specific database object */ + + if (saim_event_db_ensure_open(pss->vhd, ei->event_hash, 0, &pdb)) { + lwsl_err("%s: unable to open event-specific database\n", + __func__); + /* + * hanging up isn't a good way to deal with browser + * tabs left open with a live connection to a + * now-deleted task... the page will reconnect endlessly + */ + goto soft_error; + } + + /* + * Retreive all the related structs into a dll2 list + */ + + lws_sql_purify(esc, ei->event_hash, sizeof(esc)); + + if (lws_struct_sq3_deserialize(pdb, NULL, NULL, + lsm_schema_sq3_map_task, + &o, &a.ac, 0, 999) >= 0) { + + sqlite3_exec(vhd->master.pdb, "BEGIN TRANSACTION", + NULL, NULL, &err); + + /* + * Walk the results list resetting all the tasks + */ + + lws_start_foreach_dll(struct lws_dll2 *, p, o.head) { + sai_task_t *t = lws_container_of(p, sai_task_t, + list); + + saim_task_reset(vhd, t->uuid); + + } lws_end_foreach_dll(p); + + sqlite3_exec(vhd->master.pdb, "END TRANSACTION", + NULL, NULL, &err); + } + + saim_event_db_close(vhd, &pdb); + lwsac_free(&a.ac); + + return 0; + + case SAIM_WS_BROWSER_RX_EVENTDELETE: + /* + * User is asking us to delete the whole event + */ + + ei = (sai_browse_rx_evinfo_t *)a.dest; + + lwsl_notice("%s: received request to delete event %s\n", + __func__, ei->event_hash); + + /* open the event-specific database object */ + + if (saim_event_db_ensure_open(pss->vhd, ei->event_hash, 0, &pdb)) { + lwsl_notice("%s: unable to open event-specific database\n", + __func__); + /* + * hanging up isn't a good way to deal with browser + * tabs left open with a live connection to a + * now-deleted task... the page will reconnect endlessly + */ + + goto soft_error; + } + + /* + * Retreive all the related structs into a dll2 list + */ + + lws_sql_purify(esc, ei->event_hash, sizeof(esc)); + + if (lws_struct_sq3_deserialize(pdb, NULL, NULL, + lsm_schema_sq3_map_task, + &o, &a.ac, 0, 999) >= 0) { + + sqlite3_exec(vhd->master.pdb, "BEGIN TRANSACTION", + NULL, NULL, &err); + + /* + * Walk the results list cancelling all the tasks + * that look like they might be ongoing + */ + + lws_start_foreach_dll(struct lws_dll2 *, p, o.head) { + sai_task_t *t = lws_container_of(p, sai_task_t, + list); + + if (t->state != SAIES_WAITING && + t->state != SAIES_SUCCESS && + t->state != SAIES_FAIL && + t->state != SAIES_CANCELLED) + saim_task_cancel(vhd, t->uuid); + + } lws_end_foreach_dll(p); + + sqlite3_exec(vhd->master.pdb, "END TRANSACTION", + NULL, NULL, &err); + } + + saim_event_db_close(vhd, &pdb); + + /* delete the event iself */ + + lws_sql_purify(esc, ei->event_hash, sizeof(esc)); + lws_snprintf(qu, sizeof(qu), "delete from events where uuid='%s'", + esc); + sqlite3_exec(vhd->master.pdb, qu, NULL, NULL, &err); + + /* remove the event-specific database */ + + saim_event_db_delete_database(vhd, ei->event_hash); + + ret = 0; + lwsac_free(&a.ac); + mark_pending(pss, WSS_PREPARE_OVERVIEW); + mark_pending(pss, WSS_PREPARE_BUILDER_SUMMARY); + + goto bail;; + + case SAIM_WS_BROWSER_RX_TASKCANCEL: + /* + * Browser is informing us of task's STOP button clicked, we + * need to inform any builder that might be building it + */ + can = (sai_cancel_t *)a.dest; + + lwsl_notice("%s: received request to cancel task %s\n", + __func__, can->task_uuid); + + saim_task_cancel(vhd, can->task_uuid); + break; + + default: + assert(0); + break; + } + + ret = 0; + +bail: + lwsac_free(&a.ac); + + return ret; + +soft_error: + lwsac_free(&a.ac); + + return 0; +} + + +/* + * We're sending something on a browser ws connection. Returning nonzero from + * here drops the connection, necessary if we fail partway through a message + * but undesirable if a browser tab will keep reconnecting and asking for the + * same, no-longer-existant thing. + */ + +int +saim_ws_json_tx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf, size_t bl) +{ + uint8_t *start = buf + LWS_PRE, *p = start, *end = p + bl - LWS_PRE - 1; + int n, flags = LWS_WRITE_TEXT, first = 0, iu; + sai_browse_taskreply_t task_reply; + lws_dll2_owner_t task_owner; + lws_struct_serialize_t *js; + char esc[256], filt[128]; + char event_uuid[33]; + sqlite3 *pdb = NULL; + sai_event_t *e; + sai_task_t *t; + char any; + size_t w; + +again: + + start = buf + LWS_PRE; + p = start; + end = p + bl - LWS_PRE - 1; + flags = LWS_WRITE_TEXT; + first = 0; + + // lwsl_notice("%s: send_state %d, pss %p, wsi %p\n", __func__, + // pss->send_state, pss, pss->wsi); + + switch (pss->send_state) { + case WSS_IDLE: + + /* + * Anything from a task log he's subscribed to? + */ + + if (pss->subs_list.owner) { + + lws_snprintf(esc, sizeof(esc), + "and task_uuid='%s' and timestamp > %llu", + pss->sub_task_uuid, + (unsigned long long)pss->sub_timestamp); + + /* + * For efficiency, let's try to grab the next 100 at + * once from sqlite and work our way through sending + * them + */ + + if (pss->log_cache_index == pss->log_cache_size) { + int sr; + + sai_task_uuid_to_event_uuid(event_uuid, + pss->sub_task_uuid); + + lws_dll2_owner_clear(&task_owner); + lwsac_free(&pss->logs_ac); + + lwsl_info("%s: collecting logs %s\n", + __func__, esc); + + if (saim_event_db_ensure_open(vhd, event_uuid, 0, + &pdb)) { + lwsl_notice("%s: unable to open event-specific database\n", + __func__); + + return 0; + } + + sr = lws_struct_sq3_deserialize(pdb, esc, + "uid,timestamp ", + lsm_schema_sq3_map_log, + &pss->query_owner, + &pss->logs_ac, 0, 100); + + saim_event_db_close(vhd, &pdb); + + if (sr) { + + lwsl_err("%s: subs failed\n", __func__); + + return 0; + } + + pss->log_cache_index = 0; + pss->log_cache_size = pss->query_owner.count; + } + + if (pss->log_cache_index < pss->log_cache_size) { + sai_log_t *log = lws_container_of( + pss->query_owner.head, + sai_log_t, list); + + lws_dll2_remove(&log->list); + pss->log_cache_index++; + + /* + * Turn it back into JSON so we can give it to + * the browser + */ + + js = lws_struct_json_serialize_create( + lsm_schema_json_map_log, 1, 0, log); + if (!js) { + lwsl_notice("%s: json ser fail\n", __func__); + return 0; + } + + n = lws_struct_json_serialize(js, p, end - p, &w); + lws_struct_json_serialize_destroy(&js); + if (n == LSJS_RESULT_ERROR) { + lwsl_notice("%s: json ser error\n", __func__); + return 0; + } + + p += w; + first = 1; + pss->walk = NULL; + + /* + * Record that this was the most recent log we + * saw so far + */ + pss->sub_timestamp = log->timestamp; + goto send_it; + } + } + + if (!pss->pending) + return 0; + + for (n = 0; n < 31; n++) { + if (pss->pending & (1 << n)) { + pss->pending &= ~(1 << n); + pss->send_state = n; + goto again; + } + } + return 0; + + case WSS_PREPARE_OVERVIEW: + + filt[0] = '\0'; + if (pss->specific_project[0]) { + lws_sql_purify(esc, pss->specific_project, sizeof(esc) - 1); + lws_snprintf(filt, sizeof(filt), " and repo_name=\"%s\"", esc); + } + pss->wants_event_updates = 1; + if (!pss->query_already_done && + lws_struct_sq3_deserialize(vhd->master.pdb, + filt[0] ? filt : NULL, "created ", + lsm_schema_sq3_map_event, &pss->query_owner, + &pss->query_ac, 0, -8)) { + lwsl_notice("%s: OVERVIEW 2 failed\n", __func__); + + pss->send_state = WSS_IDLE; + lwsac_free(&pss->task_ac); + lwsac_free(&pss->query_ac); + + return 0; + } + + pss->query_already_done = 0; + + /* + * we get zero or more sai_event_t laid out in pss->query_ac, + * and listed in pss->query_owner + */ + + lwsl_debug("%s: WSS_PREPARE_OVERVIEW: %d results %p\n", + __func__, pss->query_owner.count, pss->query_ac); + + p += lws_snprintf((char *)p, end - p, + "{\"schema\":\"sai.warmcat.com.overview\"," + " \"alang\":\"%s\"," + "\"overview\":[", + lws_json_purify(esc, pss->alang, sizeof(esc) - 1, &iu)); + + if (pss->specificity) + pss->walk = lws_dll2_get_head(&pss->query_owner); + else + pss->walk = lws_dll2_get_tail(&pss->query_owner); + pss->subsequent = 0; + first = 1; + + pss->send_state = WSS_SEND_OVERVIEW; + + if (!pss->query_owner.count) + goto so_finish; + + /* fallthru */ + + case WSS_SEND_OVERVIEW: + + if (pss->ovstate == SOS_TASKS) + goto enum_tasks; + + any = 0; + while (end - p > 2048 && pss->walk && + pss->send_state == WSS_SEND_OVERVIEW) { + + e = lws_container_of(pss->walk, sai_event_t, list); + + if (pss->specificity) { + //lwsl_notice("%s %s %s\n", e->hash, e->ref, pss->specific); + if (strcmp(e->hash, pss->specific) && + strcmp(e->ref, pss->specific)) { + pss->walk = pss->walk->next; + continue; + } + //lwsl_notice("%s: match\n", __func__); + any = 1; + } + + js = lws_struct_json_serialize_create( + lsm_schema_json_map_event, + LWS_ARRAY_SIZE(lsm_schema_json_map_event), 0, e); + if (!js) { + lwsl_err("%s: json ser fail\n", __func__); + return 1; + } + if (pss->subsequent) + *p++ = ','; + pss->subsequent = 1; + + p += lws_snprintf((char *)p, end - p, "{\"e\":"); + + n = lws_struct_json_serialize(js, p, end - p, &w); + lws_struct_json_serialize_destroy(&js); + switch (n) { + case LSJS_RESULT_ERROR: + pss->send_state = WSS_IDLE; + lwsl_err("%s: json ser error\n", __func__); + return 1; + + case LSJS_RESULT_FINISH: + case LSJS_RESULT_CONTINUE: + p += w; + pss->ovstate = SOS_TASKS; + pss->task_index = 0; + p += lws_snprintf((char *)p, end - p, ", \"t\":["); + goto enum_tasks; + } + } + if (!any) { + pss->send_state = WSS_IDLE; + lwsac_free(&pss->task_ac); + lwsac_free(&pss->query_ac); + + return 0; + } + break; + +enum_tasks: + /* + * Enumerate the tasks associated with this event... we will + * come back here as often as needed to dump all the tasks + */ + + e = lws_container_of(pss->walk, sai_event_t, list); + + do { + + if (saim_event_db_ensure_open(vhd, e->uuid, 0, &pdb)) { + lwsl_err("%s: unable to open event-specific database\n", + __func__); + + break; + } + + lws_dll2_owner_clear(&task_owner); + if (lws_struct_sq3_deserialize(pdb, NULL, NULL, + lsm_schema_sq3_map_task, &task_owner, + &pss->task_ac, pss->task_index, 1)) { + lwsl_err("%s: OVERVIEW 1 failed\n", __func__); + lwsac_free(&pss->task_ac); + saim_event_db_close(vhd, &pdb); + + break; + } + saim_event_db_close(vhd, &pdb); + + if (!task_owner.count) + break; + + if (pss->task_index) + *p++ = ','; + + /* + * We don't want to send everyone the artifact nonces... + * the up nonce is a key for uploading artifacts on to + * this task, it should only be stored in the master db + * and sent to the builder to use. + * + * The down nonce is used in generated links, but still + * you should have to acquire such a link via whatever + * auth rather than be able to cook them up yourself + * from knowing the task uuid. + */ + + t = (sai_task_t *)task_owner.head; + t->art_up_nonce[0] = '\0'; + t->art_down_nonce[0] = '\0'; + + /* only one in it at a time */ + t = lws_container_of(task_owner.head, sai_task_t, list); + + js = lws_struct_json_serialize_create( + lsm_schema_json_map_task, + LWS_ARRAY_SIZE(lsm_schema_json_map_task), 0, t); + + n = lws_struct_json_serialize(js, p, end - p, &w); + lws_struct_json_serialize_destroy(&js); + lwsac_free(&pss->task_ac); + p += w; + + pss->task_index++; + } while ((end - p > 2048) && task_owner.count); + + if (task_owner.count) + /* may be more left to do */ + break; + + /* none left to do, go back up a level */ + + p += lws_snprintf((char *)p, end - p, "]}"); + + pss->ovstate = SOS_EVENT; + if (pss->specificity) + pss->walk = pss->walk->next; + else + pss->walk = pss->walk->prev; + if (!pss->walk || pss->specificity) { + while (pss->walk) + pss->walk = pss->walk->next; + goto so_finish; + } + break; + +so_finish: + p += lws_snprintf((char *)p, end - p, "]}"); + pss->send_state = WSS_IDLE; + lwsac_free(&pss->task_ac); + lwsac_free(&pss->query_ac); + break; + + case WSS_PREPARE_BUILDER_SUMMARY: + p += lws_snprintf((char *)p, end - p, + "{\"schema\":\"com.warmcat.sai.builders\"," + " \"alang\":\"%s\"," + "\"builders\":[", + lws_sql_purify(esc, pss->alang, sizeof(esc) - 1)); + + pss->walk = lws_dll2_get_head(&vhd->master.builder_owner); + pss->subsequent = 0; + pss->send_state = WSS_SEND_BUILDER_SUMMARY; + first = 1; + + /* fallthru */ + + case WSS_SEND_BUILDER_SUMMARY: + if (!pss->walk) + goto b_finish; + + /* + * We're going to send the browser some JSON about all the + * builders / platforms we feel are connected to us + */ + + while (end - p > 512 && pss->walk && + pss->send_state == WSS_SEND_BUILDER_SUMMARY) { + + sai_plat_t *b = lws_container_of(pss->walk, sai_plat_t, + sai_plat_list); + + js = lws_struct_json_serialize_create( + lsm_schema_map_plat_simple, + LWS_ARRAY_SIZE(lsm_schema_map_plat_simple), + 0, b); + if (!js) + return 1; + if (pss->subsequent) + *p++ = ','; + pss->subsequent = 1; + + switch (lws_struct_json_serialize(js, p, end - p, &w)) { + case LSJS_RESULT_ERROR: + lws_struct_json_serialize_destroy(&js); + pss->send_state = WSS_IDLE; + return 1; + case LSJS_RESULT_FINISH: + p += w; + lws_struct_json_serialize_destroy(&js); + pss->walk = pss->walk->next; + if (!pss->walk) + goto b_finish; + break; + + case LSJS_RESULT_CONTINUE: + p += w; + lws_struct_json_serialize_destroy(&js); + pss->walk = pss->walk->next; + if (!pss->walk) + goto b_finish; + break; + } + } + break; +b_finish: + p += lws_snprintf((char *)p, end - p, "]}"); + pss->send_state = WSS_IDLE; +// lwsac_free(&pss->query_ac); + lwsac_free(&pss->task_ac); + break; + + + case WSS_PREPARE_TASKINFO: + /* + * We're sending a browser the specific task info that he + * asked for. + * + * We already got the task struct out of the db in .one_task + * (all in .query_ac) + */ + + task_reply.event = pss->one_event; + task_reply.task = pss->one_task; + + js = lws_struct_json_serialize_create(lsm_schema_json_map_taskreply, + LWS_ARRAY_SIZE(lsm_schema_json_map_taskreply), + 0, &task_reply); + if (!js) + return 1; + + n = lws_struct_json_serialize(js, p, end - p, &w); + lws_struct_json_serialize_destroy(&js); + + /* + * Let's also try to fetch any artifacts into pss->aft_owner... + * no db or no artifacts can also be a normal situation... + */ + + if (pss->one_task) { + + sai_task_uuid_to_event_uuid(event_uuid, pss->one_task->uuid); + + lwsl_notice("%s: ---------------- event uuid '%s'\n", __func__, + event_uuid); + + lws_dll2_owner_clear(&pss->aft_owner); + if (!saim_event_db_ensure_open(vhd, event_uuid, 0, &pdb)) { + + lws_snprintf(filt, sizeof(filt), " and (task_uuid == '%s')", + pss->one_task->uuid); + + lwsl_notice("%s: ---------------- %s\n", __func__, filt); + + if (lws_struct_sq3_deserialize(pdb, filt, NULL, + lsm_schema_sq3_map_artifact, + &pss->aft_owner, + &pss->task_ac, 0, 10)) { + lwsl_err("%s: get afcts failed\n", __func__); + } + saim_event_db_close(vhd, &pdb); + } + } + + first = 1; + pss->walk = NULL; + pss->send_state = WSS_SEND_ARTIFACT_INFO; + if (!pss->aft_owner.head) { + lwsl_notice("%s: ---------------- no artifacts\n", __func__); + /* there's no artifact stuff to do */ + pss->send_state = WSS_IDLE; + lwsac_free(&pss->query_ac); + } + pss->one_task = NULL; + if (n == LSJS_RESULT_ERROR) { + lwsl_notice("%s: taskinfo: error generating json\n", __func__); + return 1; + } + p += w; + if (!lws_ptr_diff(p, start)) { + lwsl_notice("%s: taskinfo: empty json\n", __func__); + return 0; + } + break; + + case WSS_SEND_ARTIFACT_INFO: + if (pss->aft_owner.head) { + sai_artifact_t *aft = (sai_artifact_t *)pss->aft_owner.head; + + lws_dll2_remove(&aft->list); + + /* we don't want to disclose this to browsers */ + aft->artifact_up_nonce[0] = '\0'; + + js = lws_struct_json_serialize_create(lsm_schema_json_map_artifact, + LWS_ARRAY_SIZE(lsm_schema_json_map_artifact), + 0, aft); + if (!js) { + lwsl_err("%s ----------------- failed to render artifact json\n", __func__); + return 1; + } + + n = lws_struct_json_serialize(js, p, end - p, &w); + lws_struct_json_serialize_destroy(&js); + if (n == LSJS_RESULT_ERROR) { + lwsl_notice("%s: taskinfo: ---------- error generating json\n", __func__); + return 1; + } + first = 1; + p += w; + lwsl_warn("%s: --------------------- %.*s\n", __func__, (int)w, start); + } + + if (!pss->aft_owner.head) { + pss->send_state = WSS_IDLE; + lwsac_free(&pss->query_ac); + } + break; + + default: + lwsl_err("%s: pss state %d\n", __func__, pss->send_state); + return 0; + } + +send_it: + flags = lws_write_ws_flags(LWS_WRITE_TEXT, first, !pss->walk); + + if (lws_write(pss->wsi, start, p - start, flags) < 0) + return -1; + + /* + * We get a bad ratio of reads to write when the builder spams us + * with rx... we have to try to clear as much as we can in one go. + */ + + if (!lws_send_pipe_choked(pss->wsi)) + goto again; + + lws_callback_on_writable(pss->wsi); + + return 0; +} diff --git a/src/master/m-ws-builder.c b/src/master/m-ws-builder.c new file mode 100644 index 0000000..0b717d0 --- /dev/null +++ b/src/master/m-ws-builder.c @@ -0,0 +1,728 @@ +/* + * Sai master - ./src/master/ws-json-rx.c + * + * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * These are ws rx and tx handlers related to builder ws connections, on + * /builder + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <time.h> + +#include "m-private.h" + +enum sai_overview_state { + SOS_EVENT, + SOS_TASKS, +}; + +typedef struct saim_logcache_pertask { + lws_dll2_t list; /* vhd->tasklog_cache is the owner */ + char uuid[65]; + lws_dll2_owner_t cache; /* sai_log_t */ +} saim_logcache_pertask_t; + +/* + * The Schema that may be sent to us by a builder + * + * Artifacts are sent on secondary SS connections so they don't block ongoing + * log delivery etc. The JSON is immediately followed by binary data to the + * length told in the JSON. + */ + +static const lws_struct_map_t lsm_schema_map_ba[] = { + LSM_SCHEMA_DLL2 (sai_plat_owner_t, plat_owner, NULL, lsm_plat_list, + "com-warmcat-sai-ba"), + LSM_SCHEMA (sai_log_t, NULL, lsm_log, + "com-warmcat-sai-logs"), + LSM_SCHEMA (sai_event_t, NULL, lsm_task_rej, + "com.warmcat.sai.taskrej"), + LSM_SCHEMA (sai_artifact_t, NULL, lsm_artifact, + "com-warmcat-sai-artifact"), +}; + +enum { + SAIM_WSSCH_BUILDER_PLATS, + SAIM_WSSCH_BUILDER_LOGS, + SAIM_WSSCH_BUILDER_TASKREJ, + SAIM_WSSCH_BUILDER_ARTIFACT +}; + +static void +saim_dump_logs_to_db(lws_sorted_usec_list_t *sul) +{ + struct vhd *vhd = lws_container_of(sul, struct vhd, sul_logcache); + saim_logcache_pertask_t *lcpt; + char event_uuid[33]; + sqlite3 *pdb = NULL; + sai_log_t *hlog; + char *err; + + /* + * for each task that acquired logs in the interval + */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, + vhd->tasklog_cache.head) { + lcpt = lws_container_of(p, saim_logcache_pertask_t, list); + + sai_task_uuid_to_event_uuid(event_uuid, lcpt->uuid); + + if (!saim_event_db_ensure_open(vhd, event_uuid, 0, &pdb)) { + + /* + * Empty the task-specific log cache into the event- + * specific db for the task in one go, this is much + * more efficient + */ + + sqlite3_exec(pdb, "BEGIN TRANSACTION", NULL, NULL, &err); + + lws_struct_sq3_serialize(pdb, lsm_schema_sq3_map_log, + &lcpt->cache, 0); + + sqlite3_exec(pdb, "END TRANSACTION", NULL, NULL, &err); + saim_event_db_close(vhd, &pdb); + + } else + lwsl_err("%s: unable to open event-specific database\n", + __func__); + + /* + * Destroy the logs in the task cache and the task cache + */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, pq, pq1, + lcpt->cache.head) { + hlog = lws_container_of(pq, sai_log_t, list); + lws_dll2_remove(&hlog->list); + free(hlog); + } lws_end_foreach_dll_safe(pq, pq1); + + /* + * Inform anybody who's looking at this task's logs that + * something changed + */ + saim_subs_request_writeable(vhd, lcpt->uuid); + + /* + * Destroy the whole task-specific cache, it will regenerate + * if more logs come for it + */ + + lws_dll2_remove(&lcpt->list); + free(lcpt); + + } lws_end_foreach_dll_safe(p, p1); + +} + +/* + * We're going to stash these logs on a per-task list, and deal with them + * inside a single trasaction per task efficiently on a timer. + */ + +static void +saim_log_to_db(struct vhd *vhd, sai_log_t *log) +{ + saim_logcache_pertask_t *lcpt = NULL; + sai_log_t *hlog; + + /* + * find the pertask if one exists + */ + + lws_start_foreach_dll(struct lws_dll2 *, p, vhd->tasklog_cache.head) { + lcpt = lws_container_of(p, saim_logcache_pertask_t, list); + + if (!strcmp(lcpt->uuid, log->task_uuid)) + break; + lcpt = NULL; + + } lws_end_foreach_dll(p); + + if (!lcpt) { + /* + * Create a pertask and add it to the vhd list of them + */ + lcpt = malloc(sizeof(*lcpt)); + memset(lcpt, 0, sizeof(*lcpt)); + lws_strncpy(lcpt->uuid, log->task_uuid, sizeof(lcpt->uuid)); + lws_dll2_add_tail(&lcpt->list, &vhd->tasklog_cache); + } + + hlog = malloc(sizeof(*hlog) + log->len + strlen(log->log) + 1); + if (!hlog) + return; + + *hlog = *log; + memset(&hlog->list, 0, sizeof(hlog->list)); + memcpy(&hlog[1], log->log, strlen(log->log) + 1); + hlog->log = (char *)&hlog[1]; + + /* + * add our log copy to the task-specific cache + */ + + lws_dll2_add_tail(&hlog->list, &lcpt->cache); + + if (!vhd->sul_logcache.list.owner) + /* if not already scheduled, schedule it for 250ms */ + + lws_sul_schedule(vhd->context, 0, &vhd->sul_logcache, + saim_dump_logs_to_db, 250 * LWS_US_PER_MS); +} + +static sai_plat_t * +saim_builder_from_uuid(struct vhd *vhd, const char *hostname) +{ + lws_start_foreach_dll(struct lws_dll2 *, p, + vhd->master.builder_owner.head) { + sai_plat_t *cb = lws_container_of(p, sai_plat_t, + sai_plat_list); + + if (!strcmp(hostname, cb->name)) + return cb; + + } lws_end_foreach_dll(p); + + return NULL; +} + +int +sai_sql3_get_uint64_cb(void *user, int cols, char **values, char **name) +{ + uint64_t *pui = (uint64_t *)user; + + *pui = (uint64_t)atoll(values[0]); + + return 0; +} + +/* + * Master received a communication from a builder + */ + +int +saim_ws_json_rx_builder(struct vhd *vhd, struct pss *pss, uint8_t *buf, size_t bl) +{ + char event_uuid[33], s[128], esc[96]; + struct lwsac *ac = NULL; + sai_plat_t *build, *cb; + sai_rejection_t *rej; + lws_dll2_owner_t o; + sai_artifact_t *ap; + sai_task_t *task; + sai_log_t *log; + uint64_t rid; + int n, m; + + /* + * use the schema name on the incoming JSON to decide what kind of + * structure to instantiate + * + * We may have: + * + * - just received a fragment of the whole JSON + * + * - received the JSON and be handling appeneded blob data + */ + + if (pss->bulk_binary_data) + goto handle; + + if (!pss->frag) { + memset(&pss->a, 0, sizeof(pss->a)); + pss->a.map_st[0] = lsm_schema_map_ba; + pss->a.map_entries_st[0] = LWS_ARRAY_SIZE(lsm_schema_map_ba); + pss->a.ac_block_size = 4096; + + lws_struct_json_init_parse(&pss->ctx, NULL, &pss->a); + } else + pss->frag = 0; + + m = lejp_parse(&pss->ctx, (uint8_t *)buf, bl); + + /* + * returns negative, or unused amount... for us, we either had a + * (negative) error, had LEJP_CONTINUE, or if 0/positive, finished + */ + if (m < 0 && m != LEJP_CONTINUE) { + /* an explicit error */ + lwsl_hexdump_err(buf, bl); + lwsl_err("%s: rx JSON decode failed '%s', %d, %s, %s, %d\n", + __func__, lejp_error_to_string(m), m, + pss->ctx.path, pss->ctx.buf, pss->ctx.npos); + return 1; + } + + if (m == LEJP_CONTINUE) { + pss->frag = 1; + return 0; + } + + if (!pss->a.dest) { + lwsl_err("%s: json decode didn't make an object\n", __func__); + return 1; + } + +handle: + switch (pss->a.top_schema_index) { + case SAIM_WSSCH_BUILDER_PLATS: + + lwsl_hexdump_notice(buf, bl); + + /* + * builder is sending us an array of platforms it provides us + */ + + pss->u.o = (sai_plat_owner_t *)pss->a.dest; + + lwsl_notice("%s: seen platform list: count %d\n", __func__, + pss->u.o->plat_owner.count); + + lws_start_foreach_dll(struct lws_dll2 *, pb, + pss->u.o->plat_owner.head) { + build = lws_container_of(pb, sai_plat_t, sai_plat_list); + + lwsl_notice("%s: seeing plat %s\n", __func__, build->name); + + /* + * ... so is this one a new guy? + */ + + cb = saim_builder_from_uuid(vhd, build->name); + if (!cb) { + char *cp; + + /* + * We need to make a persistent, deep, copy of + * the (from JSON) builder object representing + * this client. + * + * "platform" is eg "linux-ubuntu-bionic-arm64" + * and "name" is "hostname.<platform>". + */ + + if (!build->name || !build->platform) { + lwsl_err("%s: missing build '%s'/hostname '%s'\n", + __func__, + build->name ? build->name : "null", + build->platform ? build->platform : "null"); + return -1; + } + + cb = malloc(sizeof(*cb) + + strlen(build->name) + 1 + + strlen(build->platform) + 1); + + memset(cb, 0, sizeof(*cb)); + cp = (char *)&cb[1]; + + memcpy(cp, build->name, strlen(build->name) + 1); + cb->name = cp; + cp += strlen(build->name) + 1; + + memcpy(cp, build->platform, strlen(build->platform) + 1); + cb->platform = cp; + cp += strlen(build->platform) + 1; + + cb->ongoing = build->ongoing; + cb->instances = build->instances; + + cb->wsi = pss->wsi; + + /* Then attach the copy to the master in the vhd + */ + lws_dll2_add_tail(&cb->sai_plat_list, + &vhd->master.builder_owner); + } + + /* + * Even if he's not new, we should use his updated info about + * builder load + */ + cb->ongoing = build->ongoing; + cb->instances = build->instances; + + lwsl_notice("%s: builder %s reports load %d/%d\n", + __func__, cb->name, cb->ongoing, + cb->instances); + + } lws_end_foreach_dll(pb); + + lwsac_free(&pss->a.ac); + + /* + * look if we should offer the builder a task, given the + * platforms he's offering + */ + + if (saim_allocate_task(vhd, pss, cb, cb->platform) < 0) + goto bail; + + break; + +bail: + lwsac_free(&pss->a.ac); + return -1; + + case SAIM_WSSCH_BUILDER_LOGS: + /* + * builder is sending us info about task logs + */ + + log = (sai_log_t *)pss->a.dest; + saim_log_to_db(vhd, log); + + if (pss->mark_started) { + pss->mark_started = 0; + pss->first_log_timestamp = log->timestamp; + if (saim_set_task_state(vhd, NULL, NULL, log->task_uuid, + SAIES_BEING_BUILT, 0, 0)) + goto bail; + } + + if (log->finished) { + /* + * We have reached the end of the logs for this task + */ + lwsl_info("%s: log->finished says 0x%x, dur %lluus\n", + __func__, log->finished, (unsigned long long)( + log->timestamp - pss->first_log_timestamp)); + if (log->finished & SAISPRF_EXIT) { + if ((log->finished & 0xff) == 0) + n = SAIES_SUCCESS; + else + n = SAIES_FAIL; + } else + if (log->finished & 8192) + n = SAIES_CANCELLED; + else + n = SAIES_FAIL; + + if (saim_set_task_state(vhd, NULL, NULL, log->task_uuid, + n, 0, log->timestamp - + pss->first_log_timestamp)) + goto bail; + } + + lwsac_free(&pss->a.ac); + + break; + + case SAIM_WSSCH_BUILDER_TASKREJ: + + /* + * builder is updating us about his status, and may be + * rejecting a task we tried to give him + */ + + rej = (sai_rejection_t *)pss->a.dest; + + cb = saim_builder_from_uuid(vhd, rej->host_platform); + if (!cb) { + lwsl_info("%s: unknown builder %s rejecting\n", + __func__, rej->host_platform); + break; + } + + /* update our info about builder state with reality */ + + cb->ongoing = rej->ongoing; + cb->instances = rej->limit; + + lwsl_notice("%s: builder %s reports load %d/%d (rej %s)\n", + __func__, cb->name, cb->ongoing, cb->instances, + rej->task_uuid[0] ? rej->task_uuid : "none"); + + if (rej->task_uuid[0]) + saim_task_reset(vhd, rej->task_uuid); + + break; + + case SAIM_WSSCH_BUILDER_ARTIFACT: + /* + * We get sent a JSON object immediately followed by binary + * data for the artifact. + * + * We place the binary data as a blob in the sql record in the + * artifact table. + */ + + lwsl_debug("%s: SAIM_WSSCH_BUILDER_ARTIFACT\n", __func__); + + if (!pss->bulk_binary_data) { + + ap = (sai_artifact_t *)pss->a.dest; + + sai_task_uuid_to_event_uuid(event_uuid, ap->task_uuid); + + /* + * Open the event-specific database object... the + * handle is closed when the stream closes for whatever + * reason. + */ + + if (saim_event_db_ensure_open(pss->vhd, event_uuid, 0, + &pss->pdb_artifact)) { + lwsl_err("%s: unable to open event-specific " + "database\n", __func__); + + return -1; + } + + /* + * Retreive the task object + */ + + lws_sql_purify(esc, ap->task_uuid, sizeof(esc)); + lws_snprintf(s, sizeof(s)," and uuid == \"%s\"", esc); + n = lws_struct_sq3_deserialize(pss->pdb_artifact, s, + NULL, lsm_schema_sq3_map_task, + &o, &ac, 0, 1); + if (n < 0 || !o.head) { + saim_event_db_close(vhd, &pss->pdb_artifact); + lwsl_notice("%s: no task of that id\n", __func__); + return -1; + } + + task = (sai_task_t *)o.head; + n = strcmp(task->art_up_nonce, ap->artifact_up_nonce); + + if (n) { + lwsl_err("%s: artifact nonce mismatch\n", + __func__); + goto afail; + } + + /* + * The task the sender is sending us an artifact for + * exists. The sender knows the random upload nonce + * for that task's artifacts. + * + * Create a random download nonce unrelated to the + * random upload nonce (so knowing the download one + * won't let you upload anything). + * + * Create the artifact's entry in the event-specific + * database + */ + + sai_uuid16_create(pss->vhd->context, + ap->artifact_down_nonce); + + lws_dll2_owner_clear(&o); + lws_dll2_add_head(&ap->list, &o); + + /* + * Create the task in event-specific database + */ + + if (lws_struct_sq3_serialize(pss->pdb_artifact, + lsm_schema_sq3_map_artifact, + &o, ap->uid)) { + lwsl_err("%s: failed artifact struct insert\n", + __func__); + + goto afail; + } + + /* + * recover the rowid + */ + + lws_snprintf(s, sizeof(s), + "select rowid from artifacts " + "where timestamp=%llu", + (unsigned long long)ap->timestamp); + + if (sqlite3_exec((sqlite3 *)pss->pdb_artifact, s, + sai_sql3_get_uint64_cb, &rid, NULL) != + SQLITE_OK) { + lwsl_err("%s: %s: %s: fail\n", __func__, s, + sqlite3_errmsg(pss->pdb_artifact)); + goto afail; + } + + /* + * Set the blob size on associated row + */ + + lws_snprintf(s, sizeof(s), + "update artifacts set blob=zeroblob(%llu) " + "where rowid=%llu", + (unsigned long long)ap->len, + (unsigned long long)rid); + + if (sqlite3_exec((sqlite3 *)pss->pdb_artifact, s, + NULL, NULL, NULL) != + SQLITE_OK) { + lwsl_err("%s: %s: %s: fail\n", __func__, s, + sqlite3_errmsg(pss->pdb_artifact)); + goto afail; + } + + /* + * Open a blob on the associated row... the blob handle + * is closed when this stream closes for whatever + * reason. + */ + + if (sqlite3_blob_open(pss->pdb_artifact, "main", + "artifacts", "blob", rid, 1, + &pss->blob_artifact) != SQLITE_OK) { + lwsl_err("%s: unable to open blob\n", __func__); + goto afail; + } + + /* + * First time around, m == number of bytes let in buf + * after JSON, (bl - m) offset + */ + pss->bulk_binary_data = 1; + pss->artifact_length = ap->len; + } else + m = bl; + + if (m) { + lwsl_notice("%s: blob write +%d, ofs %llu / %llu, len %d\n", + __func__, (int)(bl - m), + (unsigned long long)pss->artifact_offset, + (unsigned long long)pss->artifact_length, m); + if (sqlite3_blob_write(pss->blob_artifact, + (uint8_t *)buf + (bl - m), (int)m, + pss->artifact_offset)) { + lwsl_err("%s: writing blob failed\n", __func__); + goto afail; + } + + lws_set_timeout(pss->wsi, PENDING_TIMEOUT_HTTP_CONTENT, 5); + pss->artifact_offset += (int)m; + } + + if (pss->artifact_offset == pss->artifact_length) { + lwsl_notice("%s: blob upload finished\n", __func__); + + goto afail; + } + + } + + return 0; + +afail: + lwsac_free(&ac); + saim_event_db_close(vhd, &pss->pdb_artifact); + + return -1; +} + +/* + * We're sending something on a builder ws connection + */ + +int +saim_ws_json_tx_builder(struct vhd *vhd, struct pss *pss, uint8_t *buf, + size_t bl) +{ + uint8_t *start = buf + LWS_PRE, *p = start, *end = p + bl - LWS_PRE - 1; + int n, flags = LWS_WRITE_TEXT, first = 0; + lws_struct_serialize_t *js; + sai_task_t *task; + size_t w; + + if (pss->task_cancel_owner.head) { + /* + * Pending cancel message to send + */ + sai_cancel_t *c = lws_container_of(pss->task_cancel_owner.head, + sai_cancel_t, list); + + js = lws_struct_json_serialize_create(lsm_schema_json_map_can, + LWS_ARRAY_SIZE(lsm_schema_json_map_can), 0, c); + if (!js) + return 1; + + n = lws_struct_json_serialize(js, p, end - p, &w); + lws_struct_json_serialize_destroy(&js); + + lws_dll2_remove(&c->list); + free(c); + + first = 1; + pss->walk = NULL; + + goto send_json; + } + + if (!pss->issue_task_owner.count) + return 0; /* nothing to send */ + + /* + * We're sending a browser the specific task info that he + * asked for. + * + * We already got the task struct out of the db in .one_task + * (all in .query_ac) + */ + + task = lws_container_of(pss->issue_task_owner.head, sai_task_t, + pending_assign_list); + lws_dll2_remove(&task->pending_assign_list); + + js = lws_struct_json_serialize_create(lsm_schema_map_ta, + LWS_ARRAY_SIZE(lsm_schema_map_ta), + 0, task); + if (!js) + return 1; + + n = lws_struct_json_serialize(js, p, end - p, &w); + lws_struct_json_serialize_destroy(&js); + + first = 1; + pss->walk = NULL; + + //lwsac_free(&pss->query_ac); + +send_json: + p += w; + if (n == LSJS_RESULT_ERROR) { + lwsl_notice("%s: taskinfo: error generating json\n", + __func__); + return 1; + } + if (!lws_ptr_diff(p, start)) { + lwsl_notice("%s: taskinfo: empty json\n", __func__); + return 0; + } + + flags = lws_write_ws_flags(LWS_WRITE_TEXT, first, !pss->walk); + + lwsl_hexdump_notice(start, p - start); + + if (lws_write(pss->wsi, start, p - start, flags) < 0) + return -1; + + lws_callback_on_writable(pss->wsi); + + return 0; +} diff --git a/src/master/private.h b/src/master/private.h deleted file mode 100644 index 7ee6749..0000000 --- a/src/master/private.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Sai master definitions src/master/private.h - * - * Copyright (C) 2019 Andy Green <andy@warmcat.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation: - * version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -struct sai_master; - -enum sai_lease_states { - SOS_IDLE, - SOS_RESERVED, - SOS_CONFIRMED, - SOS_OCCUPIED, - SOS_GOING_DOWN -}; - -typedef struct sai_console { - struct lws_dll console_list; - - const char *name; - const char *path; - const char *mode; - int baudrate; -} sai_console_t; - -typedef struct sai_target { - struct lws_dll target_list; - struct lws_dll console_head; - - const char *name; - - enum sai_lease_states state; -} sai_target_t; - -typedef struct sai_nspawn { - struct lws_dll nspawn_list; - - const char *name; - - enum sai_lease_states state; -} sai_nspawn_t; - -typedef struct sai_builder { - struct lws_dll builder_list; - struct lws_dll target_head; - struct lws_dll nspawn_head; - - struct lws *wsi; - - char *hostname; -} sai_builder_t; - -typedef enum { - SJS_CLONING, - SJS_ASSIGNING, - SJS_WAITING, - SJS_DONE -} sai_job_state_t; - -typedef struct sai_job { - struct lws_dll jobs_list; - char reponame[64]; - char ref[64]; - char head[64]; - - time_t requested; - - sai_job_state_t state; - -} sai_job_t; - -typedef struct sai_master { - struct lws_dll builder_head; - - struct lws_dll pending_jobs_head; - struct lws_dll ongoing_jobs_head; -} sai_master_t; - - -extern struct lws_context * -sai_lws_context_from_json(const char *config_dir, - const struct lws_protocols **pprotocols); -extern const struct lws_protocols protocol_ws; diff --git a/src/master/sai-master.c b/src/master/sai-master.c deleted file mode 100644 index 138703c..0000000 --- a/src/master/sai-master.c +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Sai master - * - * Copyright (C) 2019 Andy Green <andy@warmcat.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation: - * version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#include <libwebsockets.h> -#include <string.h> -#include <signal.h> -#include <time.h> - -#include "private.h" - -static int interrupted; - -static const struct lws_protocols - *pprotocols[] = { &protocol_ws, NULL }; - -static void sigint_handler(int sig) -{ - interrupted = 1; -} - -int main(int argc, const char **argv) -{ - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; - struct lws_context *context; - const char *p, *conf = "/etc/sai/master"; - - signal(SIGINT, sigint_handler); - - if ((p = lws_cmdline_option(argc, argv, "-d"))) - logs = atoi(p); - - lws_set_log_level(logs, NULL); - lwsl_user("Sai Master - Copyright (C) 2019 Andy Green <andy@warmcat.com>\n"); - - if ((p = lws_cmdline_option(argc, argv, "-c"))) - conf = p; - - context = sai_lws_context_from_json(conf, pprotocols); - if (!context) { - lwsl_err("lws init failed\n"); - return 1; - } - - while (n >= 0 && !interrupted) - n = lws_service(context, 1000); - - lws_context_destroy(context); - - return 0; -} diff --git a/src/master/ws.c b/src/master/ws.c deleted file mode 100644 index 19aec34..0000000 --- a/src/master/ws.c +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Sai master - * - * Copyright (C) 2019 Andy Green <andy@warmcat.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation: - * version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - * - * The same ws interface is connected-to by builders (on path /builder), and - * provides the query transport for browsers (on path /browse). - */ - -#include <libwebsockets.h> -#include <string.h> -#include <signal.h> -#include <time.h> - -#include <git2.h> - -#include "private.h" - -typedef enum { - SWT_BUILDER, - SWT_BROWSE -} ws_type; - -struct pss { - ws_type type; -}; - -struct vhd { - struct lws_context *context; - struct lws_vhost *vhost; - - struct sai_master master; -}; - -/* - * {"schema":"com-warmcat-sai-builder-attach", - "hostname":"learn", "targets": [{"name":"optee-hikey-arm64"} -], -{"nspawns": []} - */ - - -static const char * const paths[] = { - "schema", - "hostname", - "nspawn-timeout", - - "targets[]", - "targets[].name", - - "nspawns[]", - "nspawns[].name", - "nspawns[].overlays[]", - "nspawns[].overlays[].name", -}; - -enum enum_paths { - SBAJP_SCHEMA, - SBAJP_HOSTNAME, - SBAJP_NSPAWN_TIMEOUT, - - SBAJP_TARGETS, - SBAJP_TARGETS_NAME, - - SBAJP_NSPAWNS, - SBAJP_NSPAWNS_NAME, - SBAJP_NSPAWNS_OVERLAYS, - SBAJP_NSPAWNS_OVERLAYS_NAME, -}; - -static int -sai_master_nspawn_destroy(struct lws_dll *d) -{ - sai_nspawn_t *n = lws_container_of(d, sai_nspawn_t, nspawn_list); - - lws_dll_remove(&n->nspawn_list); - free(n); - - return 0; -} - -static int -sai_master_console_destroy(struct lws_dll *d) -{ - sai_console_t *c = lws_container_of(d, sai_console_t, console_list); - - lws_dll_remove(&c->console_list); - free(c); - - return 0; -} - -static int -sai_master_target_destroy(struct lws_dll *d) -{ - sai_target_t *t = lws_container_of(d, sai_target_t, target_list); - - lws_dll_foreach_safe(&t->console_head, sai_master_console_destroy); - - free(t); - - return 0; -} - -static int -sai_master_builder_destroy(struct lws_dll *d) -{ - sai_builder_t *b = lws_container_of(d, sai_builder_t, builder_list); - - lws_dll_foreach_safe(&b->nspawn_head, sai_master_nspawn_destroy); - lws_dll_foreach_safe(&b->target_head, sai_master_target_destroy); - - if (b->hostname) - free(b->hostname); - - free(b); - - return 0; -} - -static int -sai_destroy_job(struct lws_dll *d) -{ - sai_job_t *job = lws_container_of(d, sai_job_t, jobs_list); - - lws_dll_remove(d); - free(job); - - return 0; -} - -static void -sai_master_master_destroy(struct sai_master *master) -{ - lws_dll_foreach_safe(&master->builder_head, sai_master_builder_destroy); - - lws_dll_foreach_safe(&master->pending_jobs_head, sai_destroy_job); - lws_dll_foreach_safe(&master->ongoing_jobs_head, sai_destroy_job); -} - -static int -callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, - void *in, size_t len) -{ - struct vhd *vhd = (struct vhd *)lws_protocol_vh_priv_get( - lws_get_vhost(wsi), lws_get_protocol(wsi)); - uint8_t buf[LWS_PRE + 2048], *start = &buf[LWS_PRE], *p = start, - *end = &buf[sizeof(buf) - LWS_PRE - 1]; - struct pss *pss = (struct pss *)user; - struct sai_job *job; - const char *ccp; - int n; - - (void)end; - (void)p; - (void)paths; - - switch (reason) { - case LWS_CALLBACK_PROTOCOL_INIT: - vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), - lws_get_protocol(wsi), - sizeof(struct vhd)); - if (!vhd) - return -1; - - vhd->context = lws_get_context(wsi); - vhd->vhost = lws_get_vhost(wsi); - break; - - case LWS_CALLBACK_PROTOCOL_DESTROY: - sai_master_master_destroy(&vhd->master); - break; - - case LWS_CALLBACK_HTTP: - - n = HTTP_STATUS_FORBIDDEN; - - if (strcmp((const char *)in, "/sai/update-hook")) { - lwsl_notice("%s: unknown path %s\n", __func__, - (const char *)in); - goto http_resp; - } - - job = malloc(sizeof(*job)); - if (!job) { - lwsl_err("%s: OOM\n", __func__); - - return 1; - } - - ccp = lws_get_urlarg_by_name(wsi, "rn=", (char *)start, - sizeof(job->reponame)); - if (!ccp || !*ccp) { - lwsl_notice("%s: missing rn=\n", __func__); - free(job); - goto http_resp; - } - lws_strncpy(job->reponame, ccp, sizeof(job->reponame)); - ccp = lws_get_urlarg_by_name(wsi, "ref=", (char *)start + 128, - sizeof(job->ref)); - if (!ccp || !*ccp) { - lwsl_notice("%s: missing ref=\n", __func__); - free(job); - goto http_resp; - } - lws_strncpy(job->ref, ccp, sizeof(job->ref)); - ccp = lws_get_urlarg_by_name(wsi, "head=", (char *)start + 256, - sizeof(job->head)); - if (!ccp || !*ccp) { - lwsl_notice("%s: missing ref=\n", __func__); - free(job); - goto http_resp; - } - lws_strncpy(job->head, ccp, sizeof(job->head)); - - job->requested = time(NULL); - - ccp = strrchr(job->ref + strlen(job->ref) - 1, '/'); - if (ccp) - ccp++; - - lwsl_user("%s: added job: %s %s %s\n", __func__, job->reponame, - ccp, job->head); - - { - git_clone_options opts; - git_repository *repo; - const char *url = "https://libwebsockets.org/repo/libwebsockets"; - - git_clone_init_options(&opts, GIT_CLONE_OPTIONS_VERSION); - opts.checkout_branch = ccp; - - if (git_clone(&repo, url, "/tmp", &opts)) { - lwsl_err("%s: unable to clone\n", __func__); - free(job); - goto http_resp; - } - - lwsl_user("%s: clone OK\n", __func__); - } - - lws_dll_add_front(&job->jobs_list, &vhd->master.pending_jobs_head); - n = HTTP_STATUS_OK; - - /* faillthru */ - -http_resp: - if (lws_add_http_header_status(wsi, n, &p, end)) - goto bail; - if (lws_add_http_header_content_length(wsi, 0, &p, end)) - goto bail; - if (lws_finalize_write_http_header(wsi, start, &p, end)) - goto bail; - goto try_to_reuse; - - case LWS_CALLBACK_ESTABLISHED: - - if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) { - if (lws_hdr_copy(wsi, (char *)start, 64, WSI_TOKEN_GET_URI) < 0) - return -1; - } else - if (lws_hdr_copy(wsi, (char *)start, 64, WSI_TOKEN_HTTP_COLON_PATH) < 0) - return -1; - - if (!strcmp((char *)start, "/browse")) { - n = SWT_BUILDER; - } else { - if (!strcmp((char *)start, "builder")) { - n = SWT_BROWSE; - } else { - lwsl_err("%s: unknown URL\n", __func__); - - return -1; - } - } - - pss->type = n; - break; - - case LWS_CALLBACK_RECEIVE: - lwsl_user("RECEIVE: %d\n", (int)len); - lwsl_user("%.*s\n", (int)len, (const char *)in); - break; - - default: - break; - } - - return lws_callback_http_dummy(wsi, reason, user, in, len); - -bail: - return 1; - -try_to_reuse: - if (lws_http_transaction_completed(wsi)) - return -1; - - return 0; -} - -const struct lws_protocols protocol_ws = - { "com-warmcat-sai", callback_ws, sizeof(struct pss), 0 };
Page fetched 0s ago, creation time: 81ms (vhost etag hits: 0%, cache hits: 0%)