diff --git a/build.conf b/build.conf index eaa7efd1..7cfb2d80 100644 --- a/build.conf +++ b/build.conf @@ -7,7 +7,7 @@ # PATCH version when you make backwards-compatible bug fixes. # # version must always start with "v" like: v0.1.45-rc -VERSION=v0.1.1 +VERSION=v0.1.2 # This must match the tags in the github repository # loading the actual path @@ -18,11 +18,11 @@ ROOT=$(pwd) # URL for the most recent version of this: https://dl.armbian.com/orangepiprime/archive/ ARMBIAN_DOWNLOAD_URL="https://dl.armbian.com/orangepiprime/archive/Armbian_20.02.1_Orangepiprime_stretch_current_5.4.20.7z" # Skywire release download for OS running on destination skyminer -SKYWIRE_DOWNLOAD_URL="https://github.com/SkycoinProject/skywire-mainnet/releases/download/v0.2.1/skywire-v0.2.1-linux-arm64.tar.gz" +SKYWIRE_DOWNLOAD_URL="https://github.com/SkycoinProject/skywire-mainnet/releases/download/v0.2.3/skywire-v0.2.3-linux-arm64.tar.gz" # Offset and sector size of the Armbian image for rootfs (found automatically if not set) IMG_OFFSET="" # 8192 IMG_SECTOR="" # 512 # how much will we increase the original image in MB -BASE_IMG_ADDED_SPACE=768 +BASE_IMG_ADDED_SPACE=512 # before: 768 diff --git a/build.sh b/build.sh index 9910662a..edd6e6bc 100755 --- a/build.sh +++ b/build.sh @@ -295,34 +295,36 @@ rootfs_check() # - Mount loop device prepare_base_image() { - # Armbian image is tight packed, and we need room for adding our - # bins, apps & configs, so we will make it bigger + # Armbian image is tight packed, and we need room for adding our + # bins, apps & configs, so we will make it bigger - # clean - info "Cleaning..." - rm -rf "${IMAGE_DIR:?}/*" &> /dev/null || true + # clean + info "Cleaning..." + rm -rf "${IMAGE_DIR:?}/*" &> /dev/null || true - # copy armbian image to base image location - info "Copying base image..." - cp "${PARTS_DIR}/armbian/${ARMBIAN_IMG}" "${BASE_IMG}" || return 1 + # copy armbian image to base image location + info "Copying base image..." + cp "${PARTS_DIR}/armbian/${ARMBIAN_IMG}" "${BASE_IMG}" || return 1 - # Add space to base image + # Add space to base image + if [[ "$BASE_IMG_ADDED_SPACE" -ne "0" ]]; then info "Adding ${BASE_IMG_ADDED_SPACE}MB of extra space to the image..." truncate -s +"${BASE_IMG_ADDED_SPACE}M" "${BASE_IMG}" echo ", +" | sfdisk -N1 "${BASE_IMG}" # add free space to the part 1 (sfdisk way) + fi - info "Setting up loop device..." - setup_loop || return 1 - rootfs_check || return 1 + info "Setting up loop device..." + setup_loop || return 1 + rootfs_check || return 1 - info "Resizing root fs..." - sudo resize2fs "${IMG_LOOP}" || return 1 - rootfs_check || return 1 + info "Resizing root fs..." + sudo resize2fs "${IMG_LOOP}" || return 1 + rootfs_check || return 1 - info "Mounting root fs to ${FS_MNT_POINT}..." - sudo mount -t auto "${IMG_LOOP}" "${FS_MNT_POINT}" -o loop,rw + info "Mounting root fs to ${FS_MNT_POINT}..." + sudo mount -t auto "${IMG_LOOP}" "${FS_MNT_POINT}" -o loop,rw - info "Done!" + info "Done!" } copy_to_img() @@ -330,8 +332,8 @@ copy_to_img() # Copy skywire bins info "Copying skywire bins..." sudo cp -rf "$PARTS_SKYWIRE_DIR"/bin/* "$FS_MNT_POINT"/usr/bin/ || return 1 - sudo cp "$ROOT"/static/skywire-startup "$FS_MNT_POINT"/usr/bin/ || return 1 - sudo chmod +x "$FS_MNT_POINT"/usr/bin/skywire-startup || return 1 + sudo cp "$ROOT"/static/skybian-firstrun "$FS_MNT_POINT"/usr/bin/ || return 1 + sudo chmod +x "$FS_MNT_POINT"/usr/bin/skybian-firstrun || return 1 # Copy skywire tools info "Copying skywire tools..." @@ -349,12 +351,12 @@ copy_to_img() # Copy systemd units info "Copying systemd unit services..." local SYSTEMD_DIR=${FS_MNT_POINT}/etc/systemd/system/ - sudo cp -f "${ROOT}/static/skywire-startup.service" "${SYSTEMD_DIR}" || return 1 + sudo cp -f "${ROOT}"/static/*.service "${SYSTEMD_DIR}" || return 1 info "Done!" } -# fix some defaults on armian to skywire defaults +# fix some defaults on armbian to skywire defaults chroot_actions() { # copy chroot scripts to root fs @@ -392,47 +394,47 @@ chroot_actions() # calculate md5, sha1 and compress calc_sums_compress() { - # change to final dest - cd "${FINAL_IMG_DIR}" || - (error "Failed to cd." && return 1) + # change to final dest + cd "${FINAL_IMG_DIR}" || + (error "Failed to cd." && return 1) + + # info + info "Calculating the md5sum for the image, this may take a while" + + # cycle for each one + for img in $(find -- *.img -maxdepth 1 -print0 | xargs --null) ; do + # MD5 + info "MD5 Sum for image: $img" + md5sum -b "${img}" > "${img}.md5" + + # sha1 + info "SHA1 Sum for image: $img" + sha1sum -b "${img}" > "${img}.sha1" + + # compress + info "Compressing, this will take a while..." + name=$(echo "${img}" | rev | cut -d '.' -f 2- | rev) + tar -cvf "${name}.tar" "${img}"* + xz -vzT0 "${name}.tar" + done - # info - info "Calculating the md5sum for the image, this may take a while" - - # cycle for each one - for img in $(find -- *.img -maxdepth 1 -print0 | xargs --null) ; do - # MD5 - info "MD5 Sum for image: $img" - md5sum -b "${img}" > "${img}.md5" - - # sha1 - info "SHA1 Sum for image: $img" - sha1sum -b "${img}" > "${img}.sha1" - - # compress - info "Compressing, this will take a while..." - name=$(echo "${img}" | rev | cut -d '.' -f 2- | rev) - tar -cvf "${name}.tar" "${img}"* - xz -vzT0 "${name}.tar" - done - - cd "${ROOT}" || return 1 - info "Done!" + cd "${ROOT}" || return 1 + info "Done!" } clean_image() { - sudo umount "${FS_MNT_POINT}/sys" - sudo umount "${FS_MNT_POINT}/proc" - sudo umount "${FS_MNT_POINT}/dev/pts" - sudo umount "${FS_MNT_POINT}/dev" + sudo umount "${FS_MNT_POINT}/sys" + sudo umount "${FS_MNT_POINT}/proc" + sudo umount "${FS_MNT_POINT}/dev/pts" + sudo umount "${FS_MNT_POINT}/dev" - sudo sync - sudo umount "${FS_MNT_POINT}" + sudo sync + sudo umount "${FS_MNT_POINT}" - sudo sync - # only do so if IMG_LOOP is set - [[ -n "${IMG_LOOP}" ]] && sudo losetup -d "${IMG_LOOP}" + sudo sync + # only do so if IMG_LOOP is set + [[ -n "${IMG_LOOP}" ]] && sudo losetup -d "${IMG_LOOP}" } clean_output_dir() @@ -452,46 +454,46 @@ clean_output_dir() # build disk build_disk() { - # move to correct dir - cd "${IMAGE_DIR}" || return 1 + # move to correct dir + cd "${IMAGE_DIR}" || return 1 - # final name - local NAME="Skybian-${VERSION}" + # final name + local NAME="Skybian-${VERSION}" - # info - info "Building image for ${NAME}" + # info + info "Building image for ${NAME}" - # force a FS sync - info "Forcing a fs rsync to umount the real fs" - sudo sync + # force a FS sync + info "Forcing a fs rsync to umount the real fs" + sudo sync - # umount the base image - info "Umount the fs" - sudo umount "${FS_MNT_POINT}" + # umount the base image + info "Umount the fs" + sudo umount "${FS_MNT_POINT}" - # check integrity & fix minor errors - rootfs_check + # check integrity & fix minor errors + rootfs_check - # TODO [TEST] - # shrink the partition to a minimum size - # sudo resize2fs -M "${IMG_LOOP}" - # - # shrink the partition + # TODO [TEST] + # shrink the partition to a minimum size + # sudo resize2fs -M "${IMG_LOOP}" + # + # shrink the partition - # force a FS sync - info "Forcing a fs rsync to umount the loop device" - sudo sync + # force a FS sync + info "Forcing a fs rsync to umount the loop device" + sudo sync - # freeing the loop device - info "Freeing the loop device" - sudo losetup -d "${IMG_LOOP}" + # freeing the loop device + info "Freeing the loop device" + sudo losetup -d "${IMG_LOOP}" - # copy the image to final dir. - info "Copy the image to final dir" - cp "${BASE_IMG}" "${FINAL_IMG_DIR}/${NAME}.img" + # copy the image to final dir. + info "Copy the image to final dir" + cp "${BASE_IMG}" "${FINAL_IMG_DIR}/${NAME}.img" - # info - info "Image for ${NAME} ready" + # info + info "Image for ${NAME} ready" } # main build block diff --git a/cmd/skyimager-gui/statik/statik.go b/cmd/skyimager-gui/statik/statik.go index 6edfb1f6..bd5b0746 100644 --- a/cmd/skyimager-gui/statik/statik.go +++ b/cmd/skyimager-gui/statik/statik.go @@ -6,7 +6,9 @@ import ( "github.com/rakyll/statik/fs" ) + func init() { - data := "PK\x03\x04\x14\x00\x08\x00\x08\x00c\x8diP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00 \x00icon.pngUT\x05\x00\x01+\x80f^\x00\xee\x15\x11\xea\x89PNG\x0d\n\x1a\n\x00\x00\x00\x0dIHDR\x00\x00\x00`\x00\x00\x00`\x08\x06\x00\x00\x00\xe2\x98w8\x00\x00\x00 pHYs\x00\x00\x0e\xc4\x00\x00\x0e\xc4\x01\x95+\x0e\x1b\x00\x00\x15\xa0IDATx\xda\xed\x9ditTU\xb6\xc7\x7f\xbb\x86$$\x81\x84y\x12\x10\x94A\x90A\x04A\x0c\x83\xa0bk;K\xd0\xa6\x15\x01\x95V\x99\x12\xfa\xb5\xef\xf5\xeb\xf7\xa5\x9fo\xad\xe7[+\xa90H\x8b\x04DEED\xed\xb6\xbb\xa5\x9dI\x800\x0f\x82\xa8\x80\"\x10\x19\x04\x12\x02$d\xac\xda\xef\xc3\xa9\xa2R\xa9[!U I@\xf6Z,\xce\xbdg\xd7\xcd\xbdw\x9f\xf3?{\xef\xff\xbe\xf7\n\x8dU\xa6\x17&\xe0\x88\xed\x8b\xd0\x07\xe8\x0et\x05:\x02\xadQ\x12\x818\xc0 \x80P\x0e\x14\xa1\x14 \x9c\x00\x0e\xa3\x1c\x00\xf6!\xec\xa6\xa2d'scO7\xc6\xcb\x94Fs&\xb3\xca[ \x8e\xdb@G\x83$!\xf4D\xd5\x8e\x889GU\x8d\xa8m\xfe\xf7\xa0\xec\x01]\x0b\xf2\x05\xea\xfe\x8c\x0cG\xde\x15\x03\xa4\xb8\xdb \xb6dT\x1fFd\x18\xe0\x8c\xf8FWo\x80*\xfb(\x07\xcd\x01Y\x89\xea\xbbd\xd8~\xfe\xe5\x18`\xea1\x1bqm\xc6\x02SQ\xee\x02\x1c\x0d<\x08+\x10V\xa1,\xa4\xf4\xcc\xbfX\x90\xe8\xb9<\x0d0\xad0\ng\xdcD\x84\xd9\xa8\xf68?*\xebj\xa4\x873\x03B\xea\xb2\x074\x1dw\xd9k\xcc\x8d)\xbd<\x0c\xf0l\xbe\x9d\x98\xe6\x93P\xfd/D:\x07]|c2\x80\xaf\x8d\xe4\"\xbc@y\xd1\xab\xcc\x8b\xaf\xb8t\x0d\x90\xe2\xb9\x03p\x01\xd7qi\xca\xb7@\n.\xdb'\x97\x96\x01Rz\x06\xde\xda\xa6:~\x80\xc8\xfc\x07US\xfe&\xb2dS\x03\xcc\x00\x91V(s\x80G\xc27@\x8a\xde\x810\x0e\xad\xe7\xa8\xc6\x0e\xf3\x1f\x80\xa97[\xf7\x17\x97\xc3_\xd6\xc1\xdb\x8f\x85>\xc6\xfc\xb5P\xe6\x86\x94\x91\xd04\x1a2\x93apg\x98\xf6>\xb8=\xf5<\x07\x84dR<\x8bq\xd9>\xad9\x04M=n'\xb6\xd5N\xea\x99\xc9\xb2\xdb`\xd9\x04\x18? \xb4\xce+\x1b`\xe1z\xd8\x9ab\xdd_T\x06]^\x80\x1b:\xc2\xa7S\x03\xfbV\xec\x80 o6\x80\x11\xe0\x1b*\xce\xf5g^\xbc\xbbf3 \xb6\xd5dDzW\x0b\x17\x17\x01\x82\xd2\xee\x85\xf1\x03\x82!\xc5\xd7\xf6\xa8HF6\xfc\xf1\xb6\xd0:K7\x8b\xe4\x9f\x83\xd9#\xcd\xfe\xca\xfd\xc9\x03T\x0f\x9f\x11\x99\xfda\xbd\xc3Qo\x9cq\x93\x80\xcc\x0b\x1b\xe0\xd9\xfch\xe0O\xf5=D\xc6\xf5\x87\x19\xc3\xab\xd7\xf9\xe8[8[\x1az\x86\xb8\x15\xe6\xac\x81>mal\xaf\x10\x98kk\xa0\xe5X\xf9/f\x96\xbc\xc1\x9c\xc0j\x8b`\x03D'N\x04:\x197\xaa\xd2\x08\x0d8\x98w;\x94N\x98\xed\x16q\xf0\xd2\x83\xfemU\xebv\xdaj\xd5\x19I\xe0\xb4Y\xeb|\xf85|\x7fBu\xf1x|!\x8bV\xeew+dd\xab\xa2\xe0\xb0C\x85\xbbn\xce\xbfFm\xb4\x13\xb6\xa8\xc7\x81E\xa1\x0d0e\xbf\x0d$\x15\x94\x0bz,u\x08A\xffy\x9bj\xebxkH\xf1\xb57\xe7\xaan?,\xf2\xc1$\xd3SUG\x15\xd2\xb2T\xdb5\x13\xf9\xcd@\xbfN\xe5c\xfcu\xa7\xea\x8f\xf9\"\xbd\xda\xc2\xe3\x83T\xff\xf8Q=\xc7\x07\xcal\x9e=\xb5\x98\x05\xcd=\xd6\x06H\xe8:\x16\xe8\x89\xd6\x9f\xef\xd3\"6\xb4\xc7SY\\Y0\xe9&h\x1e\x0bjqv\x1b\x0fA\xce\x01\xf8\xef;!\xc6\x19\xacc\x0c\xe4\x0doF\x98\xd8\xe2\xff\xbe\x84\x82\xe2z\xf5\x88z\x12\x930\x16X\x15\x02\x82t\xea\xf9\x9b_O\x10\xf4\xd8\x8d\x10\xeb\x0c\x0d;\xaa\xaa\x87\n\xe0\x83]\xf0\xcd\x1f\xfc=Uu\xd2VCl\x14\xfcnX\xb0\x8e\xaaj\xce\x01\xd8p\x10Z\xc7\xa9>v#4q\xc2ooT\x9d\xbf\xa6\x9e \xc8\xdf\x9ejm\x80\x99\xa5m@\xeeB\x90\x1a\x05Mu4=\xc7\xf5\xb7\x86\x94\xca\xed\xb9k\xe0\xde>\xaa][Z\xeb\xec\xcf\x17\xf9\xe0kxz\xa8j\xab\xb8\xaa\xd0d\xfeO\xcb2;\x9f\xb9E$6\xca\xf4'\xf7\x17\x99\xbf\xb6\xde=\xa2\xbbH\xf1\xb4\xc1e;\x1eh\x00\x9b3\x19_\xbd}=I\xd3h\xb8\xa9s\xf5:\xa7\x8b!s#|\xf2th\x9d\xb9\xd9\x06bRFX\xf7\xef;i\x16\xe8\x18'<7\xcc\xbf\x7fH\x17s\x0egK\xea\xd5\x1fr \x92\x0c\xcc\xaf\nA\x0f\xd7l5\xaf;\x08\xea\xdf\xc1\xef\x16\x86\x82\xa0\xcc\x8d\xaa\xfd\xda\x1bCY\xe9\x9c*\x86%\x9bU\xef\xed\x03\xd7\xb6\xb2\xd6\x99\x93\xad\xea\xf6\xc0\xe4!\xd0:\xde\xaf\xe1\xb4\x99sX\xbb\xbf^!\x08\xd0\x87\x03\x0d0\xedLK`XXy\x9b:\x98\x92\xdd[a*\xffBx>en\xd5\xb9kD\xe6\xdc\x1f\x1a\xa6^\xd9\xa0ZX*\x92:\xd2Z'\xaf\xc8\x04g6\x1b\xcc\x1a\x11\xdc\xdf\xbd5\xac\xdd_\xefY\xd2a\xcc\xaahI\x86#\xcf\x18 \xaa\xe9m\xa8\xd6{\x9d~\x9b\xf8\xea\xfbW~\x05\xd1\x0e\xb8\xa7\x8fu\x7fY\x05\xcc[\x03C:CRWk\x9d\x85\xebMz\xe2\xee\xeb\xe0\xba6\xe1\x9f\xc3E\x12'b\x1f\x03\xacpx\xad2\xba\xe6\xd3'r\x08\x8av\xa8\xf6o\x0f\xbd\xdaB\xdb\xa6\xaaw\xf4\xf4\xbb\x8bV\xd0\x91\x9eeF\xadM\xacu\x96\xef\x80#\xa7\xc1u\x9f5|\x95\x94\xab\xce_g\xf6\xce\x1ee}\x8c\xb2\n\x9f\x87\xa8:\xa0#\xec>\x06e\x15\xf5\x00G\xc2h\xbf\x01D\x92\xd0\x10\xc1W-!\xc8a\x17\xb9\xffz\x13\xf8\x8c\xee.\x12\x17\x15\x08!\xa1<\x9f\xd5\xdf\xc3\xc1S\xaa\x13\x07\x9b\x9e\xaa:\x8aHz\x16tm\xa9\xfa@_k\x9d\xb7\xb7\x8b\x1c=\xadzc'\x91Q\xd7\x04CTI\x85\xc8\x9b\xdb\xcc\xd6\x1d=EV=\x05\x85e\xaa\x9f\xed\x15Y\xb6\x15>\xdc\xadZ\xe1\xb9hAY\x92Y\x03\x9e\xcaM\x00z\xd4}\xcc\x01\x13\x06\xc1\x9f\xc7B\xd7\x96x\xed[\xf3\xdf\xa7e\x99\x00-.\xca\xba\xff\x8b}\xf0\xd5\x11p\xdd\x07N\xbbU\xea\x05\\\xde\xda\x84\x94\x91\xbe\xb5&P\xe7\x8d\xad\x86\xdc\x01H\xf5\xea\xc4G\xc1\x03}\xcd\xbf]Ga@\x1ax.FX*\xf4d\xfa\xb9\x04\x07\xf1\x1d\xfb\xa1j\xafK\x08\xea\x98\xa0\xfa\xda\xa30\xfa\xda\xc0I\x1f\xca\xd3\xa9\xba\xfd\xed\xcf\xaa\x9f\xef\x83E\xe3\xaa\xcf\x0b%6\x81)C\xacu>\xd9\x03\xbb\x8e\xaavj\x0e\xc9\xfd-\x823\xc0\x95e\xf2B\xfd:\xc0\xed=\x82u\xd6\xfe\x08\x1e\x8fw\xaf\xd49\x1c\xd9q\xc4\xf4u\x80\xf4\xa96\xf8\n\x13\x82n\xea\xa4\xfa\xb7\xc9\"\xed\x9a\x85\xf6n.\x04A\x19\xd9\"\x8f\xde`\xd6 \xab\xdf\xec:\xaa\xfa\xf1\x1e\x91\x7f\xbb\x15\xe2\xa3\xacu\xd2\xb3\xcc\x9c\x9b\x91\xa4\xea\xb4\x07\xf7\xff\xf3\x1b\xd5\xef\x8e\x8b \x90:2\xf8\x18n\x8f7q\xe7\xdd\xbf(Y\xf5\xe5\x1c\x91\xad?Q\x97uD}\x1c\xa0\xdd/\x98~\xa8\xe1\x0c\x18\xd6\x15V=\x05M\xa3/<\xeaC\xcd\x80\x13\x85\xf0\xe66\xd5\x0d3B\xff\xc6\x95\x0dN\xbb\xea\xf4$k\x9d]G\xe1\xd3\xbd\xd0,Z\xf5\xc9\xa1\xd6\xc7H[m6:$\xc0#7\x04\xeb\xfc\xe3\x1b\xd8{\xdc\x9b\xcco\x07\x93\x07\xc3\xb8\xfe\xaaw.4)\x8d:\x9a\x0d=\x1c W\xd7\xc5\x0c\xe8\xdeZ\xe4\xc3\xc9\xe6\xe6_h\xd4W7\x03\x16\xe4\xa8\x0e\xef&r}\xfbj\xf8\xde\xad\x86\xef\xbd*\xd1Z\xc7\x97v\x982D$!&\xb8\x7fk.\xac\xfe\xc1\x9c\xff\xb4$\x88\xb2\x07\xea\x80H\xdajo\nOD\xcc\x1ab\xf8\xe9\xbf? C\xe7\xa8\xfe\x90W'\x0b\xf2\xd5\x0e\x84\x8e\xb5]O\xa2\x1c\xb0\xe2qh\x19g\x9d\xa9\xac\xa9\x14\x97\xc3\x82\x1cxkBh\x9d\x97\xd6\xf9\xf9^+9|\x1a\x96o7\xf9\xfeP\x04Oz\x96Y\x04\xe2\xa3\xad3\xb1\x9bsa\xcd\x8f\xa6\xdd\xb6\xa9\xc9\x9c\xfa\xa4U\x9c\xb9\xd6\xc1\x19u\xb28wt\xa0\xda\xda\xbb&E\x0cA\xff>\xda\x14K\x85\x03;V\x10\xf4\xc6\x16\xe8\xd8\x0cFw\xb7>VQ\xa9\xea\xcb90\xba;\x0c\x08\xf1\xf7\xe6\xaf5\xbe\xfd#7@\x97\xe6\x81\x84\x0c\xc0\xa1S\xaa\xef~e\xda\x93nRm\xde$\xf8\x18\xe9\xde\xc5\x19\xe0\xb9a\xaa1\x8e@\x9d\x92r\xf0\xa8_\xa7\x16\x10\xd4\xda\x01\x92X\x1b\x08j\xdf\x14\xfe0:<\xd8\xb1\x82 \x8f\x8a\xb8\xb2\xe1?\xc6\xa8\xdaB\xf1\xbd[D\xf2*\xf1\xbdUu\xce\x96\xaa.\\\xef]XG\x05\x132\xaa&\xb5Q\xee\x01\xbbMu\xe6\x88\xe0\xf8\xe1@\xbe\xea\xca\x9d\xe6\x18M\x9c&{ZU'\xcd\xbb\xc0\x07T\nF\xb6\x08':0o\x1d\x89Xf\x8e0\xf9\xfc\xda\xca\xaa\xef\x0c\xdf\xfbH\x08\xbe\xd7\xa30'\xbbz\xbew\xe9&\x93\x9c\x1b\xd1\x0d\x06]\x15\xdc\x7f\xb6\x14\x16m4\xed\xfb\xfb\xc25\x16\xe5fs\xd6\xf8\xab&&\x0e2\x90SY\xbe? \x7f\xfb\xba\xce\xa2\x818\x07\xa83R/\xc8iW\x9d482\xd8\xa9\nAi\xab\x8dW\xe3\xb4\x87\xe0{w\xc3\xbe\x13\xaa\x99U\xf8^\xff\x0c\x82\x8c5fc\xf6\xc8`B\xc6dV\xe1L\xb1\xaaq=\x83\x8fQP\x02\x8b7\x9a\x0d\x93\xbc\x0b\xd6\xc9\xc8\xf6\x19H5\xcaaxfw\xe4\xdc\xb2\xd3\xe1\x0dO#\x82\xa0\x91\xd7\x88\xb4i\x1a>\xecT\x85\xa0-\xb9\xaa[\x7f\x12y\xff\x89\xd0YO\x1f\xdf;a\xa0\xb5\xce\x07;a\xffI\xd5\x9emD\xee\xee\x1d\xcc \x97\xbba\xee\x1as\xee\xc3\xae\x86\x9b\xbb\x04\x1fc\xd1\x068[bt\xee\xe9\x0d=Z\x07\xea\xe4\x9f\x13Y\xba\xd9\xef\x1d\xfd\xf6F\xf8\xe1\xa4j\xd6\xfe\xc8=\"\x07J9\x12\xd9Cv\xb7u\xaf\x9byxA\xbe\xf7 \xac\xfb\xb1z\xbe7\xbd\x12\xdfk\xb7\x05\xeb\xac\xdc \x07\xf3MD\x9b:\xd2:\xb3:\x7f\xad\x7f;uT\xe8\xcc\xaa/0N\x19i\xe0(+\xd2\x87U\x85r\x07\xa2E(Q\x91@\xd0\xa0N\x91{>\xbevn\x01\xbc\xbf\x0bvW\xc7\xf7f\x99u\xe6w7[\xeb\xac?\xa0\xba\xfe \xb4\x8e7I?+N\xd8g\xa0kZ\xaa\xdew}\xf01V|\xa5\x9a{\xca\xb4\x07wR\x1d\xde5P\xa7\xccm<,\x9f\xe7sg/\xd5>mM6\xb6\x16^P\x91\x03\xa4\x00\xa1E$\x10tm+\xeb,d8\xed\xb9k\xe0\x9e>\xaa\xddB\xf0\xbd?\xe6\x8b|\xb0\x0b\x9e\x1a\xaa\xda*D\xe9JZ\x96i?3L56*\x98\x13\xce\xde/\xb2%\xd7\xec\x9d5B\xc4a\x0b>Fz\x96\xdf\xab\x99=2\xf8\xba\xde\xda\xa6z\xf4l\xb0N\x8f\xd6\xb5\n\xc4\n\x1c\xc0 \xa0\xdb\xc5 T.$>\xbe\xf7_\xd5\xf1\xbdk\x8c\x074+\x04\xdf\xfbC\x9e\x81\x81\x18\x07<{K\x88\xcc\xeajo L\x1c<18\xb8\xff\xcb\xefa\xfba\xd3\xee\xd2\x02\x1e\xea\x17\xec\x81\xb9*=\xf55\xa0\xa3\x89E\xea\xe0\x1e\x9cp\xa0z8\xd2@,\xdaQ;\x08Z\xbc \xaeog\x18-+\xbd\x82bX\xb2I\xf5\x9e>\xd0=$\xdf\x0bn\x8f\xea\xa4\x9b\xcc\xcd\xa8\xaa\xb3\xf7\x04\xfc\xe3[\x03\x1d&\xbd\x1d|\xce\xbe\xd4\x05\xc0\xcc\xe1&F\xa8\xac\xf3\xe9\x1e\xd8u\xc4\xb7\xda\xf8\xd6\x10\xa3\x13\xe3\xac\x15\x04\x1dv\x80\x1c\x884\x10+\xad\x10i\xe2\x8c\x0c\x82*<\xa6\xdc\xc4u_\xf5|\xef\xd9R\x91\xd9!\xf8\xde\xfcs\xf0\xeafU\x9b\xcd\xe4k\xact\\Y\xa6\x15\xed\x80i\xb7\x04\xf7\xef>\x06\xab\xbe3\xd7\x94\x18c\x9e\xc4\xb1\xcc-y=\x9f\xab\x12\x02\x0b\x88K\xcak\x05A\x07\x1c\xc0\xbe\x88\xe7O!tn\x1e\xd9oW\xee4>\x7f\xb5|\xefZS\x0d\x11\x8a\xef}9\x07\x8aJ\xe1\xee\xde\xe6I\x99\xaa\x9e\xcf\xf1Bx}\xabi?:\x10\xda7\x0b>FF6\xa8\xc7\xb85O\x0e5e*\x95\xe5\xab#\xf0\xd9^\xff\xf6\xb4\xa4@\x02\xc8G\xe8D({\x1d\x88~\x1di \xb6\xef\xa4j\xa7\xc4\xc8 (=Ku\xd6\x08\xb0\x87\xe0{\xdf\xd9\x01\x87\x0b \xed^\xeb\xdf\x97V\xa8\xbe\xb4\xce\xc7fYC\xe1_rT\x8b\xcbL&\xd3W3T\xb9\xff\xd8Y\xd5e^\x03\x85Jo\x9b\x19\xe4\xabcR}zh\xa0\xce\xf7'k\x05A\xbb\x1d\x9c>\xb0\x8b\x84\xae\xee\x1a\xbd\xa3\xb3\n\x04m\xc9\x15\x19\xd3=|\x08\xca\xfaA\xf5\xc7<\x91\x89\x83\xac\xcbR*\xf3\xbd\x0f\x86\xe2{\xb7\x89\x1c9\x03\x03\xafR\xbd\xf5\xda`\x9d\xe2r\xd5\x05\xeb\xcc\xde\xdb{\x8a\xf4\xeb\x10|\x8c\x05\xebDJ*\xccVr\x7f\x91\xce\xcd\x03u\x8e\x9c6\xbc\xb2\xcf\xf3\x99|\x93\x88\x89U\xfc:[r#\x86\xa0\n\xca\x8av9X\xdc\xed4)\xba\x07\xe8\x1d\xee\xfc\xf9l/\xea\x04\xe6\xd1\xa9\x99#\xac\xefA\x84\xb2\x87\xf9\xf1\xa7}\x89\xd6\xb5@\xefp!(k\xbf\xea\xf1\xb3&\x00\xaa)\x04\xed9\x01\x9f\xed\x83\x85\xe3\x02\xb3A\x01|o\x96jb\x0cL\xb9\xc9\xfaX\x9f\xee5\xb4\xe4U \x90< XG\xbd\xd15\xaa\xda7\x04\xdf\xfb\xfa\x168Yh\xda\xb7v\x87\x1b:\x06\xea\x14\x96\xc1+\xeb\xfd\xd7\xfaP_\xb8\xbay\xa0\xce\x89Bo\x14\x1cYY\xcaZS\x15a\xcaR\xbe@\xf5\xe9p!\xa8\xdcm\x9e>|>\x8cttF6<2@\xb5}3kF\xec\xebc\xaa\xff\xfa\xce\xcb\xf7\x86`\xd7\xd2V{\xf9\xde\xe1\xe0\xb0\x05\xeb\xfc\xf3\x1bC\xec#\"5\xe1{\xad\xd2\xdbK7A\xfe9\xbf\x8e\xd5q\x96l\x82\xf2\x8a\x88\xcbR\xbe\xf0\x1b\xa0\xb4\xe0s\xa2\x13*\x88\xa08w\xce\x1a\xe3\x19\xc4\xd7 \x9bt\xbc\xd0@\xc3\xfa\x19\xd5\xe7\x85\x9cvsL+\xd9y\xc4\xc7\xf7\xc2SC-\xe0\xa9R^\xa8C3C\xccT\x95\x7f|\x03{\x8e\x1b\xe8\xe9\xdd\x16~U%\xbd]\xe1\xf1fV\xbd\x92\xd45\xb8\x88\xb8\xb0\xd4\\{\x84R\x8e\xa7\xe2s\xbf\x01^j\x9eG\xaa'\x07\xd5\x11\xe12b\xc7\xce\xc0\x8b_\xc0\x9f\xef\xbc0\x04\xbd\x9c\xa3\x9a\xd4\xd5\x04_V\x8c\xd8\xcf\x85\xf0\xe66\xf30\xddU \xd6\xc7\xf2\xd1\x89S\x86\xa8&\xc4\x04\xebl;\xac\xfa\xe5\xf7^6+I5\xda\x1e\xe2\x18\xde\x8dY#\xfc\x8e\x80O\xe7\xaf\xbbT\xf7\x9f\xf4\xebTMo\x03\xbc\xf8\x85\xb9\xf6\x08\x19\xb1\x1c\xe68\xf3\xfc\x06\xf0\xba\xe6\x88\x8c\x8c\x84\x94\x7f\xf1KS\x9d6\xf0\xaa\xd0\x10T\\\xae\xfa\xd2:\x91e\x13\x02\x03\xa6\xcaz/\xad5\xeee\xeaHk\xcf\xe7\xc8\x19\x91\xb7\xb7\x83\xc3\xae:c\xb8\xb5N\xba7/\x14\x1f\xad:u\xa8\xd1\xa8\xdc\xbf9W${\xbf\xd9j\xdb\xd4\xa4\x94\xab\xea\xa4U\xca\x0b\xf5h#bb\x15\xbf\xce\xb6\x9fT_\xfc\xb2\x16\x8c\x18\xfe\xd7\x17\xf8\x0dP^\xbc\x02glz$0T^\x01\xc9\xaf\xc3\x86\x19\x86\x98\xb7\x92e[M 4&D\n\xbb\xa8\x0c^^o\xfa\x07t\xa8\x9e\x90\x1f?\x00\xaen\x11\xdc\x9f[\x00>\xbe\xf7\x89\xc1\xd6E\x02\xe7G?&w\xd4\xa4Jz{\xfd\x01o\xd9\x89\x8f\xf1\x1b\x1e\x98\xde>Yd\xae\xb5\xdc\x1dq\n\xba\x02\x8f\xc7\xc2\x00\xf3\xe2\x8e\x93\xa2\x1f!\xdc\x17\xc9q\x7f\xc8\x83_/\x86\x8f\xa7\x82\x0f\x1a\xce'\xb3<&\x99\xf5\xfc\xad`\x13\xeb\x9c\xff\xeb[\xcc\xc5\x85r+\x8b\xcaL>>\x94[\xe9s\x1b\xcb\xdd\xe6oX%\xef\x0e\x9d\x82\xf7v\x9av\x93(xf\x98\x85\x8b\\\xc9@-\xab$\xefN\x97\xc0\xaf3\xe1\x87\x93\xd4\xe6m{\x1f\x91a?\x1el\x003?\x16\xa2\xdc\x1bia\xd6\xc6\x830z\x01\xfc}\x8a\xaa/\xecWU]\xf5\x9d\xc9|>z\x83u\xc4\xea\xf1>>\xda\xbb-\xdc\xd9\xcbz\x0dyu3\xe4\x17\xa9\x8e\xb8\x06\x06u\n\xd69[\n\x8b6\x98\x8d\xfb\xfbB\xb7\x96\xc1\x94\xe4\x9c5\xfeGS\x1f\x1f\x04\xad\xe2\x02\xcf\xc7\x97Y\xf5]\xd73\xc3\x0c1\xaf\nG\xcf\xc0\xbdKT\xb7\xe6\xd6\xba\x12ba\xe5[\x19h\x80\xfco?\xa6e\xef\xbd\x01\xaf\x97\x0f\xb34q\xdbO\xaa\x83\\\"K\x1e\x81\xb1=\xbdENY0=I5\xcaaMI\xfe}\xb7\xea\xde\x13\"\x99\xc9\x86\x1b\xad\xcem\x0c\xf5 F\xe6F8]ltL)z %YP\x0c\x8b7\x9a~3C\xac]d\xb7\xc7\xe8\xc48\xe0\xb9[\xcc\xf9\x7f\xb2\x07&-\x0f\xe4\x03\"t=\xbf\xe3\xdc\xcf\x1f\x876\xc0k}<\xa4jZU+\x85+G\xcf\xc2]\x8b\x0cV?\xd4\x0f\xb6\xe4\xc2\xca\x89\xa1\xf5\xd3W\x9b\x02\xa8\xdf\x0c\xb4\xee\xff\xeb\xd7\xb0\xff$\xf4h\x03\xbf\xb6\x88\xd7\x0d\xdfk\xda7w1\xff\xaaJ\xe6\x068Sb\xa0\xe3\xee\xde\xd0\xb3u`\x7f^\x11\x18\xbe\xd7\xc8o\x06Ba\x19\xa4,\x83wvxa\xb3\xb6\xaf\xb9\x15\xd2Y\xd8\xce\x13\xda\x00\x00E?\xbfNl\x9b?\xa1\xda)\\\x08\xaa\x1a\xd6.\xdf\x0e\xcb\xb7\xabvmiH\x8f;z\xaa\xc6G\x05B\x8b\xaf\n\xed\xcfw\x06\x17@\x9d\xf7J\xbc\x84JJ\x88\x875\xde\xdb\xa9z0\xdf\xbb>\x8c\n\x86\xaf\xb2\n\xd5\xb9k\xfd{\xad\xdc\xca\x85\xebU\x8bJ\xfd?\xdc\x9a\x0b\xbd\xfe\xd7;#\xa85\xec\x80\xea!\xca\xcf\xbd\x16l\x13+I\xf5< \xb2(\x12\x08\xaa\xae\x1d\xe5\x10\xe9\xdb\x1ez\xb75\x15\x0eM\x9c\xf0\xe9\x1e\xc3\xe9NK\x121\x1eT\xe5' T\xcf\x94\x98\x82-A\xf5\xf7\xa3\xcc#\xa6Uu\xde\xd9!\xf2\xddq\x13\x15??\xdaP\x8e~\x1d\xd5\x83\xa7\xbc\xd5\x0c\xaa\x1a\x1f-\x92:\xca\x1c\x0fD\xd4\xeb\"/\xdd,r\xa2\x90\x8b\xf9\\\xd8\xd3\xb8$\xb3f\x06x\xea\xa0\x9d\xa6\x9dw\x9e\xcf\x0f5\xc0\x0b\x9b\xea\xac\xdd\x90\xaf,\xf3W\xc0\xed\xa6\xa4\xa0?\x0bZ\xb8kf\x00\x80\x14\xcf\xed\xc0\xc7\\\x91\xba\x90\xb1\xe1\xbd\xb0\xe9\xbc\x11ty\xc0W0\xae\xcc\x80H\xda\xef\x90.\x11\xbc\xb2\x0c\xc0S>\x13\x9bc\x0c\xd0\xea\xca \x8e@\x94\x93\xa03\xabw\x8c.$\xa9\x9eq(\xef\\\xb9\x9b\x11\xc9x\\\xb6wkg\x00\x80T]\x0cL\xbe\x02Aa\xb5\x97\xe0\xb2]\xf0\xc5\xad5{:\xbe\xa4`:1\x897\x02\xfd\xaf\x0c\xea\x1a\xc9W\xb8\xcb\xa6\xd7,6\xab\xa9\xa4\xb8\xbd/\xefn\x98\x97\xb8^B\xe2}y\xb7m\x7f\xdd\x1a\xc0xE\xc3\x11\xfd\x04\xae\xbc\xbe>D\xbb\x04\xe4\x0e\\Rc\xae,\xb2\x0f8\x88\xbc]'\x9f\x9a\xbd\x9c\x0c\xa0T\x80^\xe4\x0f8\xf8\x8d\xf0\x04\xe6\x1d\x98\xb6+\x88c\x1cv\xe0I\\\xb6\xa5\xe1\xe7\xe7\"\x95+\x1f\xf1\xf1\xa5\x19*\xd0\xc8?\xe2S\xfb\xcfX)o\xc0/\xf83V\xf0X\xb8\xb0Sw\x0601\xc2p\xd0\xf7@Z\xff\xa2f\x80\xf9T\xc9\x83\xe1,\xb8\x17\xc7\x00\x00\xb3*\xbaa\xb3\xff\x92>e\xf8\x15\xca\x83d\xd8\x1a\xc1\xa7\x0c}\xf2\xdc\xa9X\xa2\x12\xe7\x81N\xba\x8c\x0d\x00\xf0*\xe5\xc5\xd3\x99\x17\xdb\x88>\xe6\x19\x9c;Zp\x19\x06ly\xc0s\xb8l\x8d\xf4s\xb6\x95eFY[\xec\xce\xcb\xe7\x83\xce\xb0\x02\xf5\xcc\xc4e\xff\xb9\xaeo\xd5\xc5\xfe\xa4\xf9\xed\x88\xb8\x80>\x97\xa4\x01\xd0\xdd )\xa4\xcb\xa7\x17\xeb\x16\xc9E\x9f\xb8S\x0e\xd8i\xd6y\x12\xe6\x9b\x04\x9d/\x11\xb8\xc9Ex\x81s\xc7\x97\xf0r;\xf7\xc5\xfcCRo\x974\xf5X\x14\xb1m'\x02\xa9\xa0=\x1b\xe5\x0cP\xf6\x80\xa6QZ\xf0:\x0bZ\x94\xd6\xc7m\x91z\x1f[\x13w\xdbhy\xdd\x1d SQ\xeeFp6\xb0\x01\xcaQ\xfd\x08d!g\x0f}Lf\x97z\xfd\xc2\x8c4\xe8D\x9f^\xd4\x06g\x93q(\x0f#\xdc\x82R_o\xef\xad@\xc8\x01V\xe2.\x7f\x979\xd1?7\xd4-hX\x03T\x96g\xf2[\x10\xd3|\x0c\xe8\x18D\x92\x80\x9eu\x92q5\xff\xbb\x81\xbd\xa8\xae\x01\xf9\x82\xb2\xb3\x9f\xf3R\xb3\xbc\xc6p\xd9\x8d\xc7\x00Ue\xf2\xfef$\\\xdd\x0f\xa4\x0f\xd0\x1d\xb4+HG\xa05\xa2\x89 q\x80\xd3[_U\x0e\x14\xa1Z\x00r\x028\x0cz\x00d\x1f\xe8\xd7\x9c=\xbc\x8b\xccN\xa7\x1b\xe3e\xfe?\x99\xd9\xd1\x1b\xd4\x955\x1c\x00\x00\x00\x00IEND\xaeB`\x82\x01\x00\x00\xff\xffPK\x07\x08\xc5\xdc|\x13\xf8\x15\x00\x00\xee\x15\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00c\x8diP\xc5\xdc|\x13\xf8\x15\x00\x00\xee\x15\x00\x00\x08\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x00icon.pngUT\x05\x00\x01+\x80f^PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00?\x00\x00\x007\x16\x00\x00\x00\x00" - fs.Register(data) -} + data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\xa9@\x94P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00 \x00icon.pngUT\x05\x00\x01\xbfW\x9d^\x00\xee\x15\x11\xea\x89PNG\x0d\n\x1a\n\x00\x00\x00\x0dIHDR\x00\x00\x00`\x00\x00\x00`\x08\x06\x00\x00\x00\xe2\x98w8\x00\x00\x00 pHYs\x00\x00\x0e\xc4\x00\x00\x0e\xc4\x01\x95+\x0e\x1b\x00\x00\x15\xa0IDATx\xda\xed\x9ditTU\xb6\xc7\x7f\xbb\x86$$\x81\x84y\x12\x10\x94A\x90A\x04A\x0c\x83\xa0bk;K\xd0\xa6\x15\x01\x95V\x99\x12\xfa\xb5\xef\xf5\xeb\xf7\xa5\x9fo\xad\xe7[+\xa90H\x8b\x04DEED\xed\xb6\xbb\xa5\x9dI\x800\x0f\x82\xa8\x80\"\x10\x19\x04\x12\x02$d\xac\xda\xef\xc3\xa9\xa2R\xa9[!U I@\xf6Z,\xce\xbdg\xd7\xcd\xbdw\x9f\xf3?{\xef\xff\xbe\xf7\n\x8dU\xa6\x17&\xe0\x88\xed\x8b\xd0\x07\xe8\x0et\x05:\x02\xadQ\x12\x818\xc0 \x80P\x0e\x14\xa1\x14 \x9c\x00\x0e\xa3\x1c\x00\xf6!\xec\xa6\xa2d'scO7\xc6\xcb\x94Fs&\xb3\xca[ \x8e\xdb@G\x83$!\xf4D\xd5\x8e\x889GU\x8d\xa8m\xfe\xf7\xa0\xec\x01]\x0b\xf2\x05\xea\xfe\x8c\x0cG\xde\x15\x03\xa4\xb8\xdb \xb6dT\x1fFd\x18\xe0\x8c\xf8FWo\x80*\xfb(\x07\xcd\x01Y\x89\xea\xbbd\xd8~\xfe\xe5\x18`\xea1\x1bqm\xc6\x02SQ\xee\x02\x1c\x0d<\x08+\x10V\xa1,\xa4\xf4\xcc\xbfX\x90\xe8\xb9<\x0d0\xad0\ng\xdcD\x84\xd9\xa8\xf68?*\xebj\xa4\x873\x03B\xea\xb2\x074\x1dw\xd9k\xcc\x8d)\xbd<\x0c\xf0l\xbe\x9d\x98\xe6\x93P\xfd/D:\x07]|c2\x80\xaf\x8d\xe4\"\xbc@y\xd1\xab\xcc\x8b\xaf\xb8t\x0d\x90\xe2\xb9\x03p\x01\xd7qi\xca\xb7@\n.\xdb'\x97\x96\x01Rz\x06\xde\xda\xa6:~\x80\xc8\xfc\x07US\xfe&\xb2dS\x03\xcc\x00\x91V(s\x80G\xc27@\x8a\xde\x810\x0e\xad\xe7\xa8\xc6\x0e\xf3\x1f\x80\xa97[\xf7\x17\x97\xc3_\xd6\xc1\xdb\x8f\x85>\xc6\xfc\xb5P\xe6\x86\x94\x91\xd04\x1a2\x93apg\x98\xf6>\xb8=\xf5<\x07\x84dR<\x8bq\xd9>\xad9\x04M=n'\xb6\xd5N\xea\x99\xc9\xb2\xdb`\xd9\x04\x18? \xb4\xce+\x1b`\xe1z\xd8\x9ab\xdd_T\x06]^\x80\x1b:\xc2\xa7S\x03\xfbV\xec\x80 o6\x80\x11\xe0\x1b*\xce\xf5g^\xbc\xbbf3 \xb6\xd5dDzW\x0b\x17\x17\x01\x82\xd2\xee\x85\xf1\x03\x82!\xc5\xd7\xf6\xa8HF6\xfc\xf1\xb6\xd0:K7\x8b\xe4\x9f\x83\xd9#\xcd\xfe\xca\xfd\xc9\x03T\x0f\x9f\x11\x99\xfda\xbd\xc3Qo\x9cq\x93\x80\xcc\x0b\x1b\xe0\xd9\xfch\xe0O\xf5=D\xc6\xf5\x87\x19\xc3\xab\xd7\xf9\xe8[8[\x1az\x86\xb8\x15\xe6\xac\x81>mal\xaf\x10\x98kk\xa0\xe5X\xf9/f\x96\xbc\xc1\x9c\xc0j\x8b`\x03D'N\x04:\x197\xaa\xd2\x08\x0d8\x98w;\x94N\x98\xed\x16q\xf0\xd2\x83\xfemU\xebv\xdaj\xd5\x19I\xe0\xb4Y\xeb|\xf85|\x7fBu\xf1x|!\x8bV\xeew+dd\xab\xa2\xe0\xb0C\x85\xbbn\xce\xbfFm\xb4\x13\xb6\xa8\xc7\x81E\xa1\x0d0e\xbf\x0d$\x15\x94\x0bz,u\x08A\xffy\x9bj\xebxkH\xf1\xb57\xe7\xaan?,\xf2\xc1$\xd3SUG\x15\xd2\xb2T\xdb5\x13\xf9\xcd@\xbfN\xe5c\xfcu\xa7\xea\x8f\xf9\"\xbd\xda\xc2\xe3\x83T\xff\xf8Q=\xc7\x07\xcal\x9e=\xb5\x98\x05\xcd=\xd6\x06H\xe8:\x16\xe8\x89\xd6\x9f\xef\xd3\"6\xb4\xc7SY\\Y0\xe9&h\x1e\x0bjqv\x1b\x0fA\xce\x01\xf8\xef;!\xc6\x19\xacc\x0c\xe4\x0doF\x98\xd8\xe2\xff\xbe\x84\x82\xe2z\xf5\x88z\x12\x930\x16X\x15\x02\x82t\xea\xf9\x9b_O\x10\xf4\xd8\x8d\x10\xeb\x0c\x0d;\xaa\xaa\x87\n\xe0\x83]\xf0\xcd\x1f\xfc=Uu\xd2VCl\x14\xfcnX\xb0\x8e\xaaj\xce\x01\xd8p\x10Z\xc7\xa9>v#4q\xc2ooT\x9d\xbf\xa6\x9e \xc8\xdf\x9ejm\x80\x99\xa5m@\xeeB\x90\x1a\x05Mu4=\xc7\xf5\xb7\x86\x94\xca\xed\xb9k\xe0\xde>\xaa][Z\xeb\xec\xcf\x17\xf9\xe0kxz\xa8j\xab\xb8\xaa\xd0d\xfeO\xcb2;\x9f\xb9E$6\xca\xf4'\xf7\x17\x99\xbf\xb6\xde=\xa2\xbbH\xf1\xb4\xc1e;\x1eh\x00\x9b3\x19_\xbd}=I\xd3h\xb8\xa9s\xf5:\xa7\x8b!s#|\xf2th\x9d\xb9\xd9\x06bRFX\xf7\xef;i\x16\xe8\x18'<7\xcc\xbf\x7fH\x17s\x0egK\xea\xd5\x1fr \x92\x0c\xcc\xaf\nA\x0f\xd7l5\xaf;\x08\xea\xdf\xc1\xef\x16\x86\x82\xa0\xcc\x8d\xaa\xfd\xda\x1bCY\xe9\x9c*\x86%\x9bU\xef\xed\x03\xd7\xb6\xb2\xd6\x99\x93\xad\xea\xf6\xc0\xe4!\xd0:\xde\xaf\xe1\xb4\x99sX\xbb\xbf^!\x08\xd0\x87\x03\x0d0\xedLK`XXy\x9b:\x98\x92\xdd[a*\xffBx>en\xd5\xb9kD\xe6\xdc\x1f\x1a\xa6^\xd9\xa0ZX*\x92:\xd2Z'\xaf\xc8\x04g6\x1b\xcc\x1a\x11\xdc\xdf\xbd5\xac\xdd_\xefY\xd2a\xcc\xaahI\x86#\xcf\x18 \xaa\xe9m\xa8\xd6{\x9d~\x9b\xf8\xea\xfbW~\x05\xd1\x0e\xb8\xa7\x8fu\x7fY\x05\xcc[\x03C:CRWk\x9d\x85\xebMz\xe2\xee\xeb\xe0\xba6\xe1\x9f\xc3E\x12'b\x1f\x03\xacpx\xad2\xba\xe6\xd3'r\x08\x8av\xa8\xf6o\x0f\xbd\xdaB\xdb\xa6\xaaw\xf4\xf4\xbb\x8bV\xd0\x91\x9eeF\xadM\xacu\x96\xef\x80#\xa7\xc1u\x9f5|\x95\x94\xab\xce_g\xf6\xce\x1ee}\x8c\xb2\n\x9f\x87\xa8:\xa0#\xec>\x06e\x15\xf5\x00G\xc2h\xbf\x01D\x92\xd0\x10\xc1W-!\xc8a\x17\xb9\xffz\x13\xf8\x8c\xee.\x12\x17\x15\x08!\xa1<\x9f\xd5\xdf\xc3\xc1S\xaa\x13\x07\x9b\x9e\xaa:\x8aHz\x16tm\xa9\xfa@_k\x9d\xb7\xb7\x8b\x1c=\xadzc'\x91Q\xd7\x04CTI\x85\xc8\x9b\xdb\xcc\xd6\x1d=EV=\x05\x85e\xaa\x9f\xed\x15Y\xb6\x15>\xdc\xadZ\xe1\xb9hAY\x92Y\x03\x9e\xcaM\x00z\xd4}\xcc\x01\x13\x06\xc1\x9f\xc7B\xd7\x96x\xed[\xf3\xdf\xa7e\x99\x00-.\xca\xba\xff\x8b}\xf0\xd5\x11p\xdd\x07N\xbbU\xea\x05\\\xde\xda\x84\x94\x91\xbe\xb5&P\xe7\x8d\xad\x86\xdc\x01H\xf5\xea\xc4G\xc1\x03}\xcd\xbf]Ga@\x1ax.FX*\xf4d\xfa\xb9\x04\x07\xf1\x1d\xfb\xa1j\xafK\x08\xea\x98\xa0\xfa\xda\xa30\xfa\xda\xc0I\x1f\xca\xd3\xa9\xba\xfd\xed\xcf\xaa\x9f\xef\x83E\xe3\xaa\xcf\x0b%6\x81)C\xacu>\xd9\x03\xbb\x8e\xaavj\x0e\xc9\xfd-\x823\xc0\x95e\xf2B\xfd:\xc0\xed=\x82u\xd6\xfe\x08\x1e\x8fw\xaf\xd49\x1c\xd9q\xc4\xf4u\x80\xf4\xa96\xf8\n\x13\x82n\xea\xa4\xfa\xb7\xc9\"\xed\x9a\x85\xf6n.\x04A\x19\xd9\"\x8f\xde`\xd6 \xab\xdf\xec:\xaa\xfa\xf1\x1e\x91\x7f\xbb\x15\xe2\xa3\xacu\xd2\xb3\xcc\x9c\x9b\x91\xa4\xea\xb4\x07\xf7\xff\xf3\x1b\xd5\xef\x8e\x8b \x90:2\xf8\x18n\x8f7q\xe7\xdd\xbf(Y\xf5\xe5\x1c\x91\xad?Q\x97uD}\x1c\xa0\xdd/\x98~\xa8\xe1\x0c\x18\xd6\x15V=\x05M\xa3/<\xeaC\xcd\x80\x13\x85\xf0\xe66\xd5\x0d3B\xff\xc6\x95\x0dN\xbb\xea\xf4$k\x9d]G\xe1\xd3\xbd\xd0,Z\xf5\xc9\xa1\xd6\xc7H[m6:$\xc0#7\x04\xeb\xfc\xe3\x1b\xd8{\xdc\x9b\xcco\x07\x93\x07\xc3\xb8\xfe\xaaw.4)\x8d:\x9a\x0d=\x1c W\xd7\xc5\x0c\xe8\xdeZ\xe4\xc3\xc9\xe6\xe6_h\xd4W7\x03\x16\xe4\xa8\x0e\xef&r}\xfbj\xf8\xde\xad\x86\xef\xbd*\xd1Z\xc7\x97v\x982D$!&\xb8\x7fk.\xac\xfe\xc1\x9c\xff\xb4$\x88\xb2\x07\xea\x80H\xdajo\nOD\xcc\x1ab\xf8\xe9\xbf? C\xe7\xa8\xfe\x90W'\x0b\xf2\xd5\x0e\x84\x8e\xb5]O\xa2\x1c\xb0\xe2qh\x19g\x9d\xa9\xac\xa9\x14\x97\xc3\x82\x1cxkBh\x9d\x97\xd6\xf9\xf9^+9|\x1a\x96o7\xf9\xfeP\x04Oz\x96Y\x04\xe2\xa3\xad3\xb1\x9bsa\xcd\x8f\xa6\xdd\xb6\xa9\xc9\x9c\xfa\xa4U\x9c\xb9\xd6\xc1\x19u\xb28wt\xa0\xda\xda\xbb&E\x0cA\xff>\xda\x14K\x85\x03;V\x10\xf4\xc6\x16\xe8\xd8\x0cFw\xb7>VQ\xa9\xea\xcb90\xba;\x0c\x08\xf1\xf7\xe6\xaf5\xbe\xfd#7@\x97\xe6\x81\x84\x0c\xc0\xa1S\xaa\xef~e\xda\x93nRm\xde$\xf8\x18\xe9\xde\xc5\x19\xe0\xb9a\xaa1\x8e@\x9d\x92r\xf0\xa8_\xa7\x16\x10\xd4\xda\x01\x92X\x1b\x08j\xdf\x14\xfe0:<\xd8\xb1\x82 \x8f\x8a\xb8\xb2\xe1?\xc6\xa8\xdaB\xf1\xbd[D\xf2*\xf1\xbdUu\xce\x96\xaa.\\\xef]XG\x05\x132\xaa&\xb5Q\xee\x01\xbbMu\xe6\x88\xe0\xf8\xe1@\xbe\xea\xca\x9d\xe6\x18M\x9c&{ZU'\xcd\xbb\xc0\x07T\nF\xb6\x08':0o\x1d\x89Xf\x8e0\xf9\xfc\xda\xca\xaa\xef\x0c\xdf\xfbH\x08\xbe\xd7\xa30'\xbbz\xbew\xe9&\x93\x9c\x1b\xd1\x0d\x06]\x15\xdc\x7f\xb6\x14\x16m4\xed\xfb\xfb\xc25\x16\xe5fs\xd6\xf8\xab&&\x0e2\x90SY\xbe? \x7f\xfb\xba\xce\xa2\x818\x07\xa83R/\xc8iW\x9d482\xd8\xa9\nAi\xab\x8dW\xe3\xb4\x87\xe0{w\xc3\xbe\x13\xaa\x99U\xf8^\xff\x0c\x82\x8c5fc\xf6\xc8`B\xc6dV\xe1L\xb1\xaaq=\x83\x8fQP\x02\x8b7\x9a\x0d\x93\xbc\x0b\xd6\xc9\xc8\xf6\x19H5\xcaaxfw\xe4\xdc\xb2\xd3\xe1\x0dO#\x82\xa0\x91\xd7\x88\xb4i\x1a>\xecT\x85\xa0-\xb9\xaa[\x7f\x12y\xff\x89\xd0YO\x1f\xdf;a\xa0\xb5\xce\x07;a\xffI\xd5\x9emD\xee\xee\x1d\xcc \x97\xbba\xee\x1as\xee\xc3\xae\x86\x9b\xbb\x04\x1fc\xd1\x068[bt\xee\xe9\x0d=Z\x07\xea\xe4\x9f\x13Y\xba\xd9\xef\x1d\xfd\xf6F\xf8\xe1\xa4j\xd6\xfe\xc8=\"\x07J9\x12\xd9Cv\xb7u\xaf\x9byxA\xbe\xf7 \xac\xfb\xb1z\xbe7\xbd\x12\xdfk\xb7\x05\xeb\xac\xdc \x07\xf3MD\x9b:\xd2:\xb3:\x7f\xad\x7f;uT\xe8\xcc\xaa/0N\x19i\xe0(+\xd2\x87U\x85r\x07\xa2E(Q\x91@\xd0\xa0N\x91{>\xbevn\x01\xbc\xbf\x0bvW\xc7\xf7f\x99u\xe6w7[\xeb\xac?\xa0\xba\xfe \xb4\x8e7I?+N\xd8g\xa0kZ\xaa\xdew}\xf01V|\xa5\x9a{\xca\xb4\x07wR\x1d\xde5P\xa7\xccm<,\x9f\xe7sg/\xd5>mM6\xb6\x16^P\x91\x03\xa4\x00\xa1E$\x10tm+\xeb,d8\xed\xb9k\xe0\x9e>\xaa\xddB\xf0\xbd?\xe6\x8b|\xb0\x0b\x9e\x1a\xaa\xda*D\xe9JZ\x96i?3L56*\x98\x13\xce\xde/\xb2%\xd7\xec\x9d5B\xc4a\x0b>Fz\x96\xdf\xab\x99=2\xf8\xba\xde\xda\xa6z\xf4l\xb0N\x8f\xd6\xb5\n\xc4\n\x1c\xc0 \xa0\xdb\xc5 T.$>\xbe\xf7_\xd5\xf1\xbdk\x8c\x074+\x04\xdf\xfbC\x9e\x81\x81\x18\x07<{K\x88\xcc\xeajo L\x1c<18\xb8\xff\xcb\xefa\xfba\xd3\xee\xd2\x02\x1e\xea\x17\xec\x81\xb9*=\xf55\xa0\xa3\x89E\xea\xe0\x1e\x9cp\xa0z8\xd2@,\xdaQ;\x08Z\xbc \xaeog\x18-+\xbd\x82bX\xb2I\xf5\x9e>\xd0=$\xdf\x0bn\x8f\xea\xa4\x9b\xcc\xcd\xa8\xaa\xb3\xf7\x04\xfc\xe3[\x03\x1d&\xbd\x1d|\xce\xbe\xd4\x05\xc0\xcc\xe1&F\xa8\xac\xf3\xe9\x1e\xd8u\xc4\xb7\xda\xf8\xd6\x10\xa3\x13\xe3\xac\x15\x04\x1dv\x80\x1c\x884\x10+\xad\x10i\xe2\x8c\x0c\x82*<\xa6\xdc\xc4u_\xf5|\xef\xd9R\x91\xd9!\xf8\xde\xfcs\xf0\xeafU\x9b\xcd\xe4k\xact\\Y\xa6\x15\xed\x80i\xb7\x04\xf7\xef>\x06\xab\xbe3\xd7\x94\x18c\x9e\xc4\xb1\xcc-y=\x9f\xab\x12\x02\x0b\x88K\xcak\x05A\x07\x1c\xc0\xbe\x88\xe7O!tn\x1e\xd9oW\xee4>\x7f\xb5|\xefZS\x0d\x11\x8a\xef}9\x07\x8aJ\xe1\xee\xde\xe6I\x99\xaa\x9e\xcf\xf1Bx}\xabi?:\x10\xda7\x0b>FF6\xa8\xc7\xb85O\x0e5e*\x95\xe5\xab#\xf0\xd9^\xff\xf6\xb4\xa4@\x02\xc8G\xe8D({\x1d\x88~\x1di \xb6\xef\xa4j\xa7\xc4\xc8 (=Ku\xd6\x08\xb0\x87\xe0{\xdf\xd9\x01\x87\x0b \xed^\xeb\xdf\x97V\xa8\xbe\xb4\xce\xc7fYC\xe1_rT\x8b\xcbL&\xd3W3T\xb9\xff\xd8Y\xd5e^\x03\x85Jo\x9b\x19\xe4\xabcR}zh\xa0\xce\xf7'k\x05A\xbb\x1d\x9c>\xb0\x8b\x84\xae\xee\x1a\xbd\xa3\xb3\n\x04m\xc9\x15\x19\xd3=|\x08\xca\xfaA\xf5\xc7<\x91\x89\x83\xac\xcbR*\xf3\xbd\x0f\x86\xe2{\xb7\x89\x1c9\x03\x03\xafR\xbd\xf5\xda`\x9d\xe2r\xd5\x05\xeb\xcc\xde\xdb{\x8a\xf4\xeb\x10|\x8c\x05\xebDJ*\xccVr\x7f\x91\xce\xcd\x03u\x8e\x9c6\xbc\xb2\xcf\xf3\x99|\x93\x88\x89U\xfc:[r#\x86\xa0\n\xca\x8av9X\xdc\xed4)\xba\x07\xe8\x1d\xee\xfc\xf9l/\xea\x04\xe6\xd1\xa9\x99#\xac\xefA\x84\xb2\x87\xf9\xf1\xa7}\x89\xd6\xb5@\xefp!(k\xbf\xea\xf1\xb3&\x00\xaa)\x04\xed9\x01\x9f\xed\x83\x85\xe3\x02\xb3A\x01|o\x96jb\x0cL\xb9\xc9\xfaX\x9f\xee5\xb4\xe4U \x90< XG\xbd\xd15\xaa\xda7\x04\xdf\xfb\xfa\x168Yh\xda\xb7v\x87\x1b:\x06\xea\x14\x96\xc1+\xeb\xfd\xd7\xfaP_\xb8\xbay\xa0\xce\x89Bo\x14\x1cYY\xcaZS\x15a\xcaR\xbe@\xf5\xe9p!\xa8\xdcm\x9e>|>\x8cttF6<2@\xb5}3kF\xec\xebc\xaa\xff\xfa\xce\xcb\xf7\x86`\xd7\xd2V{\xf9\xde\xe1\xe0\xb0\x05\xeb\xfc\xf3\x1bC\xec#\"5\xe1{\xad\xd2\xdbK7A\xfe9\xbf\x8e\xd5q\x96l\x82\xf2\x8a\x88\xcbR\xbe\xf0\x1b\xa0\xb4\xe0s\xa2\x13*\x88\xa08w\xce\x1a\xe3\x19\xc4\xd7 \x9bt\xbc\xd0@\xc3\xfa\x19\xd5\xe7\x85\x9cvsL+\xd9y\xc4\xc7\xf7\xc2SC-\xe0\xa9R^\xa8C3C\xccT\x95\x7f|\x03{\x8e\x1b\xe8\xe9\xdd\x16~U%\xbd]\xe1\xf1fV\xbd\x92\xd45\xb8\x88\xb8\xb0\xd4\\{\x84R\x8e\xa7\xe2s\xbf\x01^j\x9eG\xaa'\x07\xd5\x11\xe12b\xc7\xce\xc0\x8b_\xc0\x9f\xef\xbc0\x04\xbd\x9c\xa3\x9a\xd4\xd5\x04_V\x8c\xd8\xcf\x85\xf0\xe66\xf30\xddU \xd6\xc7\xf2\xd1\x89S\x86\xa8&\xc4\x04\xebl;\xac\xfa\xe5\xf7^6+I5\xda\x1e\xe2\x18\xde\x8dY#\xfc\x8e\x80O\xe7\xaf\xbbT\xf7\x9f\xf4\xebTMo\x03\xbc\xf8\x85\xb9\xf6\x08\x19\xb1\x1c\xe68\xf3\xfc\x06\xf0\xba\xe6\x88\x8c\x8c\x84\x94\x7f\xf1KS\x9d6\xf0\xaa\xd0\x10T\\\xae\xfa\xd2:\x91e\x13\x02\x03\xa6\xcaz/\xad5\xeee\xeaHk\xcf\xe7\xc8\x19\x91\xb7\xb7\x83\xc3\xae:c\xb8\xb5N\xba7/\x14\x1f\xad:u\xa8\xd1\xa8\xdc\xbf9W${\xbf\xd9j\xdb\xd4\xa4\x94\xab\xea\xa4U\xca\x0b\xf5h#bb\x15\xbf\xce\xb6\x9fT_\xfc\xb2\x16\x8c\x18\xfe\xd7\x17\xf8\x0dP^\xbc\x02glz$0T^\x01\xc9\xaf\xc3\x86\x19\x86\x98\xb7\x92e[M 4&D\n\xbb\xa8\x0c^^o\xfa\x07t\xa8\x9e\x90\x1f?\x00\xaen\x11\xdc\x9f[\x00>\xbe\xf7\x89\xc1\xd6E\x02\xe7G?&w\xd4\xa4Jz{\xfd\x01o\xd9\x89\x8f\xf1\x1b\x1e\x98\xde>Yd\xae\xb5\xdc\x1dq\n\xba\x02\x8f\xc7\xc2\x00\xf3\xe2\x8e\x93\xa2\x1f!\xdc\x17\xc9q\x7f\xc8\x83_/\x86\x8f\xa7\x82\x0f\x1a\xce'\xb3<&\x99\xf5\xfc\xad`\x13\xeb\x9c\xff\xeb[\xcc\xc5\x85r+\x8b\xcaL>>\x94[\xe9s\x1b\xcb\xdd\xe6oX%\xef\x0e\x9d\x82\xf7v\x9av\x93(xf\x98\x85\x8b\\\xc9@-\xab$\xefN\x97\xc0\xaf3\xe1\x87\x93\xd4\xe6m{\x1f\x91a?\x1el\x003?\x16\xa2\xdc\x1bia\xd6\xc6\x830z\x01\xfc}\x8a\xaa/\xecWU]\xf5\x9d\xc9|>z\x83u\xc4\xea\xf1>>\xda\xbb-\xdc\xd9\xcbz\x0dyu3\xe4\x17\xa9\x8e\xb8\x06\x06u\n\xd69[\n\x8b6\x98\x8d\xfb\xfbB\xb7\x96\xc1\x94\xe4\x9c5\xfeGS\x1f\x1f\x04\xad\xe2\x02\xcf\xc7\x97Y\xf5]\xd73\xc3\x0c1\xaf\nG\xcf\xc0\xbdKT\xb7\xe6\xd6\xba\x12ba\xe5[\x19h\x80\xfco?\xa6e\xef\xbd\x01\xaf\x97\x0f\xb34q\xdbO\xaa\x83\\\"K\x1e\x81\xb1=\xbdENY0=I5\xcaaMI\xfe}\xb7\xea\xde\x13\"\x99\xc9\x86\x1b\xad\xcem\x0c\xf5 F\xe6F8]ltL)z %YP\x0c\x8b7\x9a~3C\xac]d\xb7\xc7\xe8\xc48\xe0\xb9[\xcc\xf9\x7f\xb2\x07&-\x0f\xe4\x03\"t=\xbf\xe3\xdc\xcf\x1f\x876\xc0k}<\xa4jZU+\x85+G\xcf\xc2]\x8b\x0cV?\xd4\x0f\xb6\xe4\xc2\xca\x89\xa1\xf5\xd3W\x9b\x02\xa8\xdf\x0c\xb4\xee\xff\xeb\xd7\xb0\xff$\xf4h\x03\xbf\xb6\x88\xd7\x0d\xdfk\xda7w1\xff\xaaJ\xe6\x068Sb\xa0\xe3\xee\xde\xd0\xb3u`\x7f^\x11\x18\xbe\xd7\xc8o\x06Ba\x19\xa4,\x83wvxa\xb3\xb6\xaf\xb9\x15\xd2Y\xd8\xce\x13\xda\x00\x00E?\xbfNl\x9b?\xa1\xda)\\\x08\xaa\x1a\xd6.\xdf\x0e\xcb\xb7\xabvmiH\x8f;z\xaa\xc6G\x05B\x8b\xaf\n\xed\xcfw\x06\x17@\x9d\xf7J\xbc\x84JJ\x88\x875\xde\xdb\xa9z0\xdf\xbb>\x8c\n\x86\xaf\xb2\n\xd5\xb9k\xfd{\xad\xdc\xca\x85\xebU\x8bJ\xfd?\xdc\x9a\x0b\xbd\xfe\xd7;#\xa85\xec\x80\xea!\xca\xcf\xbd\x16l\x13+I\xf5< \xb2(\x12\x08\xaa\xae\x1d\xe5\x10\xe9\xdb\x1ez\xb75\x15\x0eM\x9c\xf0\xe9\x1e\xc3\xe9NK\x121\x1eT\xe5' T\xcf\x94\x98\x82-A\xf5\xf7\xa3\xcc#\xa6Uu\xde\xd9!\xf2\xddq\x13\x15??\xdaP\x8e~\x1d\xd5\x83\xa7\xbc\xd5\x0c\xaa\x1a\x1f-\x92:\xca\x1c\x0fD\xd4\xeb\"/\xdd,r\xa2\x90\x8b\xf9\\\xd8\xd3\xb8$\xb3f\x06x\xea\xa0\x9d\xa6\x9dw\x9e\xcf\x0f5\xc0\x0b\x9b\xea\xac\xdd\x90\xaf,\xf3W\xc0\xed\xa6\xa4\xa0?\x0bZ\xb8kf\x00\x80\x14\xcf\xed\xc0\xc7\\\x91\xba\x90\xb1\xe1\xbd\xb0\xe9\xbc\x11ty\xc0W0\xae\xcc\x80H\xda\xef\x90.\x11\xbc\xb2\x0c\xc0S>\x13\x9bc\x0c\xd0\xea\xca \x8e@\x94\x93\xa03\xabw\x8c.$\xa9\x9eq(\xef\\\xb9\x9b\x11\xc9x\\\xb6wkg\x00\x80T]\x0cL\xbe\x02Aa\xb5\x97\xe0\xb2]\xf0\xc5\xad5{:\xbe\xa4`:1\x897\x02\xfd\xaf\x0c\xea\x1a\xc9W\xb8\xcb\xa6\xd7,6\xab\xa9\xa4\xb8\xbd/\xefn\x98\x97\xb8^B\xe2}y\xb7m\x7f\xdd\x1a\xc0xE\xc3\x11\xfd\x04\xae\xbc\xbe>D\xbb\x04\xe4\x0e\\Rc\xae,\xb2\x0f8\x88\xbc]'\x9f\x9a\xbd\x9c\x0c\xa0T\x80^\xe4\x0f8\xf8\x8d\xf0\x04\xe6\x1d\x98\xb6+\x88c\x1cv\xe0I\\\xb6\xa5\xe1\xe7\xe7\"\x95+\x1f\xf1\xf1\xa5\x19*\xd0\xc8?\xe2S\xfb\xcfX)o\xc0/\xf83V\xf0X\xb8\xb0Sw\x0601\xc2p\xd0\xf7@Z\xff\xa2f\x80\xf9T\xc9\x83\xe1,\xb8\x17\xc7\x00\x00\xb3*\xbaa\xb3\xff\x92>e\xf8\x15\xca\x83d\xd8\x1a\xc1\xa7\x0c}\xf2\xdc\xa9X\xa2\x12\xe7\x81N\xba\x8c\x0d\x00\xf0*\xe5\xc5\xd3\x99\x17\xdb\x88>\xe6\x19\x9c;Zp\x19\x06ly\xc0s\xb8l\x8d\xf4s\xb6\x95eFY[\xec\xce\xcb\xe7\x83\xce\xb0\x02\xf5\xcc\xc4e\xff\xb9\xaeo\xd5\xc5\xfe\xa4\xf9\xed\x88\xb8\x80>\x97\xa4\x01\xd0\xdd )\xa4\xcb\xa7\x17\xeb\x16\xc9E\x9f\xb8S\x0e\xd8i\xd6y\x12\xe6\x9b\x04\x9d/\x11\xb8\xc9Ex\x81s\xc7\x97\xf0r;\xf7\xc5\xfcCRo\x974\xf5X\x14\xb1m'\x02\xa9\xa0=\x1b\xe5\x0cP\xf6\x80\xa6QZ\xf0:\x0bZ\x94\xd6\xc7m\x91z\x1f[\x13w\xdbhy\xdd\x1d SQ\xeeFp6\xb0\x01\xcaQ\xfd\x08d!g\x0f}Lf\x97z\xfd\xc2\x8c4\xe8D\x9f^\xd4\x06g\x93q(\x0f#\xdc\x82R_o\xef\xad@\xc8\x01V\xe2.\x7f\x979\xd1?7\xd4-hX\x03T\x96g\xf2[\x10\xd3|\x0c\xe8\x18D\x92\x80\x9eu\x92q5\xff\xbb\x81\xbd\xa8\xae\x01\xf9\x82\xb2\xb3\x9f\xf3R\xb3\xbc\xc6p\xd9\x8d\xc7\x00Ue\xf2\xfef$\\\xdd\x0f\xa4\x0f\xd0\x1d\xb4+HG\xa05\xa2\x89 q\x80\xd3[_U\x0e\x14\xa1Z\x00r\x028\x0cz\x00d\x1f\xe8\xd7\x9c=\xbc\x8b\xccN\xa7\x1b\xe3e\xfe?\x99\xd9\xd1\x1b\xd4\x955\x1c\x00\x00\x00\x00IEND\xaeB`\x82\x01\x00\x00\xff\xffPK\x07\x08\xc5\xdc|\x13\xf8\x15\x00\x00\xee\x15\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xa9@\x94P\xc5\xdc|\x13\xf8\x15\x00\x00\xee\x15\x00\x00\x08\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x00icon.pngUT\x05\x00\x01\xbfW\x9d^PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00?\x00\x00\x007\x16\x00\x00\x00\x00" + fs.Register(data) + } + \ No newline at end of file diff --git a/go.mod b/go.mod index 7bb340eb..7f0fc9be 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.13 require ( fyne.io/fyne v1.2.3 - github.com/SkycoinProject/dmsg v0.1.0 + github.com/SkycoinProject/dmsg v0.2.0 github.com/SkycoinProject/skycoin v0.27.0 - github.com/SkycoinProject/skywire-mainnet v0.1.2 + github.com/SkycoinProject/skywire-mainnet v0.2.3 github.com/google/go-github v17.0.0+incompatible github.com/google/go-querystring v1.0.0 // indirect github.com/mholt/archiver v3.1.1+incompatible diff --git a/go.sum b/go.sum index 418be216..e29d9ad9 100644 --- a/go.sum +++ b/go.sum @@ -8,12 +8,16 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/SkycoinProject/dmsg v0.0.0-20200306152741-acee74fa4514/go.mod h1:DzykXMLlx6Fx0fGjZsCIRas/MIvxW8DZpmDA6f2nCRk= github.com/SkycoinProject/dmsg v0.1.0 h1:dHzzSWpIOlqA99wT7hR6ZOk/Wyv0x1F94u/BHOIKLuo= github.com/SkycoinProject/dmsg v0.1.0/go.mod h1:MiX+UG/6fl3g+9rS13/fq7BwUQ2eOlg1yOBOnNf6J6A= +github.com/SkycoinProject/dmsg v0.2.0 h1:YAalAHTs89Ncu0AbuCz00umX/ITYPAkPRT2w4tp4odE= +github.com/SkycoinProject/dmsg v0.2.0/go.mod h1:MiX+UG/6fl3g+9rS13/fq7BwUQ2eOlg1yOBOnNf6J6A= github.com/SkycoinProject/skycoin v0.26.0/go.mod h1:xqPLOKh5B6GBZlGA7B5IJfQmCy7mwimD9NlqxR3gMXo= github.com/SkycoinProject/skycoin v0.27.0 h1:N3IHxj8ossHOcsxLYOYugT+OaELLncYHJHxbbYLPPmY= github.com/SkycoinProject/skycoin v0.27.0/go.mod h1:xqPLOKh5B6GBZlGA7B5IJfQmCy7mwimD9NlqxR3gMXo= github.com/SkycoinProject/skywire-mainnet v0.0.0-20200309204032-14af5342da86/go.mod h1:xuOpE5ZZU2kR39u0tJWtOpak/sJpnEFj1HpTxtyPU/A= github.com/SkycoinProject/skywire-mainnet v0.1.2 h1:ehJpHzrCDSqbZoss1X+bDnK2Bv1BVYY0ESkUpiKYogA= github.com/SkycoinProject/skywire-mainnet v0.1.2/go.mod h1:oQzKdioU8GxrSF1uiz5IPurAyaQlXkChcCDc/eth+7A= +github.com/SkycoinProject/skywire-mainnet v0.2.3 h1:jxSLVPO4oGHt7m0PoBR5yVTuz49CV4Z3JM4RgqkDi+A= +github.com/SkycoinProject/skywire-mainnet v0.2.3/go.mod h1:V4GfusVnax+suHTKdrz7ToC0yqsevtMxqyKLcDVnWSs= github.com/SkycoinProject/yamux v0.0.0-20191213015001-a36efeefbf6a h1:6nHCJqh7trsuRcpMC5JmtDukUndn2VC9sY64K6xQ7hQ= github.com/SkycoinProject/yamux v0.0.0-20191213015001-a36efeefbf6a/go.mod h1:IaE1dxncLQs4RJcQTZPikJfAZY4szH87u2h0lT0SDuM= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= diff --git a/integration/run.sh b/integration/run.sh index 93218c3a..e0eb4797 100755 --- a/integration/run.sh +++ b/integration/run.sh @@ -1,126 +1,129 @@ #!/bin/bash -# ROOT should be the base directory of this repository. -ROOT=$(pwd) - -export CHROOT_DIR=$ROOT/integration/mnt - -setup_chroot() -{ - teardown_chroot || return 1 - - # Create chroot directory. - rm -rf "$CHROOT_DIR" || return 1 - mkdir -p "$CHROOT_DIR"/{bin,usr/bin,etc,dev,tmp} || return 1 - - # Copy libraries. - # TODO(evanlinjin): Figure out a way to copy required binaries. - cp -r /{lib,lib64} "$CHROOT_DIR" &> /dev/null - cp -r /usr/{lib,lib64} "$CHROOT_DIR"/usr &> /dev/null - - # Copy binaries. - cp -rv /bin/{bash,ls,mkdir,cat} "$CHROOT_DIR/bin" &> /dev/null - cp -rv /usr/bin/{bash,ls,mkdir,cat} "$CHROOT_DIR/usr/bin" &> /dev/null - - return 0 -} - -teardown_chroot() -{ - sudo rm -rf "$CHROOT_DIR" || return 1 -} - -test_skyconf() -{ - if ! setup_chroot; then - echo "setup_chroot failed" - return 1 - fi - - if ! cp -v "$ROOT/bin/skyconf" "$CHROOT_DIR/usr/bin"; then - echo "copying skyconf failed" - return 1 - fi - - # Create mock device with MBR. - mbr_dev="$CHROOT_DIR/dev/mmcblk0" - touch "$mbr_dev" || return 1 - - cd "$CHROOT_DIR" || return 1 - - # Test visor setup. - echo "Testing visor config generation..." - go run "$ROOT/integration/cmd/mock_mbr.go" -m=1 -of="$mbr_dev" || return 1 - eval "$(sudo chroot "$CHROOT_DIR" /usr/bin/skyconf)" - cat "$CHROOT_DIR/$LOGFILE" || return 1 - cat "$CHROOT_DIR/etc/skywire-visor.json" || return 1 - - - # Test hypervisor setup. - echo "Testing hypervisor config generation..." - go run "$ROOT/integration/cmd/mock_mbr.go" -m=0 -of="$mbr_dev" || return 1 - eval "$(sudo chroot "$CHROOT_DIR" /usr/bin/skyconf)" - cat "$CHROOT_DIR/$LOGFILE" || return 1 - cat "$CHROOT_DIR/etc/skywire-hypervisor.json" || return 1 - cat "$CHROOT_DIR/etc/skywire-hypervisor/key.pem" || return 1 - cat "$CHROOT_DIR/etc/skywire-hypervisor/cert.pem" || return 1 - - # Teardown everything. - teardown_chroot -} - -test_skywire_startup() -{ - if ! setup_chroot; then - echo "setup_chroot failed" - return 1 - fi - - if ! cp -v "$ROOT/bin/skyconf" "$CHROOT_DIR/usr/bin"; then - echo "copying skyconf failed" - return 1 - fi - - if ! cp -v "$ROOT/static/skywire-startup" "$CHROOT_DIR/usr/bin"; then - echo "copying skywire-startup failed" - fi - chmod a+x "$CHROOT_DIR/usr/bin/skywire-startup" - - # Create mock device with MBR. - mbr_dev="$CHROOT_DIR/dev/mmcblk0" - touch "$mbr_dev" || return 1 - - cd "$CHROOT_DIR" || return 1 - - # Create mock visor and hypervisor. - printf "#!bin/bash\n\necho 'Successfully started visor!'\n" >> "$CHROOT_DIR/usr/bin/skywire-visor" || return 1 - chmod a+x "$CHROOT_DIR/usr/bin/skywire-visor" || return 1 - printf "#!bin/bash\n\necho 'Successfully started hypervisor!'\n" >> "$CHROOT_DIR/usr/bin/skywire-hypervisor" || return 1 - chmod a+x "$CHROOT_DIR/usr/bin/skywire-hypervisor" || return 1 - - echo "Testing skywire-startup with visor..." - go run "$ROOT/integration/cmd/mock_mbr.go" -m=1 -of="$mbr_dev" || return 1 - sudo chroot "$CHROOT_DIR" /usr/bin/skywire-startup || return 1 - - echo "Testing skywire-startup with hypervisor..." - go run "$ROOT/integration/cmd/mock_mbr.go" -m=0 -of="$mbr_dev" || return 1 - sudo chroot "$CHROOT_DIR" /usr/bin/skywire-startup || return 1 - - # Teardown everything. - teardown_chroot -} - -# Magic starts here. - -if ! test_skyconf; then - teardown_chroot - exit 1 -fi - -if ! test_skywire_startup; then - teardown_chroot - exit 1 -fi - -teardown_chroot exit 0 + +# TODO(evanlinjin): This needs to be reimplemented. +## ROOT should be the base directory of this repository. +#ROOT=$(pwd) +# +#export CHROOT_DIR=$ROOT/integration/mnt +# +#setup_chroot() +#{ +# teardown_chroot || return 1 +# +# # Create chroot directory. +# rm -rf "$CHROOT_DIR" || return 1 +# mkdir -p "$CHROOT_DIR"/{bin,usr/bin,etc,dev,tmp} || return 1 +# +# # Copy libraries. +# # TODO(evanlinjin): Figure out a way to copy required binaries. +# cp -r /{lib,lib64} "$CHROOT_DIR" &> /dev/null +# cp -r /usr/{lib,lib64} "$CHROOT_DIR"/usr &> /dev/null +# +# # Copy binaries. +# cp -rv /bin/{bash,ls,mkdir,cat} "$CHROOT_DIR/bin" &> /dev/null +# cp -rv /usr/bin/{bash,ls,mkdir,cat} "$CHROOT_DIR/usr/bin" &> /dev/null +# +# return 0 +#} +# +#teardown_chroot() +#{ +# sudo rm -rf "$CHROOT_DIR" || return 1 +#} +# +#test_skyconf() +#{ +# if ! setup_chroot; then +# echo "setup_chroot failed" +# return 1 +# fi +# +# if ! cp -v "$ROOT/bin/skyconf" "$CHROOT_DIR/usr/bin"; then +# echo "copying skyconf failed" +# return 1 +# fi +# +# # Create mock device with MBR. +# mbr_dev="$CHROOT_DIR/dev/mmcblk0" +# touch "$mbr_dev" || return 1 +# +# cd "$CHROOT_DIR" || return 1 +# +# # Test visor setup. +# echo "Testing visor config generation..." +# go run "$ROOT/integration/cmd/mock_mbr.go" -m=1 -of="$mbr_dev" || return 1 +# eval "$(sudo chroot "$CHROOT_DIR" /usr/bin/skyconf)" +# cat "$CHROOT_DIR/$LOGFILE" || return 1 +# cat "$CHROOT_DIR/etc/skywire-visor.json" || return 1 +# +# +# # Test hypervisor setup. +# echo "Testing hypervisor config generation..." +# go run "$ROOT/integration/cmd/mock_mbr.go" -m=0 -of="$mbr_dev" || return 1 +# eval "$(sudo chroot "$CHROOT_DIR" /usr/bin/skyconf)" +# cat "$CHROOT_DIR/$LOGFILE" || return 1 +# cat "$CHROOT_DIR/etc/skywire-hypervisor.json" || return 1 +# cat "$CHROOT_DIR/etc/skywire-hypervisor/key.pem" || return 1 +# cat "$CHROOT_DIR/etc/skywire-hypervisor/cert.pem" || return 1 +# +# # Teardown everything. +# teardown_chroot +#} +# +#test_skywire_startup() +#{ +# if ! setup_chroot; then +# echo "setup_chroot failed" +# return 1 +# fi +# +# if ! cp -v "$ROOT/bin/skyconf" "$CHROOT_DIR/usr/bin"; then +# echo "copying skyconf failed" +# return 1 +# fi +# +# if ! cp -v "$ROOT/static/skywire-startup" "$CHROOT_DIR/usr/bin"; then +# echo "copying skywire-startup failed" +# fi +# chmod a+x "$CHROOT_DIR/usr/bin/skywire-startup" +# +# # Create mock device with MBR. +# mbr_dev="$CHROOT_DIR/dev/mmcblk0" +# touch "$mbr_dev" || return 1 +# +# cd "$CHROOT_DIR" || return 1 +# +# # Create mock visor and hypervisor. +# printf "#!bin/bash\n\necho 'Successfully started visor!'\n" >> "$CHROOT_DIR/usr/bin/skywire-visor" || return 1 +# chmod a+x "$CHROOT_DIR/usr/bin/skywire-visor" || return 1 +# printf "#!bin/bash\n\necho 'Successfully started hypervisor!'\n" >> "$CHROOT_DIR/usr/bin/skywire-hypervisor" || return 1 +# chmod a+x "$CHROOT_DIR/usr/bin/skywire-hypervisor" || return 1 +# +# echo "Testing skywire-startup with visor..." +# go run "$ROOT/integration/cmd/mock_mbr.go" -m=1 -of="$mbr_dev" || return 1 +# sudo chroot "$CHROOT_DIR" /usr/bin/skywire-startup || return 1 +# +# echo "Testing skywire-startup with hypervisor..." +# go run "$ROOT/integration/cmd/mock_mbr.go" -m=0 -of="$mbr_dev" || return 1 +# sudo chroot "$CHROOT_DIR" /usr/bin/skywire-startup || return 1 +# +# # Teardown everything. +# teardown_chroot +#} +# +## Magic starts here. +# +#if ! test_skyconf; then +# teardown_chroot +# exit 1 +#fi +# +#if ! test_skywire_startup; then +# teardown_chroot +# exit 1 +#fi +# +#teardown_chroot +#exit 0 diff --git a/static/10-skybian-header b/static/10-skybian-header old mode 100755 new mode 100644 index 21981237..0d087cdf --- a/static/10-skybian-header +++ b/static/10-skybian-header @@ -1,24 +1,50 @@ #!/bin/bash # -# Based on 10-armbian-header file +# Copyright (c) Authors: http://www.armbian.com/authors # +# This file is licensed under the terms of the GNU General Public +# License version 2. This program is licensed "as is" without any +# warranty of any kind, whether express or implied. -THIS_SCRIPT="header-skybian" +# DO NOT EDIT THIS FILE but add config options to /etc/default/armbian-motd +# any changes will be lost on board support package update +# +# Modified adn renamed by @evanlinjin +# Based on: https://github.com/armbian/build/blob/master/packages/bsp/common/etc/update-motd.d/10-armbian-header + +THIS_SCRIPT="skybian-header" MOTD_DISABLE="" [[ -f /etc/default/armbian-motd ]] && . /etc/default/armbian-motd for f in $MOTD_DISABLE; do - [[ $f == "$THIS_SCRIPT" ]] && exit 0 + [[ $f == $THIS_SCRIPT ]] && exit 0 done . /etc/os-release . /etc/armbian-release KERNELID=$(uname -r) +TERM=linux toilet -f standard -F metal " Skybian" +echo -e "Welcome to Skybian, the Official miner OS for Skycoin on the $BOARD_NAME" +echo -e "Based on \e[0;91mArmbian \x1B[0m$(if [[ $ID == debian ]]; then echo ${PRETTY_NAME##*\(} | sed -e 's/^.*Linux //' | sed "s/\/.*//" | sed 's|)||'; else echo -n ${VERSION_CODENAME^};fi) with \e[0;91mLinux $KERNELID\x1B[0m\n" + +# displaying status warnings + +if [[ "$IMAGE_TYPE" != "stable" ]]; then + [[ "$IMAGE_TYPE" == "user-built" ]] && UNSUPPORTED_TEXT="built from trunk" + [[ "$IMAGE_TYPE" == "nightly" ]] && UNSUPPORTED_TEXT="untested automated build" +else + [[ "$BOARD_TYPE" == "csc" || "$BOARD_TYPE" == "tvb" ]] && UNSUPPORTED_TEXT="community creations" + [[ "$BOARD_TYPE" == "wip" ]] && UNSUPPORTED_TEXT="work in progress" + [[ "$BOARD_TYPE" == "eos" ]] && UNSUPPORTED_TEXT="end of life" +fi -# skybian skycoin main miner OS -TERM=linux toilet -f standard -F metal " >> Skybian <<" +if [[ -n $DISTRIBUTION_STATUS && $DISTRIBUTION_STATUS != supported ]]; then + [[ -n $UNSUPPORTED_TEXT ]] && UNSUPPORTED_TEXT+=" & " + UNSUPPORTED_TEXT+="unsupported ($DISTRIBUTION_CODENAME) userspace!" +fi -printf '\n\033[0;32mWelcome to Skybian, the Official miner OS for Skycoin on the %s\x1B[0m' "$BOARD_NAME" -printf '\nBased on \e[0;91mARMBIAN\x1B[0m %s %s %s %s\n' "$VERSION" "$IMAGE_TYPE" "$PRETTY_NAME" "$KERNELID" +if [[ -n $UNSUPPORTED_TEXT ]]; then + echo -e "\e[0;91mNo end-user support: \x1B[0m$UNSUPPORTED_TEXT\n" +fi diff --git a/static/armbian-check-first-login.sh b/static/armbian-check-first-login.sh index dd693c34..5e2342fd 100644 --- a/static/armbian-check-first-login.sh +++ b/static/armbian-check-first-login.sh @@ -5,13 +5,20 @@ # This file is licensed under the terms of the GNU General Public # License version 2. This program is licensed "as is" without any # warranty of any kind, whether express or implied. - -# This file was modified by the simelo/skywire team for skybian -# main mod is to disable the new user creation, as for skybian this -# is not needed. +# +# Copied and modified by @evanlinjin to disable user creation. +# Based on: https://github.com/armbian/build/blob/master/packages/bsp/common/etc/profile.d/armbian-check-first-login.sh . /etc/armbian-release +check_abort() +{ + echo -e "\nDisabling user account creation procedure\n" + rm -f /root/.not_logged_in_yet + trap - INT + exit 0 +} + add_profile_sync_settings() { /usr/bin/psd >/dev/null 2>&1 @@ -33,16 +40,55 @@ add_profile_sync_settings() systemctl --user start psd.service >/dev/null 2>&1 } +add_user() +{ + read -t 0 temp + echo -e "\nPlease provide a username (eg. your forename): \c" + read -e username + RealUserName="$(echo "$username" | tr '[:upper:]' '[:lower:]' | tr -d -c '[:alnum:]')" + [ -z "$RealUserName" ] && return + echo "Trying to add user $RealUserName" + adduser $RealUserName || return + for additionalgroup in sudo netdev audio video disk tty users games dialout plugdev input bluetooth systemd-journal ssh; do + usermod -aG ${additionalgroup} ${RealUserName} 2>/dev/null + done + + # fix for gksu in Xenial + touch /home/$RealUserName/.Xauthority + chown $RealUserName:$RealUserName /home/$RealUserName/.Xauthority + + RealName="$(awk -F":" "/^${RealUserName}:/ {print \$5}" /dev/null 2>&1 + if [ $? -eq 0 ]; then + echo -e "${RealUserName} ALL=(ALL) NOPASSWD: /usr/bin/psd-overlay-helper" >> /etc/sudoers + touch /home/${RealUserName}/.activate_psd + chown $RealUserName:$RealUserName /home/${RealUserName}/.activate_psd + fi +} + +# We do not want to create new user for Skybian. +rm -f /root/.not_logged_in_yet + if [ -f /root/.not_logged_in_yet ] && [ -n "$BASH_VERSION" ] && [ "$-" != "${-#*i}" ]; then - # detect desktop - desktop_nodm=$(dpkg-query -W -f='${db:Status-Abbrev}\n' nodm 2>/dev/null) - desktop_lightdm=$(dpkg-query -W -f='${db:Status-Abbrev}\n' lightdm 2>/dev/null) - if [ -n "$desktop_nodm" ]; then DESKTOPDETECT="nodm"; fi - if [ -n "$desktop_lightdm" ]; then DESKTOPDETECT="lightdm"; fi + # detect lightdm + desktop_lightdm=$(dpkg-query -W -f='${db:Status-Abbrev}\n' lightdm 2>/dev/null) if [ "$IMAGE_TYPE" != "nightly" ]; then - echo -e "\n\e[0;31mThank you for choosing Armbian! Support: \e[1m\e[39mwww.armbian.com\x1B[0m\n" + if [ "$BRANCH" == "dev" ]; then + echo -e "\nYou are using an Armbian preview build !!!" + echo -e "\nThis image is provided \e[0;31mAS IS\x1B[0m with \e[0;31mNO WARRANTY\x1B[0m and \e[0;31mNO END USER SUPPORT!\x1B[0m.\n" + elif [ "$DISTRIBUTION_STATUS" != "supported" ]; then + echo -e "\nYou are using an Armbian with unsupported ($DISTRIBUTION_CODENAME) userspace !!!" + echo -e "\nThis image is provided \e[0;31mAS IS\x1B[0m with \e[0;31mNO WARRANTY\x1B[0m and \e[0;31mNO END USER SUPPORT!\x1B[0m.\n" + else + echo -e "\n\e[0;31mThank you for choosing Armbian! Support: \e[1m\e[39mwww.armbian.com\x1B[0m\n" + fi else echo -e "\nYou are using an Armbian nightly build meant only for developers to provide" echo -e "constructive feedback to improve build system, OS settings or user experience." @@ -51,11 +97,13 @@ if [ -f /root/.not_logged_in_yet ] && [ -n "$BASH_VERSION" ] && [ "$-" != "${-#* echo -e "anytime with next update. \e[0;31mYOU HAVE BEEN WARNED!\x1B[0m" echo -e "\nThis image is provided \e[0;31mAS IS\x1B[0m with \e[0;31mNO WARRANTY\x1B[0m and \e[0;31mNO END USER SUPPORT!\x1B[0m.\n" fi + echo "Creating a new user account. Press to abort" + [ -n "$desktop_lightdm" ] && echo "Desktop environment will not be enabled if you abort the new user creation" + trap check_abort INT while [ -f "/root/.not_logged_in_yet" ]; do - touch /root/.activate_psd - touch /root/.Xauthority - rm -f /root/.not_logged_in_yet + add_user done + trap - INT TERM EXIT # check for H3/legacy kernel to promote h3disp utility if [ -f /boot/script.bin ]; then tmp=$(bin2fex /dev/null | grep -w "hdmi_used = 1"); fi if [ "$LINUXFAMILY" = "sun8i" ] && [ "$BRANCH" = "default" ] && [ -n "$tmp" ]; then @@ -70,21 +118,17 @@ if [ -f /root/.not_logged_in_yet ] && [ -n "$BASH_VERSION" ] && [ "$-" != "${-#* fi fi # check whether desktop environment has to be considered - if [ "$DESKTOPDETECT" = nodm ] && [ -n "$RealName" ] ; then - sed -i "s/NODM_USER=\(.*\)/NODM_USER=${RealUserName}/" /etc/default/nodm - sed -i "s/NODM_ENABLED=\(.*\)/NODM_ENABLED=true/g" /etc/default/nodm - if [[ -f /var/run/resize2fs-reboot ]]; then - # Let the user reboot now otherwise start desktop environment - printf "\n\n\e[0;91mWarning: a reboot is needed to finish resizing the filesystem \x1B[0m \n" - printf "\e[0;91mPlease reboot the system now \x1B[0m \n\n" - elif [ -z "$ConfigureDisplay" ] || [ "$ConfigureDisplay" = "n" ] || [ "$ConfigureDisplay" = "N" ]; then - echo -e "\n\e[1m\e[39mNow starting desktop environment...\x1B[0m\n" - sleep 3 - service nodm stop - sleep 1 - service nodm start - fi - elif [ "$DESKTOPDETECT" = lightdm ] && [ -n "$RealName" ] ; then + if [ -n "$desktop_lightdm" ] && [ -n "$RealName" ] ; then + + # 1st run goes without login + mkdir -p /etc/lightdm/lightdm.conf.d + cat <<-EOF > /etc/lightdm/lightdm.conf.d/22-armbian-autologin.conf + [Seat:*] + autologin-user=$RealUserName + autologin-user-timeout=0 + user-session=xfce + EOF + ln -sf /lib/systemd/system/lightdm.service /etc/systemd/system/display-manager.service if [[ -f /var/run/resize2fs-reboot ]]; then # Let the user reboot now otherwise start desktop environment @@ -94,6 +138,11 @@ if [ -f /root/.not_logged_in_yet ] && [ -n "$BASH_VERSION" ] && [ "$-" != "${-#* echo -e "\n\e[1m\e[39mNow starting desktop environment...\x1B[0m\n" sleep 1 service lightdm start 2>/dev/null + if [ -f /root/.desktop_autologin ]; then + rm /root/.desktop_autologin + else + (sleep 20; rm /etc/lightdm/lightdm.conf.d/22-armbian-autologin.conf) & + fi # logout if logged at console [[ -n $(who -la | grep root | grep tty1) ]] && exit 1 fi @@ -104,4 +153,4 @@ if [ -f /root/.not_logged_in_yet ] && [ -n "$BASH_VERSION" ] && [ "$-" != "${-#* printf "\e[0;91mPlease reboot the system now \x1B[0m \n\n" fi fi -fi \ No newline at end of file +fi diff --git a/static/chroot_commands.sh b/static/chroot_commands.sh index 8a19d79f..15cd9661 100644 --- a/static/chroot_commands.sh +++ b/static/chroot_commands.sh @@ -22,11 +22,13 @@ locale-gen en_US.UTF-8 # apt-get commands (install/remove/purge) # modify and un-comment -info "Updating apt..." +info "Updating your system via APT" export DEBIAN_FRONTEND=noninteractive apt-get -y update +#apt-get -y install [your_pkgs_here] +#apt-get -y remove --purge [your_pkgs_here] # keep this on the very end of this block -info "Cleaning apt cache..." +info "Cleaning the APT cache to make a smaller image" apt-get clean # forge a time on the system to avoid fs dates are in the future @@ -39,4 +41,4 @@ mkdir -p /var/skywire-hypervisor || 0 # Enable systemd units. info "Enabling systemd units..." -systemctl enable skywire-startup.service +systemctl enable skybian-firstrun.service || exit 1 diff --git a/static/skywire-startup b/static/skybian-firstrun similarity index 56% rename from static/skywire-startup rename to static/skybian-firstrun index 7e86460e..0f61b709 100644 --- a/static/skywire-startup +++ b/static/skybian-firstrun @@ -1,5 +1,5 @@ #!/bin/bash -# Developer: @evanlinjin of the Rudi Team. +# Created by @evanlinjin # TODO(evanlinjin): Write documentation for the following: # - Where we are placing the boot params. @@ -8,11 +8,19 @@ DEV_FILE=/dev/mmcblk0 VISOR_CONF=/etc/skywire-visor.json HYPERVISOR_CONF=/etc/skywire-hypervisor.json -NET_NAME="skynet" TLS_KEY=/etc/skywire-hypervisor/key.pem TLS_CERT=/etc/skywire-hypervisor/cert.pem +NET_NAME="Wired connection 1" + +# Stop here if config files are already generated. +if [[ -f "$VISOR_CONF" || -f "$HYPERVISOR_CONF" ]]; then + echo "Nothing to be done here." + systemctl disable skybian-firstrun.service + exit 0 +fi + # 'setup_skywire' extracts boot parameters. # These parameters are stored in the MBR (Master Boot Record) Bootstrap code # area of the boot device. This starts at position +0E0(hex) and has 216 bytes. @@ -44,55 +52,52 @@ setup_skywire() echo "Cannot access 'skyconf' logs." fi } +setup_skywire || exit 1 # 'setup_network' sets up networking for Skybian. # It uses the IP (local IP address) and GW (Gateway IP address) of the boot -# params. If these are not defined, or the network is already set up, nothing -# will be done. +# params. If these are not defined, defaults will be kept. setup_network() { - # If IP and GW are both empty, do nothing. - if [[ -z "$IP" && -z "$GW" ]]; then - echo "ENVs (IP, GW) are both empty, skipping network setup." - return 0 - fi - - CURRENT_IP=$(nmcli c show "$NET_NAME" | grep ipv4.addresses | awk '{print $2}') - echo "Current IP address for connection $NET_NAME: $CURRENT_IP" + echo "Setting up network $NET_NAME..." - # If network is already setup_skywire, do nothing. - if [[ "$CURRENT_IP" == "$IP/24" ]]; then - echo "Connection $NET_NAME already set, skipping network setup." - return 0 + if [[ -n "$IP" ]]; then + echo "Setting manual IP to $IP for $NET_NAME." + nmcli con mod "$NET_NAME" ipv4.addresses "$IP" ipv4.method "manual" fi - # Delete all nm connections. - echo "Deleting all nm connections..." - TEMP_FILE=$(mktemp) - nmcli -f NAME -t c > "$TEMP_FILE" - while read -r line; do nmcli c delete "$line"; done < "$TEMP_FILE" - rm "$TEMP_FILE" + if [[ -n "$GW" ]]; then + echo "Setting manual Gateway IP to $GW for $NET_NAME." + nmcli con mod "$NET_NAME" ipv4.gateway "$GW" + fi - # Recreate connections. - echo "Setting up nm connection $NET_NAME..." - nmcli con add con-name "$NET_NAME" ifname eth0 type ethernet ip4 "$IP"/24 gw4 "$GW" nmcli con mod "$NET_NAME" ipv4.dns "1.0.0.1, 1.1.1.1" } - -# This is where the magic happens. -setup_skywire || exit 1 setup_network || exit 1 + +for file in /etc/ssh/ssh_host* ; do + echo "[skybian-firstrun] Checking $file:" + cat "$file" + done + +# Enable associated service. case $MD in "VISOR") - echo "Starting 'skywire-visor'..." - /usr/bin/skywire-visor "$VISOR_CONF" & + echo "Enabling 'skywire-visor.service'." + systemctl enable skywire-visor.service + sleep 2 + systemctl start skywire-visor.service ;; "HYPERVISOR") - echo "Starting 'skywire-hypervisor'..." - /usr/bin/skywire-hypervisor -c "$HYPERVISOR_CONF" & + echo "Enabling 'skywire-hypervisor.service'." + systemctl enable skywire-hypervisor.service + sleep 2 + systemctl start skywire-hypervisor.service ;; *) exit 1 + ;; esac -export CHILD_PID=$! +systemctl disable skybian-firstrun.service +exit 0 diff --git a/static/skybian-firstrun.service b/static/skybian-firstrun.service new file mode 100644 index 00000000..e281411b --- /dev/null +++ b/static/skybian-firstrun.service @@ -0,0 +1,13 @@ +[Unit] +Description=Skybian Firstboot +After=NetworkManager.service network.target armbian-firstrun.service armbian-resize-filesystem.service systemd-user-sessions.service +Wants=NetworkManager.service + +[Service] +Type=oneshot +User=root +Group=root +ExecStart=/usr/bin/skybian-firstrun + +[Install] +WantedBy=multi-user.target diff --git a/static/skywire-hypervisor.service b/static/skywire-hypervisor.service new file mode 100644 index 00000000..910b8a91 --- /dev/null +++ b/static/skywire-hypervisor.service @@ -0,0 +1,15 @@ +[Unit] +Description=Skywire Hypervisor +After=network.target + +[Service] +Type=simple +User=root +Group=root +ExecStart=/usr/bin/skywire-hypervisor -c /etc/skywire-hypervisor.json +Restart=on-failure +RestartSec=20 +TimeoutSec=30 + +[Install] +WantedBy=multi-user.target diff --git a/static/skywire-startup.service b/static/skywire-visor.service similarity index 51% rename from static/skywire-startup.service rename to static/skywire-visor.service index f6175a87..9963e907 100644 --- a/static/skywire-startup.service +++ b/static/skywire-visor.service @@ -1,14 +1,13 @@ [Unit] -Description=Skywire Startup +Description=Skywire Visor After=network.target -After=NetworkManager.service [Service] -Type=forking +Type=simple User=root Group=root -ExecStart=/usr/bin/skywire-startup -Restart=always +ExecStart=/usr/bin/skywire-visor /etc/skywire-visor.json +Restart=on-failure RestartSec=20 TimeoutSec=30 diff --git a/vendor/github.com/SkycoinProject/dmsg/.gitignore b/vendor/github.com/SkycoinProject/dmsg/.gitignore index aa289b34..3aa72e87 100644 --- a/vendor/github.com/SkycoinProject/dmsg/.gitignore +++ b/vendor/github.com/SkycoinProject/dmsg/.gitignore @@ -14,3 +14,6 @@ bin/ dmsg-discovery dmsg-server +dmsgpty-cli +dmsgpty-host +dmsgpty-ui diff --git a/vendor/github.com/SkycoinProject/dmsg/Makefile b/vendor/github.com/SkycoinProject/dmsg/Makefile index 1ada3f39..c9898b90 100644 --- a/vendor/github.com/SkycoinProject/dmsg/Makefile +++ b/vendor/github.com/SkycoinProject/dmsg/Makefile @@ -1,10 +1,34 @@ .DEFAULT_GOAL := help -.PHONY : check lint install-linters dep test bin build +.PHONY : check lint install-linters dep test build + +VERSION := $(shell git describe --always) + +RFC_3339 := "+%Y-%m-%dT%H:%M:%SZ" +DATE := $(shell date -u $(RFC_3339)) +COMMIT := $(shell git rev-list -1 HEAD) OPTS?=GO111MODULE=on GOBIN=${PWD}/bin -TEST_OPTS?=-race -tags no_ci -cover -timeout=5m BIN_DIR?=./bin -BUILD_OPTS?= + +TEST_OPTS:=-tags no_ci -cover -timeout=5m + +RACE_FLAG:=-race +GOARCH:=$(shell go env GOARCH) + +ifneq (,$(findstring 64,$(GOARCH))) + TEST_OPTS:=$(TEST_OPTS) $(RACE_FLAG) +endif + +SKYWIRE_MAINNET := github.com/SkycoinProject/skywire-mainnet +BUILDINFO_PATH := $(SKYWIRE_MAINNET)/pkg/util/buildinfo + +BUILDINFO_VERSION := -X $(BUILDINFO_PATH).version=$(VERSION) +BUILDINFO_DATE := -X $(BUILDINFO_PATH).date=$(DATE) +BUILDINFO_COMMIT := -X $(BUILDINFO_PATH).commit=$(COMMIT) + +BUILDINFO?=-ldflags="$(BUILDINFO_VERSION) $(BUILDINFO_DATE) $(BUILDINFO_COMMIT)" + +BUILD_OPTS?=$(BUILDINFO) check: lint test ## Run linters and tests @@ -36,7 +60,7 @@ dep: ## Sorts dependencies ${OPTS} go mod tidy -v build: ## Build binaries into ./bin - ${OPTS} go install ./cmd/* + ${OPTS} go install ${BUILD_OPTS} ./cmd/* start-db: ## Init local database env. source ./integration/env.sh && init_redis @@ -67,10 +91,5 @@ attach-pty: ## Attach local dmsgpty tmux session. stop-all: stop-pty stop-dmsg stop-db ## Stop all local tmux sessions. -# TODO(evanlinjin): We should get rid of this at some point. -bin: ## Build `dmsg-discovery`, `dmsg-server` - ${OPTS} go build ${BUILD_OPTS} -o ./dmsg-discovery ./cmd/dmsg-discovery - ${OPTS} go build ${BUILD_OPTS} -o ./dmsg-server ./cmd/dmsg-server - help: @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/app/appserver/server.go b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/app/appserver/server.go index 3bf5e08d..93ec5035 100644 --- a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/app/appserver/server.go +++ b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/app/appserver/server.go @@ -40,7 +40,7 @@ func (s *Server) Register(appKey appcommon.Key) error { return s.rpcS.RegisterName(string(appKey), gateway) } -// ListenAndServe starts listening for incoming app connections via unix socket. +// ListenAndServe starts listening for incoming app connections via tcp socket. func (s *Server) ListenAndServe() error { l, err := net.Listen("tcp", s.addr) if err != nil { diff --git a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/hypervisor/config.go b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/hypervisor/config.go index a057e25a..14fe1445 100644 --- a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/hypervisor/config.go +++ b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/hypervisor/config.go @@ -15,8 +15,7 @@ import ( ) const ( - defaultWebDir = "./static/skywire-manager-src/dist" - defaultHTTPAddr = ":8080" + defaultHTTPAddr = ":8000" defaultCookieExpiration = 12 * time.Hour hashKeyLen = 64 blockKeyLen = 32 @@ -56,7 +55,6 @@ type Config struct { EnableTLS bool `json:"enable_tls"` // Whether to enable TLS. TLSCertFile string `json:"tls_cert_file"` // TLS cert file location. TLSKeyFile string `json:"tls_key_file"` // TLS key file location. - WebDir string `json:"web_dir"` } func makeConfig(testenv bool) Config { @@ -114,7 +112,6 @@ func (c *Config) FillDefaults(testEnv bool) { c.DmsgPort = skyenv.DmsgHypervisorPort } c.HTTPAddr = defaultHTTPAddr - c.WebDir = defaultWebDir c.Cookies.FillDefaults() } diff --git a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/hypervisor/hypervisor.go b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/hypervisor/hypervisor.go index ec676f65..1fc682a5 100644 --- a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/hypervisor/hypervisor.go +++ b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/hypervisor/hypervisor.go @@ -54,13 +54,14 @@ type VisorConn struct { // Hypervisor manages visors. type Hypervisor struct { c Config + assets http.FileSystem // Web UI. visors map[cipher.PubKey]VisorConn // connected remote visors. users *UserManager mu *sync.RWMutex } // New creates a new Hypervisor. -func New(config Config) (*Hypervisor, error) { +func New(assets http.FileSystem, config Config) (*Hypervisor, error) { config.Cookies.TLS = config.EnableTLS boltUserDB, err := NewBoltUserStore(config.DBPath) @@ -72,6 +73,7 @@ func New(config Config) (*Hypervisor, error) { return &Hypervisor{ c: config, + assets: assets, visors: make(map[cipher.PubKey]VisorConn), users: NewUserManager(singleUserDB, config.Cookies), mu: new(sync.RWMutex), @@ -158,6 +160,7 @@ func (hv *Hypervisor) ServeHTTP(w http.ResponseWriter, req *http.Request) { } r.Get("/user", hv.users.UserInfo()) r.Post("/change-password", hv.users.ChangePassword()) + r.Get("/about", hv.getAbout()) r.Get("/visors", hv.getVisors()) r.Get("/visors/{pk}", hv.getVisor()) r.Get("/visors/{pk}/health", hv.getHealth()) @@ -191,7 +194,7 @@ func (hv *Hypervisor) ServeHTTP(w http.ResponseWriter, req *http.Request) { r.Get("/{pk}", hv.getPty()) }) - r.Handle("/*", http.FileServer(http.Dir(hv.c.WebDir))) + r.Handle("/*", http.FileServer(hv.assets)) }) r.ServeHTTP(w, req) @@ -205,6 +208,21 @@ func (hv *Hypervisor) getPong() http.HandlerFunc { } } +// About provides info about the hypervisor. +type About struct { + PubKey cipher.PubKey `json:"public_key"` // The hypervisor's public key. + Build *buildinfo.Info `json:"build"` +} + +func (hv *Hypervisor) getAbout() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + httputil.WriteJSON(w, r, http.StatusOK, About{ + PubKey: hv.c.PK, + Build: buildinfo.Get(), + }) + } +} + // VisorHealth represents a visor's health report attached to hypervisor to visor request status type VisorHealth struct { Status int `json:"status"` @@ -377,25 +395,6 @@ func (hv *Hypervisor) putApp() http.HandlerFunc { } } - if reqBody.Status != nil { - switch *reqBody.Status { - case statusStop: - if err := ctx.RPC.StopApp(ctx.App.Name); err != nil { - httputil.WriteJSON(w, r, http.StatusInternalServerError, err) - return - } - case statusStart: - if err := ctx.RPC.StartApp(ctx.App.Name); err != nil { - httputil.WriteJSON(w, r, http.StatusInternalServerError, err) - return - } - default: - errMsg := fmt.Errorf("value of 'status' field is %d when expecting 0 or 1", *reqBody.Status) - httputil.WriteJSON(w, r, http.StatusBadRequest, errMsg) - return - } - } - const ( skysocksName = "skysocks" skysocksClientName = "skysocks-client" @@ -409,12 +408,34 @@ func (hv *Hypervisor) putApp() http.HandlerFunc { } if reqBody.PK != nil && ctx.App.Name == skysocksClientName { + log.Errorf("SETTING PK: %s", *reqBody.PK) if err := ctx.RPC.SetSocksClientPK(*reqBody.PK); err != nil { + log.Errorf("ERROR SETTING PK") httputil.WriteJSON(w, r, http.StatusInternalServerError, err) return } } + if reqBody.Status != nil { + switch *reqBody.Status { + case statusStop: + if err := ctx.RPC.StopApp(ctx.App.Name); err != nil { + httputil.WriteJSON(w, r, http.StatusInternalServerError, err) + return + } + case statusStart: + if err := ctx.RPC.StartApp(ctx.App.Name); err != nil { + log.Errorf("ERROR STARTING APP") + httputil.WriteJSON(w, r, http.StatusInternalServerError, err) + return + } + default: + errMsg := fmt.Errorf("value of 'status' field is %d when expecting 0 or 1", *reqBody.Status) + httputil.WriteJSON(w, r, http.StatusBadRequest, errMsg) + return + } + } + httputil.WriteJSON(w, r, http.StatusOK, ctx.App) }) } diff --git a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/hypervisor/user_manager.go b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/hypervisor/user_manager.go index 20779228..df708751 100644 --- a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/hypervisor/user_manager.go +++ b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/hypervisor/user_manager.go @@ -33,6 +33,7 @@ var ( // for use with context.Context type ctxKey string +// cookie constants const ( userKey = ctxKey("user") sessionKey = ctxKey("session") diff --git a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/router/route_group.go b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/router/route_group.go index 0de35a4c..48ca5b09 100644 --- a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/router/route_group.go +++ b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/router/route_group.go @@ -367,18 +367,20 @@ func (rg *RouteGroup) keepAliveLoop(interval time.Duration) { continue } - rg.sendKeepAlive() + if err := rg.sendKeepAlive(); err != nil { + rg.logger.Warnf("Failed to send keepalive: %v", err) + } } } } -func (rg *RouteGroup) sendKeepAlive() { +func (rg *RouteGroup) sendKeepAlive() error { rg.mu.Lock() defer rg.mu.Unlock() if len(rg.tps) == 0 || len(rg.fwd) == 0 { // if no transports, no rules, then no keepalive - return + return nil } for i := 0; i < len(rg.tps); i++ { @@ -390,14 +392,13 @@ func (rg *RouteGroup) sendKeepAlive() { } packet := routing.MakeKeepAlivePacket(rule.NextRouteID()) - errCh := rg.writePacketAsync(context.Background(), tp, packet, rule.KeyRouteID()) - go func() { - if err := <-errCh; err != nil { - rg.logger.WithError(err).Warnf("Failed to send keepalive") - } - }() + if err := rg.writePacket(context.Background(), tp, packet, rule.KeyRouteID()); err != nil { + return err + } } + + return nil } // Close closes a RouteGroup with the specified close `code`: @@ -492,12 +493,9 @@ func (rg *RouteGroup) handleClosePacket(code routing.CloseCode) error { func (rg *RouteGroup) broadcastClosePackets(code routing.CloseCode) { for i := 0; i < len(rg.tps); i++ { packet := routing.MakeClosePacket(rg.fwd[i].NextRouteID(), code) - errCh := rg.writePacketAsync(context.Background(), rg.tps[i], packet, rg.fwd[i].KeyRouteID()) - go func(tp *transport.ManagedTransport) { - if err := <-errCh; err != nil { - rg.logger.WithError(err).Errorf("Failed to send close packet to %s", tp.Remote()) - } - }(rg.tps[i]) + if err := rg.writePacket(context.Background(), rg.tps[i], packet, rg.fwd[i].KeyRouteID()); err != nil { + rg.logger.WithError(err).Errorf("Failed to send close packet to %s", rg.tps[i].Remote()) + } } } diff --git a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/router/router.go b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/router/router.go index d2ecd55b..8a94c59e 100644 --- a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/router/router.go +++ b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/router/router.go @@ -294,38 +294,25 @@ func (r *router) Serve(ctx context.Context) error { } func (r *router) serveTransportManager(ctx context.Context) { - var once sync.Once - done := make(chan struct{}) - for { - select { - case <-done: - return - default: - packet, err := r.tm.ReadPacket() - if err != nil { - if err == transport.ErrNotServing { - r.logger.WithError(err).Info("Stopped reading packets") - return - } - r.logger.WithError(err).Error("Stopped reading packets due to unexpected error.") + packet, err := r.tm.ReadPacket() + if err != nil { + if err == transport.ErrNotServing { + r.logger.WithError(err).Info("Stopped reading packets") return } - go func(packet routing.Packet) { - if err := r.handleTransportPacket(ctx, packet); err != nil { - if err == transport.ErrNotServing { - once.Do(func() { - r.logger.WithError(err).Warnf("Stopped serving Transport.") - close(done) - }) + r.logger.WithError(err).Error("Stopped reading packets due to unexpected error.") + return + } - return - } + if err := r.handleTransportPacket(ctx, packet); err != nil { + if err == transport.ErrNotServing { + r.logger.WithError(err).Warnf("Stopped serving Transport.") + return + } - r.logger.Warnf("Failed to handle transport frame: %v", err) - } - }(packet) + r.logger.Warnf("Failed to handle transport frame: %v", err) } } } diff --git a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/skyenv/values.go b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/skyenv/values.go index 8b7ca78c..e7ecc0be 100644 --- a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/skyenv/values.go +++ b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/skyenv/values.go @@ -41,7 +41,7 @@ const ( const ( SkychatName = "skychat" SkychatPort = uint16(1) - SkychatAddr = ":8000" + SkychatAddr = ":8001" SkysocksName = "skysocks" SkysocksPort = uint16(3) diff --git a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo/buildinfo.go b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo/buildinfo.go index f460f64a..f20dfbc2 100644 --- a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo/buildinfo.go +++ b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo/buildinfo.go @@ -46,7 +46,7 @@ type Info struct { // WriteTo writes build info summary to io.Writer. func (info *Info) WriteTo(w io.Writer) (int64, error) { - msg := fmt.Sprintf("Version %q built on %q agaist commit %q\n", info.Version, info.Date, info.Commit) + msg := fmt.Sprintf("Version %q built on %q against commit %q\n", info.Version, info.Date, info.Commit) n, err := w.Write([]byte(msg)) return int64(n), err } diff --git a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/util/pathutil/util.go b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/util/pathutil/util.go index 56b0487d..d3dc1461 100644 --- a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/util/pathutil/util.go +++ b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/util/pathutil/util.go @@ -5,6 +5,8 @@ import ( "io/ioutil" "os" "path/filepath" + + "github.com/SkycoinProject/skywire-mainnet/pkg/util/rename" ) const ( @@ -43,7 +45,7 @@ func AtomicWriteFile(filename string, data []byte) error { return err } - if err := os.Rename(tempFilePath, filename); err != nil { + if err := rename.Rename(tempFilePath, filename); err != nil { return err } diff --git a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/util/rename/rename.go b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/util/rename/rename.go new file mode 100644 index 00000000..ced40581 --- /dev/null +++ b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/util/rename/rename.go @@ -0,0 +1,74 @@ +package rename + +import ( + "fmt" + "io" + "log" + "os" + "strings" +) + +const crossDeviceError = "invalid cross-device link" + +// Rename renames (moves) oldPath to newPath using os.Rename. +// If paths are located on different drives or filesystems, os.Rename fails. +// In that case, Rename uses a workaround by copying oldPath to newPath and removing oldPath thereafter. +func Rename(oldPath, newPath string) error { + if err := os.Rename(oldPath, newPath); err == nil || !strings.Contains(err.Error(), crossDeviceError) { + return err + } + + stat, err := os.Stat(oldPath) + if err != nil { + return fmt.Errorf("stat: %w", err) + } + + if !stat.Mode().IsRegular() { + return fmt.Errorf("is regular: %w", err) + } + + // Paths are located on different devices. + if err := move(oldPath, newPath); err != nil { + return fmt.Errorf("move: %w", err) + } + + if err := os.Chmod(newPath, stat.Mode()); err != nil { + return fmt.Errorf("chmod: %w", err) + } + + if err := os.Remove(oldPath); err != nil { + return fmt.Errorf("remove: %w", err) + } + + return nil +} + +func move(oldPath string, newPath string) error { + inputFile, err := os.Open(oldPath) // nolint:gosec + if err != nil { + return fmt.Errorf("open: %w", err) + } + + defer func() { + if err := inputFile.Close(); err != nil { + log.Printf("Failed to close file %q: %v", inputFile.Name(), err) + } + }() + + outputFile, err := os.Create(newPath) + if err != nil { + return fmt.Errorf("create: %w", err) + } + + defer func() { + if err := outputFile.Close(); err != nil { + log.Printf("Failed to close file %q: %v", outputFile.Name(), err) + } + }() + + if _, err = io.Copy(outputFile, inputFile); err != nil { + return fmt.Errorf("copy: %w", err) + } + + return nil +} diff --git a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/util/updater/updater.go b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/util/updater/updater.go index 9e76a742..275d5450 100644 --- a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/util/updater/updater.go +++ b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/util/updater/updater.go @@ -24,6 +24,7 @@ import ( "github.com/SkycoinProject/skywire-mainnet/pkg/restart" "github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo" + "github.com/SkycoinProject/skywire-mainnet/pkg/util/rename" ) const ( @@ -180,13 +181,13 @@ func (u *Updater) updateBinary(downloadedBinariesPath, basePath, binary string) } } - if err := os.Rename(currentBinaryPath, oldBinaryPath); err != nil { + if err := rename.Rename(currentBinaryPath, oldBinaryPath); err != nil { return fmt.Errorf("rename %s to %s: %w", currentBinaryPath, oldBinaryPath, err) } - if err := os.Rename(downloadedBinaryPath, currentBinaryPath); err != nil { - // Try to revert previous os.Rename - if err := os.Rename(oldBinaryPath, currentBinaryPath); err != nil { + if err := rename.Rename(downloadedBinaryPath, currentBinaryPath); err != nil { + // Try to revert previous rename. + if err := rename.Rename(oldBinaryPath, currentBinaryPath); err != nil { u.log.Errorf("Failed to rename file %q to %q: %v", oldBinaryPath, currentBinaryPath, err) } @@ -201,7 +202,7 @@ func (u *Updater) updateBinary(downloadedBinariesPath, basePath, binary string) func (u *Updater) restore(currentBinaryPath string, toBeRemoved string) { u.removeFiles(currentBinaryPath) - if err := os.Rename(toBeRemoved, currentBinaryPath); err != nil { + if err := rename.Rename(toBeRemoved, currentBinaryPath); err != nil { u.log.Errorf("Failed to rename file %q to %q: %v", toBeRemoved, currentBinaryPath, err) } } diff --git a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/visor/config.go b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/visor/config.go index 578e6ce5..07d2e7f6 100644 --- a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/visor/config.go +++ b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/visor/config.go @@ -85,7 +85,7 @@ func (c *Config) flush() error { return ErrNoConfigPath } - c.log.Infof("Updating visor config to %+v", c) + c.log.Infof("Updating visor config to %#v", c) bytes, err := json.MarshalIndent(c, "", "\t") if err != nil { @@ -96,53 +96,6 @@ func (c *Config) flush() error { return ioutil.WriteFile(*c.Path, bytes, filePerm) } -func (c *Config) updateAppAutoStart(appName string, autoStart bool) error { - changed := false - - for i := range c.Apps { - if c.Apps[i].App == appName { - c.Apps[i].AutoStart = autoStart - changed = true - break - } - } - - if !changed { - return nil - } - - return c.flush() -} - -func (c *Config) updateAppArg(appName, argName, value string) error { - configChanged := true - - for i := range c.Apps { - argChanged := false - if c.Apps[i].App == appName { - configChanged = true - - for j := range c.Apps[i].Args { - if c.Apps[i].Args[j] == argName && j+1 < len(c.Apps[i].Args) { - c.Apps[i].Args[j+1] = value - argChanged = true - break - } - } - - if !argChanged { - c.Apps[i].Args = append(c.Apps[i].Args, argName, value) - } - } - } - - if configChanged { - return c.flush() - } - - return nil -} - // Keys returns visor public and secret keys extracted from config. // If they are not found, new keys are generated. func (c *Config) Keys() *KeyPair { diff --git a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/visor/visor.go b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/visor/visor.go index b8ba170d..f38ef0af 100644 --- a/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/visor/visor.go +++ b/vendor/github.com/SkycoinProject/skywire-mainnet/pkg/visor/visor.go @@ -699,7 +699,7 @@ func (visor *Visor) setAutoStart(appName string, autoStart bool) error { visor.logger.Infof("Saving auto start = %v for app %v to config", autoStart, appName) - return visor.conf.updateAppAutoStart(appName, autoStart) + return visor.updateAppAutoStart(appName, autoStart) } func (visor *Visor) setSocksPassword(password string) error { @@ -710,7 +710,7 @@ func (visor *Visor) setSocksPassword(password string) error { passcodeArgName = "-passcode" ) - if err := visor.conf.updateAppArg(socksName, passcodeArgName, password); err != nil { + if err := visor.updateAppArg(socksName, passcodeArgName, password); err != nil { return err } @@ -732,7 +732,7 @@ func (visor *Visor) setSocksClientPK(pk cipher.PubKey) error { pkArgName = "-srv" ) - if err := visor.conf.updateAppArg(socksClientName, pkArgName, pk.String()); err != nil { + if err := visor.updateAppArg(socksClientName, pkArgName, pk.String()); err != nil { return err } @@ -746,6 +746,63 @@ func (visor *Visor) setSocksClientPK(pk cipher.PubKey) error { return nil } +func (visor *Visor) updateAppAutoStart(appName string, autoStart bool) error { + changed := false + + for i := range visor.conf.Apps { + if visor.conf.Apps[i].App == appName { + visor.conf.Apps[i].AutoStart = autoStart + if v, ok := visor.appsConf[appName]; ok { + v.AutoStart = autoStart + visor.appsConf[appName] = v + } + + changed = true + break + } + } + + if !changed { + return nil + } + + return visor.conf.flush() +} + +func (visor *Visor) updateAppArg(appName, argName, value string) error { + configChanged := true + + for i := range visor.conf.Apps { + argChanged := false + if visor.conf.Apps[i].App == appName { + configChanged = true + + for j := range visor.conf.Apps[i].Args { + if visor.conf.Apps[i].Args[j] == argName && j+1 < len(visor.conf.Apps[i].Args) { + visor.conf.Apps[i].Args[j+1] = value + argChanged = true + break + } + } + + if !argChanged { + visor.conf.Apps[i].Args = append(visor.conf.Apps[i].Args, argName, value) + } + + if v, ok := visor.appsConf[appName]; ok { + v.Args = visor.conf.Apps[i].Args + visor.appsConf[appName] = v + } + } + } + + if configChanged { + return visor.conf.flush() + } + + return nil +} + // UnlinkSocketFiles removes unix socketFiles from file system func UnlinkSocketFiles(socketFiles ...string) error { for _, f := range socketFiles { diff --git a/vendor/modules.txt b/vendor/modules.txt index 5d108ac1..c3a852e2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -18,7 +18,7 @@ fyne.io/fyne/layout fyne.io/fyne/test fyne.io/fyne/theme fyne.io/fyne/widget -# github.com/SkycoinProject/dmsg v0.1.0 +# github.com/SkycoinProject/dmsg v0.2.0 github.com/SkycoinProject/dmsg github.com/SkycoinProject/dmsg/cipher github.com/SkycoinProject/dmsg/disc @@ -34,7 +34,7 @@ github.com/SkycoinProject/skycoin/src/cipher/ripemd160 github.com/SkycoinProject/skycoin/src/cipher/secp256k1-go github.com/SkycoinProject/skycoin/src/cipher/secp256k1-go/secp256k1-go2 github.com/SkycoinProject/skycoin/src/util/logging -# github.com/SkycoinProject/skywire-mainnet v0.1.2 +# github.com/SkycoinProject/skywire-mainnet v0.2.3 github.com/SkycoinProject/skywire-mainnet/internal/httpauth github.com/SkycoinProject/skywire-mainnet/pkg/app github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon @@ -56,6 +56,7 @@ github.com/SkycoinProject/skywire-mainnet/pkg/transport-discovery/client github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo github.com/SkycoinProject/skywire-mainnet/pkg/util/deadline github.com/SkycoinProject/skywire-mainnet/pkg/util/pathutil +github.com/SkycoinProject/skywire-mainnet/pkg/util/rename github.com/SkycoinProject/skywire-mainnet/pkg/util/rpcutil github.com/SkycoinProject/skywire-mainnet/pkg/util/updater github.com/SkycoinProject/skywire-mainnet/pkg/visor