From 16b5c0c5d0fd521178c184db68d5ba59bfa6cfc4 Mon Sep 17 00:00:00 2001 From: David Barth Date: Wed, 14 Dec 2016 17:54:58 +0100 Subject: [PATCH 01/55] spread test infrastructure, and initial installation test --- spread/lib/prepare-all.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/spread/lib/prepare-all.sh b/spread/lib/prepare-all.sh index 95798d12..4005aeb9 100644 --- a/spread/lib/prepare-all.sh +++ b/spread/lib/prepare-all.sh @@ -13,4 +13,3 @@ if [ -e /home/snapweb/snapweb_*_amd64.snap ] ; then fi echo "Not trying to build snapweb on the test target: provide a pre-built snap" -test -e /home/snapweb/snapweb_*_amd64.snap From 8e763cb1f00cc79dabcc4c15e24556c3e8c40dfc Mon Sep 17 00:00:00 2001 From: David Barth Date: Fri, 16 Dec 2016 11:52:00 +0100 Subject: [PATCH 02/55] check if the system is managed --- cmd/snapweb/main.go | 8 ++++++ cmd/snapweb/state.go | 59 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 cmd/snapweb/state.go diff --git a/cmd/snapweb/main.go b/cmd/snapweb/main.go index 5b8dc1b8..33008fbe 100644 --- a/cmd/snapweb/main.go +++ b/cmd/snapweb/main.go @@ -44,6 +44,14 @@ func init() { } func main() { + config := readConfig() + + if ! IsManaged() { + panic("Start FirstBoot instead") + } + + GenerateCertificate() + // TODO set warning for too hazardous config? config, err := snappy.ReadConfig() if err != nil { diff --git a/cmd/snapweb/state.go b/cmd/snapweb/state.go new file mode 100644 index 00000000..a7d55801 --- /dev/null +++ b/cmd/snapweb/state.go @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014-2016 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package main + +import ( + "github.com/snapcore/snapd/client" +) + +// IsManaged determines if the device is in the 'managed' state +func IsManaged() bool { + client := client.New(nil) + + sysInfo, err := client.SysInfo() + if err != nil { + panic(err) + } + + if sysInfo.OnClassic || sysInfo.Managed { + return true + } + + return false +} + +func IsJustOperational() bool { + client := client.New(nil) + + users, err := client.Users() + if err != nil { + panic(err) + } + + if len(users) > 0 { + return false + } + + return true +} + +// IsEmbryonic determines if the devices is in 'embryonic' state +func IsEmbryonic() bool { + // FIXME + return false +} From 7a5b68a91c85956afcd07a9a21580d6e87c00fba Mon Sep 17 00:00:00 2001 From: David Barth Date: Wed, 4 Jan 2017 17:53:22 +0100 Subject: [PATCH 03/55] simplify --- cmd/snapweb/main.go | 2 +- cmd/snapweb/state.go | 25 ++----------------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/cmd/snapweb/main.go b/cmd/snapweb/main.go index 33008fbe..68875e97 100644 --- a/cmd/snapweb/main.go +++ b/cmd/snapweb/main.go @@ -47,7 +47,7 @@ func main() { config := readConfig() if ! IsManaged() { - panic("Start FirstBoot instead") + panic("Snapweb does not run on un-managed devices") } GenerateCertificate() diff --git a/cmd/snapweb/state.go b/cmd/snapweb/state.go index a7d55801..78692801 100644 --- a/cmd/snapweb/state.go +++ b/cmd/snapweb/state.go @@ -21,8 +21,8 @@ import ( "github.com/snapcore/snapd/client" ) -// IsManaged determines if the device is in the 'managed' state -func IsManaged() bool { +// IsDeviceManaged determines if the device is in the 'managed' state +func IsDeviceManaged() bool { client := client.New(nil) sysInfo, err := client.SysInfo() @@ -36,24 +36,3 @@ func IsManaged() bool { return false } - -func IsJustOperational() bool { - client := client.New(nil) - - users, err := client.Users() - if err != nil { - panic(err) - } - - if len(users) > 0 { - return false - } - - return true -} - -// IsEmbryonic determines if the devices is in 'embryonic' state -func IsEmbryonic() bool { - // FIXME - return false -} From d466cea778ff3275c18107998536eb2f5d450315 Mon Sep 17 00:00:00 2001 From: David Barth Date: Fri, 6 Jan 2017 12:24:39 +0100 Subject: [PATCH 04/55] optimize spread test suite --- spread/lib/prepare-all.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/spread/lib/prepare-all.sh b/spread/lib/prepare-all.sh index 4005aeb9..95798d12 100644 --- a/spread/lib/prepare-all.sh +++ b/spread/lib/prepare-all.sh @@ -13,3 +13,4 @@ if [ -e /home/snapweb/snapweb_*_amd64.snap ] ; then fi echo "Not trying to build snapweb on the test target: provide a pre-built snap" +test -e /home/snapweb/snapweb_*_amd64.snap From f7576b9e7e5dc0064d5db51237ec45e563b9ba28 Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 12 Jan 2017 17:43:31 +0100 Subject: [PATCH 05/55] use 1G image; this should be sufficient --- spread/image/create-image.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spread/image/create-image.sh b/spread/image/create-image.sh index 90201eee..d291c192 100755 --- a/spread/image/create-image.sh +++ b/spread/image/create-image.sh @@ -43,7 +43,7 @@ fi ubuntu-image \ --channel $channel \ -o $image_name \ - --image-size 4G \ + --image-size 1G \ $ubuntu_image_extra_args \ $model.model From 41a5ded3eb64ab6be4fbabb5e194a4137a6eaabc Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 12 Jan 2017 17:56:50 +0100 Subject: [PATCH 06/55] 2G it is --- spread/image/create-image.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spread/image/create-image.sh b/spread/image/create-image.sh index d291c192..5c8c9820 100755 --- a/spread/image/create-image.sh +++ b/spread/image/create-image.sh @@ -43,7 +43,7 @@ fi ubuntu-image \ --channel $channel \ -o $image_name \ - --image-size 1G \ + --image-size 2G \ $ubuntu_image_extra_args \ $model.model From e042012650810bd770480435ba23aa231c62efb3 Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 12 Jan 2017 18:08:20 +0100 Subject: [PATCH 07/55] add support for new firstboot test suite --- run-spread-tests.sh | 8 ++ spread.yaml | 11 ++ spread/firstboot/firstboot-runs/task.yaml | 22 ++++ spread/image/create-image-embryonic.sh | 132 ++++++++++++++++++++++ spread/main/firstboot-not/task.yaml | 22 ++++ 5 files changed, 195 insertions(+) create mode 100644 spread/firstboot/firstboot-runs/task.yaml create mode 100755 spread/image/create-image-embryonic.sh create mode 100644 spread/main/firstboot-not/task.yaml diff --git a/run-spread-tests.sh b/run-spread-tests.sh index 78b68718..9c525883 100755 --- a/run-spread-tests.sh +++ b/run-spread-tests.sh @@ -17,6 +17,7 @@ set -e image_name=ubuntu-core-16.img +image_name2=ubuntu-core-16-embryonic.img channel=candidate spread_opts= force_new_image=0 @@ -75,6 +76,13 @@ if [ ! -e $SPREAD_QEMU_PATH/$image_name ] || [ $force_new_image -eq 1 ] ; then mkdir -p $SPREAD_QEMU_PATH mv -f spread/image/ubuntu-core-16.img $SPREAD_QEMU_PATH/$image_name fi +if [ ! -e $SPREAD_QEMU_PATH/$image_name2 ] || [ $force_new_image -eq 1 ] ; then + echo "INFO: Creating new qemu test image(2) ..." + (cd spread/image ; sudo ./create-image-embryonic.sh $channel) + mkdir -p $SPREAD_QEMU_PATH + mv -f spread/image/ubuntu-core-16-embryonic.img $SPREAD_QEMU_PATH/$image_name +fi + # We currently only run spread tests but we could do other things # here as well like running our snap-lintian tool etc. diff --git a/spread.yaml b/spread.yaml index 9cfca85f..d7ea0ea9 100644 --- a/spread.yaml +++ b/spread.yaml @@ -30,6 +30,9 @@ backends: - ubuntu-core-16: username: test password: test + - ubuntu-core-16-embryonic: + # root user access; not a managed system yet + password: test # Put this somewhere where we have read-write access path: /home/snapweb @@ -58,3 +61,11 @@ suites: . $TESTSLIB/prepare.sh restore-each: | . $TESTSLIB/restore-each.sh + spread/firstboot/: + summary: Full-system tests for the firstboot variant of snapweb + environment: + FIRSTBOOT: yes + systems: + - ubuntu-core-16-embryonic + prepare: | + . $TESTSLIB/prepare.sh diff --git a/spread/firstboot/firstboot-runs/task.yaml b/spread/firstboot/firstboot-runs/task.yaml new file mode 100644 index 00000000..ad59a10d --- /dev/null +++ b/spread/firstboot/firstboot-runs/task.yaml @@ -0,0 +1,22 @@ +summary: Verify that firstboot will start on an un-managed system by default +environment: + SEED_DIR: /var/lib/snapd/seed +prepare: | + systemctl stop snapd.service + rm -f /var/lib/snapd/state.json +restore: | + systemctl start snapd.service +execute: | + echo "Start the daemon with an empty state, this will make it import " + echo "assertions from the $SEED_DIR/assertions subdirectory." + systemctl start snapd.service + + echo "The system should not be managed yet" + test `snap managed` = 'false' + + echo "Verifying that snapweb is NOT running as normal" + test `ps ax | grep -- -linux-gnu/snapweb | grep -v grep | grep -v firstboot | wc -l` -eq 0 + + echo "Verifying that firstboot is running" + test `ps ax | grep -- -linux-gnu/firstboot | grep -v grep | wc -l` -eq 1 + \ No newline at end of file diff --git a/spread/image/create-image-embryonic.sh b/spread/image/create-image-embryonic.sh new file mode 100755 index 00000000..971607a9 --- /dev/null +++ b/spread/image/create-image-embryonic.sh @@ -0,0 +1,132 @@ +#!/bin/bash +# +# Copyright (C) 2016 Canonical Ltd +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3 as +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set -e + +if [ $(id -u) -ne 0 ] ; then + echo "ERROR: needs to be executed as root" + exit 1 +fi + +channel=candidate +if [ ! -z "$1" ] ; then + channel=$1 +fi + +snap= +if [ ! -z "$2" ] ; then + snap=$2 +fi + +model=pc +arch=amd64 +image_name=ubuntu-core-16-embryonic.img +ubuntu_image_extra_args= + +if [ ! -z "$snap" ] ; then + ubuntu_image_extra_args="--extra-snaps $snap" +fi + +ubuntu-image \ + --channel $channel \ + -o $image_name \ + --image-size 2G \ + $ubuntu_image_extra_args \ + $model.model + +kpartx -a $image_name +sleep 0.5 + +loop_path=`findfs LABEL=writable` +tmp_mount=`mktemp -d` + +mount $loop_path $tmp_mount + +core_snap=$(find $tmp_mount/system-data/var/lib/snapd/snaps -name "core_*.snap") +tmp_core=`mktemp -d` +mount $core_snap $tmp_core +# copy over all systemd units +mkdir -p $tmp_mount/system-data/etc/systemd +cp -rav $tmp_core/etc/systemd/* \ + $tmp_mount/system-data/etc/systemd/ +# copy some more etc files into the writable area +mkdir -p $tmp_mount/system-data/etc/ssh +cp -av $tmp_core/etc/passwd $tmp_mount/system-data/etc/ +cp -av $tmp_core/etc/shadow $tmp_mount/system-data/etc/ +cp -av $tmp_core/etc/ssh/sshd_config $tmp_mount/system-data/etc/ssh +# mkdir -p $tmp_mount/system-data/var/lib/system-image +# cp -av $tmp_core/etc/system-image/writable-paths $tmp_mount/system-data/var/lib/system-image +umount $tmp_core +rm -rf $tmp_core + +# allow root user to ssh into the system without password +# test_pass=`python -c 'import crypt; print crypt.crypt("test","Fx")'` +# ie, FxhZ/XVdPJNZE +sed -i 's/root:x:/root:FxhZ\/XVdPJNZE:/' $tmp_mount/system-data/etc/passwd +sed -i 's/root:x:/root:\!:/' $tmp_mount/system-data/etc/shadow +sed -i 's/\(PermitRootLogin\)\>.*/\1 yes/' $tmp_mount/system-data/etc/ssh/sshd_config +# sed -i 's/\(PermitEmptyPasswords\)\>.*/\1 yes/' $tmp_mount/system-data/etc/ssh/sshd_config + +if false; then + mkdir -p $tmp_mount/system-data/var/lib/extrausers + echo 'test:FxhZ/XVdPJNZE:800:800:spread test:/root:/bin/bash' > $tmp_mount/system-data/var/lib/extrausers/passwd + echo 'test:!:16891:0:99999:7:::' > $tmp_mount/system-data/var/lib/extrausers/shadow + echo 'adm:x:4:syslog,test' > $tmp_mount/system-data/var/lib/extrausers/group + echo 'test:x:800:' >> $tmp_mount/system-data/var/lib/extrausers/group +fi + +# Create systemd service to run on firstboot and install our passwd/group overrides +if true; then + mkdir -p $tmp_mount/system-data/etc/systemd/system + cat << 'EOF' > $tmp_mount/system-data/etc/systemd/system/spread-access.service +[Unit] +Description=Configure spread access +After=networking.service + +[Service] +Type=oneshot +ExecStart=/writable/system-data/var/lib/spread-access/run.sh +RemainAfterExit=no + +[Install] +WantedBy=multi-user.target +EOF + + mkdir $tmp_mount/system-data/var/lib/spread-access + cat << 'EOF' > $tmp_mount/system-data/var/lib/spread-access/run.sh +#!/bin/bash + +set -e + +echo "Start spread-access $(date -Iseconds --utc)" + +# mount our special passwd/shadow files to permit spread access as root +mount --bind /writable/system-data/etc/passwd /etc/passwd +mount --bind /writable/system-data/etc/shadow /etc/shadow + +EOF + + chmod +x $tmp_mount/system-data/var/lib/spread-access/run.sh + + # install the new service + mkdir -p $tmp_mount/system-data/etc/systemd/system/multi-user.target.wants + ln -sf /etc/systemd/system/spread-access.service \ + $tmp_mount/system-data/etc/systemd/system/multi-user.target.wants/spread-access.service +fi + +umount $tmp_mount +kpartx -d $image_name +rm -rf $tmp_mount diff --git a/spread/main/firstboot-not/task.yaml b/spread/main/firstboot-not/task.yaml new file mode 100644 index 00000000..272982c6 --- /dev/null +++ b/spread/main/firstboot-not/task.yaml @@ -0,0 +1,22 @@ +summary: Verify that firstboot won't run on a managed system +systems: [-ubuntu-core-16-64, -ubuntu-core-16-arm-64, -ubuntu-core-16-arm-32] +environment: + SEED_DIR: /var/lib/snapd/seed +prepare: | + systemctl stop snapd.service + rm -f /var/lib/snapd/state.json +restore: | + systemctl start snapd.service +execute: | + echo "Start the daemon with an empty state, this will make it import " + echo "assertions from the $SEED_DIR/assertions subdirectory." + systemctl start snapd.service + + echo "The system should be managed by now" + test `snap managed` + + echo "Verifying that firstboot is NOT running" + test `ps axu | grep snapweb.firstboot | grep -v grep | wc -l` -eq 0 + + echo "Verifying that snapweb is running as normal" + test `ps axu | grep snapweb | grep -v grep | wc -l` -eq 1 From c72de1b53c78591fa19409c1125a5d9fc817c10c Mon Sep 17 00:00:00 2001 From: David Barth Date: Fri, 13 Jan 2017 15:32:10 +0100 Subject: [PATCH 08/55] compat with kvm --- pkg/snapweb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/snapweb b/pkg/snapweb index 3b200cdf..c5e6bbec 100755 --- a/pkg/snapweb +++ b/pkg/snapweb @@ -2,7 +2,7 @@ set -e case $SNAP_ARCH in - amd64) + amd64|x86_64) plat_abi=x86_64-linux-gnu ;; armhf) From 124a53669f02f2c93f0ccf18c01f30882dba851f Mon Sep 17 00:00:00 2001 From: David Barth Date: Fri, 13 Jan 2017 15:32:57 +0100 Subject: [PATCH 09/55] create 2nd image properly --- run-spread-tests.sh | 3 +-- spread/image/create-image-embryonic.sh | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/run-spread-tests.sh b/run-spread-tests.sh index 9c525883..0f90edcb 100755 --- a/run-spread-tests.sh +++ b/run-spread-tests.sh @@ -80,10 +80,9 @@ if [ ! -e $SPREAD_QEMU_PATH/$image_name2 ] || [ $force_new_image -eq 1 ] ; then echo "INFO: Creating new qemu test image(2) ..." (cd spread/image ; sudo ./create-image-embryonic.sh $channel) mkdir -p $SPREAD_QEMU_PATH - mv -f spread/image/ubuntu-core-16-embryonic.img $SPREAD_QEMU_PATH/$image_name + mv -f spread/image/ubuntu-core-16-embryonic.img $SPREAD_QEMU_PATH/$image_name2 fi - # We currently only run spread tests but we could do other things # here as well like running our snap-lintian tool etc. if [ $test_from_channel -eq 1 ] ; then diff --git a/spread/image/create-image-embryonic.sh b/spread/image/create-image-embryonic.sh index 971607a9..ec764256 100755 --- a/spread/image/create-image-embryonic.sh +++ b/spread/image/create-image-embryonic.sh @@ -43,7 +43,7 @@ fi ubuntu-image \ --channel $channel \ -o $image_name \ - --image-size 2G \ + --image-size 4G \ $ubuntu_image_extra_args \ $model.model From 5de0330e02a40427bfe1c0d52af14e30e7809721 Mon Sep 17 00:00:00 2001 From: David Barth Date: Fri, 13 Jan 2017 15:33:41 +0100 Subject: [PATCH 10/55] ok, 4G per image then --- spread/image/create-image.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spread/image/create-image.sh b/spread/image/create-image.sh index 5c8c9820..90201eee 100755 --- a/spread/image/create-image.sh +++ b/spread/image/create-image.sh @@ -43,7 +43,7 @@ fi ubuntu-image \ --channel $channel \ -o $image_name \ - --image-size 2G \ + --image-size 4G \ $ubuntu_image_extra_args \ $model.model From 0828110090402966d98983661f8820359642a3bb Mon Sep 17 00:00:00 2001 From: David Barth Date: Fri, 13 Jan 2017 15:34:31 +0100 Subject: [PATCH 11/55] restore-each for the firstboot test suite --- spread.yaml | 2 ++ spread/lib/restore-each.sh | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/spread.yaml b/spread.yaml index d7ea0ea9..46cccbfc 100644 --- a/spread.yaml +++ b/spread.yaml @@ -69,3 +69,5 @@ suites: - ubuntu-core-16-embryonic prepare: | . $TESTSLIB/prepare.sh + restore-each: | + . $TESTSLIB/restore-each.sh diff --git a/spread/lib/restore-each.sh b/spread/lib/restore-each.sh index 94030e1c..80b7c556 100644 --- a/spread/lib/restore-each.sh +++ b/spread/lib/restore-each.sh @@ -15,21 +15,23 @@ for snap in /snap/*; do esac done +# Depending on what the test did both services are not meant to be +# running here. +systemctl stop snap.snapweb.snapweb.service || true + # Cleanup all configuration files from the snap so that we have # a fresh start for the next test rm -rf /var/snap/$SNAP_NAME/common/* rm -rf /var/snap/$SNAP_NAME/current/* -# Depending on what the test did both services are not meant to be -# running here. -systemctl stop snap.snapweb.snapweb.service || true - # Ensure we have the same state for snapd as we had before -systemctl stop snapd.service snapd.socket -rm -rf /var/lib/snapd/* -tar xzf $SPREAD_PATH/snapd-state.tar.gz -C / -rm -rf /root/.snap -systemctl start snapd.service snapd.socket +if [ -f $SPREAD_PATH/snapd-state.tar.gz ]; then + systemctl stop snapd.service snapd.socket + rm -rf /var/lib/snapd/* + tar xzf $SPREAD_PATH/snapd-state.tar.gz -C / + rm -rf /root/.snap + systemctl start snapd.service snapd.socket +fi # Start services again now that the system is restored systemctl start snap.snapweb.snapweb.service From 1701016f319d61b25c6d5111a5535ace53be1e4e Mon Sep 17 00:00:00 2001 From: David Barth Date: Fri, 13 Jan 2017 15:35:09 +0100 Subject: [PATCH 12/55] initial firstboot code --- build.sh | 3 +- cmd/firstboot/main.go | 81 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 cmd/firstboot/main.go diff --git a/build.sh b/build.sh index d9810230..c5e56265 100755 --- a/build.sh +++ b/build.sh @@ -59,7 +59,8 @@ gobuild() { mkdir -p $output_dir cd $output_dir - GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -ldflags "-extld=${plat_abi}-gcc -X main.httpAddr=${httpAddr} -X main.httpsAddr=${httpsAddr}" github.com/snapcore/snapweb/cmd/snapweb + GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -ldflags "-extld=${plat_abi}-gcc" github.com/snapcore/snapweb/cmd/snapweb + GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -ldflags "-extld=${plat_abi}-gcc" github.com/snapcore/snapweb/cmd/firstboot GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -o generate-token -ldflags "-extld=${plat_abi}-gcc" $srcdir/cmd/generate-token/main.go cp generate-token ../../ cd - > /dev/null diff --git a/cmd/firstboot/main.go b/cmd/firstboot/main.go new file mode 100644 index 00000000..0c2e92dc --- /dev/null +++ b/cmd/firstboot/main.go @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014-2017 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package main + +import ( + "log" + "net/http" + "os" + + "github.com/snapcore/snapd/client" + "github.com/snapcore/snapweb/avahi" +) + +var logger *log.Logger + +const ( + httpAddr string = ":4200" + // httpsAddr string = ":4201" +) + +func init() { + logger = log.New(os.Stderr, "Snapweb/firstboot: ", log.Ldate|log.Ltime|log.Lshortfile) +} + +// IsDeviceManaged determines if the device is in the 'managed' state +func IsDeviceManaged() bool { + client := client.New(nil) + + sysInfo, err := client.SysInfo() + if err != nil { + panic(err) + } + + if sysInfo.OnClassic || sysInfo.Managed { + return true + } + + return false +} + +func initURLHandlers(log *log.Logger) { + // API + http.Handle("/api/", makeAPIHandler("/api/")) + + // Resources + http.Handle("/public/", loggingHandler(http.FileServer(http.Dir(filepath.Join(os.Getenv("SNAP"), "www"))))) + + http.HandleFunc("/", makeMainPageHandler()) +} + +func main() { + + if IsDeviceManaged() { + panic("The Snapweb/Firstboot module does not run on managed devices") + } + + initURLHandlers(logger) + + go avahi.InitMDNS(logger) + + // open a plain HTTP end-point on the "usual" 4200 port + if err := http.ListenAndServe(httpAddr, nil); err != nil { + logger.Fatalf("%v", err) + } + +} From 9fd16dc82ce63de7b77203bb2dbf563d09097402 Mon Sep 17 00:00:00 2001 From: David Barth Date: Fri, 13 Jan 2017 19:36:57 +0100 Subject: [PATCH 13/55] initial implementation of a separate service for firstboot --- cmd/firstboot/main.go | 108 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/cmd/firstboot/main.go b/cmd/firstboot/main.go index 0c2e92dc..a45d3a84 100644 --- a/cmd/firstboot/main.go +++ b/cmd/firstboot/main.go @@ -18,11 +18,18 @@ package main import ( + "io" "log" + "net" "net/http" "os" + "path/filepath" + "strings" + "text/template" + "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/client" + "github.com/snapcore/snapweb/avahi" ) @@ -53,9 +60,108 @@ func IsDeviceManaged() bool { return false } +func unixDialer(socketPath string) func(string, string) (net.Conn, error) { + file, err := os.OpenFile(socketPath, os.O_RDWR, 0666) + if err == nil { + file.Close() + } + + return func(_, _ string) (net.Conn, error) { + return net.Dial("unix", socketPath) + } +} + +func makePassthroughHandler(socketPath string, prefix string) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c := &http.Client{ + Transport: &http.Transport{Dial: unixDialer(socketPath)}, + } + + log.Println(r.Method, r.URL.Path) + + // need to remove the RequestURI field + // and remove the /api prefix from snapweb URLs + r.URL.Scheme = "http" + r.URL.Host = "localhost" + r.URL.Path = strings.TrimPrefix(r.URL.Path, prefix) + + outreq, err := http.NewRequest(r.Method, r.URL.String(), r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + resp, err := c.Do(outreq) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Note: the client.Do method above only returns JSON responses + // even if it doesn't say so + hdr := w.Header() + hdr.Set("Content-Type", "application/json") + w.WriteHeader(resp.StatusCode) + + io.Copy(w, resp.Body) + + }) +} + +func loggingHandler(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.URL.Path) + h.ServeHTTP(w, r) + }) +} + +type branding struct { + Name string + Subname string +} + +type templateData struct { + Branding branding + SnapdVersion string +} + +func makeMainPageHandler() http.HandlerFunc { + + return func(w http.ResponseWriter, r *http.Request) { + data := templateData{ + Branding: branding{ + Name: "Ubuntu", + Subname: "", + }, + SnapdVersion: "snapd", + } + + if err := renderLayout("index.html", &data, w); err != nil { + log.Println(err) + } + } +} + +func renderLayout(html string, data *templateData, w http.ResponseWriter) error { + htmlPath := filepath.Join(os.Getenv("SNAP"), "www", "templates", html) + if _, err := os.Stat(htmlPath); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + + layoutPath := filepath.Join(os.Getenv("SNAP"), "www", "templates", "base.html") + t, err := template.ParseFiles(layoutPath, htmlPath) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + + return t.Execute(w, *data) +} + func initURLHandlers(log *log.Logger) { // API - http.Handle("/api/", makeAPIHandler("/api/")) + http.Handle("/api/", makePassthroughHandler(dirs.SnapdSocket, "/api/")) // Resources http.Handle("/public/", loggingHandler(http.FileServer(http.Dir(filepath.Join(os.Getenv("SNAP"), "www"))))) From 736e9d13da9b747e0803eef6347b4b6019e692dd Mon Sep 17 00:00:00 2001 From: David Barth Date: Fri, 13 Jan 2017 19:49:26 +0100 Subject: [PATCH 14/55] start firstboot as a normal service; just exit gracefully if the system is already managed --- cmd/firstboot/main.go | 5 +++-- pkg/meta/snap.yaml | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/firstboot/main.go b/cmd/firstboot/main.go index a45d3a84..5e80812e 100644 --- a/cmd/firstboot/main.go +++ b/cmd/firstboot/main.go @@ -29,7 +29,7 @@ import ( "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/client" - + "github.com/snapcore/snapweb/avahi" ) @@ -172,7 +172,8 @@ func initURLHandlers(log *log.Logger) { func main() { if IsDeviceManaged() { - panic("The Snapweb/Firstboot module does not run on managed devices") + log.Println("The Snapweb/Firstboot module does not run on managed devices") + os.Exit(0) } initURLHandlers(logger) diff --git a/pkg/meta/snap.yaml b/pkg/meta/snap.yaml index 684dc196..18cd4f0e 100644 --- a/pkg/meta/snap.yaml +++ b/pkg/meta/snap.yaml @@ -16,5 +16,9 @@ apps: daemon: simple command: snapweb plugs: [network, network-bind, snapd-control, timeserver-control, timezone-control] + firstboot: + daemon: simple + command: firtsboot + plugs: [network, network-bind, snapd-control, timeserver-control] generate-token: command: generate-token From ff26e7cc6878295a6447119f781e2475cf4ee129 Mon Sep 17 00:00:00 2001 From: David Barth Date: Wed, 18 Jan 2017 11:40:13 +0100 Subject: [PATCH 15/55] serve a specific JS code for firstboot --- cmd/firstboot/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/firstboot/main.go b/cmd/firstboot/main.go index 5e80812e..323f643b 100644 --- a/cmd/firstboot/main.go +++ b/cmd/firstboot/main.go @@ -149,7 +149,7 @@ func renderLayout(html string, data *templateData, w http.ResponseWriter) error return err } - layoutPath := filepath.Join(os.Getenv("SNAP"), "www", "templates", "base.html") + layoutPath := filepath.Join(os.Getenv("SNAP"), "www", "templates", "fistboot.html") t, err := template.ParseFiles(layoutPath, htmlPath) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -164,7 +164,7 @@ func initURLHandlers(log *log.Logger) { http.Handle("/api/", makePassthroughHandler(dirs.SnapdSocket, "/api/")) // Resources - http.Handle("/public/", loggingHandler(http.FileServer(http.Dir(filepath.Join(os.Getenv("SNAP"), "www"))))) + http.Handle("/firstboot/", loggingHandler(http.FileServer(http.Dir(filepath.Join(os.Getenv("SNAP"), "www"))))) http.HandleFunc("/", makeMainPageHandler()) } From 967522b5db3da5c50458c5c9d2afe31d0450c073 Mon Sep 17 00:00:00 2001 From: David Barth Date: Wed, 18 Jan 2017 19:44:19 +0100 Subject: [PATCH 16/55] rename to web-conf, same as console-conf --- build.sh | 2 +- cmd/{firstboot => web-conf}/main.go | 8 ++++---- pkg/meta/snap.yaml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename cmd/{firstboot => web-conf}/main.go (93%) diff --git a/build.sh b/build.sh index c5e56265..43ea5254 100755 --- a/build.sh +++ b/build.sh @@ -60,7 +60,7 @@ gobuild() { mkdir -p $output_dir cd $output_dir GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -ldflags "-extld=${plat_abi}-gcc" github.com/snapcore/snapweb/cmd/snapweb - GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -ldflags "-extld=${plat_abi}-gcc" github.com/snapcore/snapweb/cmd/firstboot + GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -ldflags "-extld=${plat_abi}-gcc" github.com/snapcore/snapweb/cmd/web-conf GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -o generate-token -ldflags "-extld=${plat_abi}-gcc" $srcdir/cmd/generate-token/main.go cp generate-token ../../ cd - > /dev/null diff --git a/cmd/firstboot/main.go b/cmd/web-conf/main.go similarity index 93% rename from cmd/firstboot/main.go rename to cmd/web-conf/main.go index 323f643b..6d59fd4f 100644 --- a/cmd/firstboot/main.go +++ b/cmd/web-conf/main.go @@ -41,7 +41,7 @@ const ( ) func init() { - logger = log.New(os.Stderr, "Snapweb/firstboot: ", log.Ldate|log.Ltime|log.Lshortfile) + logger = log.New(os.Stderr, "web-conf: ", log.Ldate|log.Ltime|log.Lshortfile) } // IsDeviceManaged determines if the device is in the 'managed' state @@ -149,7 +149,7 @@ func renderLayout(html string, data *templateData, w http.ResponseWriter) error return err } - layoutPath := filepath.Join(os.Getenv("SNAP"), "www", "templates", "fistboot.html") + layoutPath := filepath.Join(os.Getenv("SNAP"), "www", "templates", "web-conf.html") t, err := template.ParseFiles(layoutPath, htmlPath) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -164,7 +164,7 @@ func initURLHandlers(log *log.Logger) { http.Handle("/api/", makePassthroughHandler(dirs.SnapdSocket, "/api/")) // Resources - http.Handle("/firstboot/", loggingHandler(http.FileServer(http.Dir(filepath.Join(os.Getenv("SNAP"), "www"))))) + http.Handle("/web-conf/", loggingHandler(http.FileServer(http.Dir(filepath.Join(os.Getenv("SNAP"), "www"))))) http.HandleFunc("/", makeMainPageHandler()) } @@ -172,7 +172,7 @@ func initURLHandlers(log *log.Logger) { func main() { if IsDeviceManaged() { - log.Println("The Snapweb/Firstboot module does not run on managed devices") + log.Println("web-conf does not run on managed devices") os.Exit(0) } diff --git a/pkg/meta/snap.yaml b/pkg/meta/snap.yaml index 18cd4f0e..51aea3c2 100644 --- a/pkg/meta/snap.yaml +++ b/pkg/meta/snap.yaml @@ -16,9 +16,9 @@ apps: daemon: simple command: snapweb plugs: [network, network-bind, snapd-control, timeserver-control, timezone-control] - firstboot: + web-conf: daemon: simple - command: firtsboot + command: web-conf plugs: [network, network-bind, snapd-control, timeserver-control] generate-token: command: generate-token From 4fc8bd738add05ddb324ffc401865655995f0ab3 Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 19 Jan 2017 15:29:29 +0100 Subject: [PATCH 17/55] simplify templates; let w-c run on managed for now --- cmd/web-conf/main.go | 46 +++++++++++++++++++------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/cmd/web-conf/main.go b/cmd/web-conf/main.go index 6d59fd4f..9a732cc1 100644 --- a/cmd/web-conf/main.go +++ b/cmd/web-conf/main.go @@ -127,36 +127,30 @@ type templateData struct { func makeMainPageHandler() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - data := templateData{ - Branding: branding{ - Name: "Ubuntu", - Subname: "", - }, - SnapdVersion: "snapd", - } - - if err := renderLayout("index.html", &data, w); err != nil { - log.Println(err) - } + layoutPath := filepath.Join(os.Getenv("SNAP"), "www", "templates", "web-conf.html") + t, err := template.ParseFiles(layoutPath) + if err != nil { + logger.Fatalf("%v", err) } -} -func renderLayout(html string, data *templateData, w http.ResponseWriter) error { - htmlPath := filepath.Join(os.Getenv("SNAP"), "www", "templates", html) - if _, err := os.Stat(htmlPath); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + data := templateData{ + Branding: branding{ + Name: "Ubuntu", + Subname: "", + }, + SnapdVersion: "snapd", } - layoutPath := filepath.Join(os.Getenv("SNAP"), "www", "templates", "web-conf.html") - t, err := template.ParseFiles(layoutPath, htmlPath) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } + return func(w http.ResponseWriter, r *http.Request) { - return t.Execute(w, *data) + err = t.Execute(w, &data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Println(err) + return; + } + + } } func initURLHandlers(log *log.Logger) { @@ -173,7 +167,7 @@ func main() { if IsDeviceManaged() { log.Println("web-conf does not run on managed devices") - os.Exit(0) + // os.Exit(0) } initURLHandlers(logger) From 0bddf2ce67eee8aa52dee9d68758e1ec47c15245 Mon Sep 17 00:00:00 2001 From: David Barth Date: Wed, 25 Jan 2017 14:53:50 +0100 Subject: [PATCH 18/55] initial view --- cmd/web-conf/main.go | 4 +- gulpfile.js | 22 +++++++---- www/src/js/routers/webconf-router.js | 59 ++++++++++++++++++++++++++++ www/src/js/views/webconf-layout.js | 55 ++++++++++++++++++++++++++ www/src/js/webconf-app.js | 26 ++++++++++++ www/templates/webconf.html | 24 +++++++++++ 6 files changed, 181 insertions(+), 9 deletions(-) create mode 100644 www/src/js/routers/webconf-router.js create mode 100644 www/src/js/views/webconf-layout.js create mode 100644 www/src/js/webconf-app.js create mode 100644 www/templates/webconf.html diff --git a/cmd/web-conf/main.go b/cmd/web-conf/main.go index 9a732cc1..ed63aea6 100644 --- a/cmd/web-conf/main.go +++ b/cmd/web-conf/main.go @@ -127,7 +127,7 @@ type templateData struct { func makeMainPageHandler() http.HandlerFunc { - layoutPath := filepath.Join(os.Getenv("SNAP"), "www", "templates", "web-conf.html") + layoutPath := filepath.Join(os.Getenv("SNAP"), "www", "templates", "webconf.html") t, err := template.ParseFiles(layoutPath) if err != nil { logger.Fatalf("%v", err) @@ -158,7 +158,7 @@ func initURLHandlers(log *log.Logger) { http.Handle("/api/", makePassthroughHandler(dirs.SnapdSocket, "/api/")) // Resources - http.Handle("/web-conf/", loggingHandler(http.FileServer(http.Dir(filepath.Join(os.Getenv("SNAP"), "www"))))) + http.Handle("/public/", loggingHandler(http.FileServer(http.Dir(filepath.Join(os.Getenv("SNAP"), "www"))))) http.HandleFunc("/", makeMainPageHandler()) } diff --git a/gulpfile.js b/gulpfile.js index 453d439a..22878969 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -18,15 +18,23 @@ var uglify = require('gulp-uglify'); var watchify = require('watchify'); gulp.task('js:build', ['js:clean', 'js:lint'], function() { - return createBundler(); + return createBundler('./www/src/js/app.js', 'snapweb.js', 'www/public/js'); }); gulp.task('js:clean', function(cb) { del(['www/public/js'], cb); }); -function createBundler(watch) { - var bundler = browserify('./www/src/js/app.js', { +gulp.task('js:build:webconf', ['js:clean:webconf', 'js:lint'], function() { + return createBundler('./www/src/js/webconf-app.js', 'webconf.js', 'www/public/js'); +}); + +gulp.task('js:clean:webconf', function(cb) { + del(['www/webconf/js'], cb); +}); + +function createBundler(appSrc, appJs, destDir, watch) { + var bundler = browserify(appSrc, { cache: {}, packageCache: {} }); @@ -43,22 +51,22 @@ function createBundler(watch) { } else { } - return bundleShared(bundler); + return bundleShared(bundler, appJs, destDir); } -function bundleShared(bundler) { +function bundleShared(bundler, appJs, destDir) { return bundler.bundle() .on('error', function(err) { gutil.log(gutil.colors.green('Browserify Error: ' + err)); this.emit('end'); process.exit(1); }) - .pipe(source('snapweb.js')) + .pipe(source(appJs)) .pipe(buffer()) .pipe(sourcemaps.init({loadMaps: true})) // loads map from browserify file .pipe(process.env.NODE_ENV === 'development'? gutil.noop() : uglify()) .pipe(sourcemaps.write('./')) // writes .map file - .pipe(gulp.dest('www/public/js/')); + .pipe(gulp.dest(destDir)); } gulp.task('js:lint', function() { diff --git a/www/src/js/routers/webconf-router.js b/www/src/js/routers/webconf-router.js new file mode 100644 index 00000000..f3c2f72f --- /dev/null +++ b/www/src/js/routers/webconf-router.js @@ -0,0 +1,59 @@ +// webconf-router.js + +var Backbone = require('backbone'); +var Marionette = require('backbone.marionette'); + +var homeController = require('../controllers/home.js'); +// var initController = require('../controllers/init.js'); +var searchController = require('../controllers/search.js'); +var storeController = require('../controllers/store.js'); +var settingsController = require('../controllers/settings.js'); +var snapController = require('../controllers/snaps.js'); +var tokenController = require('../controllers/token.js'); + +module.exports = { + + home: new Marionette.AppRouter({ + controller: homeController, + appRoutes: { + '': 'index' + } + }), + + token: new Marionette.AppRouter({ + controller: tokenController, + appRoutes: { + 'access-control': 'index' + } + }), + + store: new Marionette.AppRouter({ + controller: storeController, + appRoutes: { + 'store': 'index', + 'store/section/:section': 'section', + 'search?q=': 'index' + } + }), + + settings: new Marionette.AppRouter({ + controller: settingsController, + appRoutes: { + 'settings': 'index' + } + }), + + snap: new Marionette.AppRouter({ + controller: snapController, + appRoutes: { + 'snap/:id': 'snap', + } + }), + + search: new Marionette.AppRouter({ + controller: searchController, + appRoutes: { + 'search?q=:query': 'query', + } + }) +}; diff --git a/www/src/js/views/webconf-layout.js b/www/src/js/views/webconf-layout.js new file mode 100644 index 00000000..4984beae --- /dev/null +++ b/www/src/js/views/webconf-layout.js @@ -0,0 +1,55 @@ +// webconf layout view +var $ = require('jquery'); +var _ = require('lodash'); +var Backbone = require('backbone'); +Backbone.$ = $; +var Marionette = require('backbone.marionette'); +var React = require('react'); +var ReactDOM = require('react-dom'); +var Radio = require('backbone.radio'); +var BannerView = require('./layout-banner.js'); +var FooterView = require('./layout-footer.js'); +var NotificationsView = require('./alerts.js'); +var template = require('../templates/layout.hbs'); +var chan = Radio.channel('root'); + +module.exports = Marionette.LayoutView.extend({ + + initialize: function() { + chan.comply('set:content', this.setContent, this); + chan.comply('alert:error', this.alertError, this); + }, + + el: '.b-layout', + + template : function() { + return template(); + }, + + onRender: function() { + this.showChildView('bannerRegion', new BannerView()); + this.showChildView('footerRegion', new FooterView()); + }, + + setContent: function(content) { + var reactElement = content.reactElement || null; + if (reactElement !== null) { + ReactDOM.render(reactElement, this.$('.b-layout__main').get(0)); + } else { + this.mainRegion.show(content.backboneView); + } + }, + + alertError: function(model) { + this.showChildView('alertsRegion', new NotificationsView({ + model: model + })); + }, + + regions: { + bannerRegion: '.b-layout__banner', + mainRegion: '.b-layout__main', + footerRegion: '.b-layout__footer', + alertsRegion: '.b-layout__alerts' + } +}); diff --git a/www/src/js/webconf-app.js b/www/src/js/webconf-app.js new file mode 100644 index 00000000..c1a343a9 --- /dev/null +++ b/www/src/js/webconf-app.js @@ -0,0 +1,26 @@ +// webconf-app.js +'use strict'; + +var $ = require('jquery'); +var Backbone = require('backbone'); +Backbone.$ = $; +var Marionette = require('backbone.marionette'); +var Radio = require('backbone.radio'); + +if (window.__agent) { + window.__agent.start(Backbone, Marionette); +} +var LayoutView = require('./views/webconf-layout.js'); +var router = require('./routers/webconf-router.js'); + +var webconf = new Marionette.Application(); +var layout = new LayoutView(); +layout.render(); + +$(document).ready(function() { + webconf.start(); +}); + +webconf.on('start', function() { + Backbone.history.start({pushState: true}); +}); diff --git a/www/templates/webconf.html b/www/templates/webconf.html new file mode 100644 index 00000000..60675f2d --- /dev/null +++ b/www/templates/webconf.html @@ -0,0 +1,24 @@ + + + + web-conf + + + + + + +
+
+
+ +
+ + + + From 164dcbefdf7eaa004820c56594ae55aaaca053ea Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 26 Jan 2017 12:28:21 +0100 Subject: [PATCH 19/55] re-establish the create-user feature in the new webconf module --- www/src/css/styles.scss | 16 ++++ .../js/controllers/{init.js => webconf.js} | 2 +- www/src/js/models/create-user.js | 4 +- www/src/js/routers/router.js | 1 - www/src/js/routers/webconf-router.js | 46 +----------- www/src/js/templates/webconf-layout.hbs | 17 +++++ .../templates/{first-boot.hbs => webconf.hbs} | 24 ++---- www/src/js/views/{init.js => create-user.js} | 8 +- www/src/js/views/webconf-layout.js | 4 +- www/src/js/views/webconf.js | 73 +++++++++++++++++++ 10 files changed, 124 insertions(+), 71 deletions(-) rename www/src/js/controllers/{init.js => webconf.js} (89%) create mode 100644 www/src/js/templates/webconf-layout.hbs rename www/src/js/templates/{first-boot.hbs => webconf.hbs} (72%) rename www/src/js/views/{init.js => create-user.js} (89%) create mode 100644 www/src/js/views/webconf.js diff --git a/www/src/css/styles.scss b/www/src/css/styles.scss index 504ea036..d1caddd8 100644 --- a/www/src/css/styles.scss +++ b/www/src/css/styles.scss @@ -67,3 +67,19 @@ hr { transform: rotate(360deg); } } +// webconf +.region-webconf .inner-wrapper { + background-color: $color-light; +} +.region-webconf .p-card--highlighted { + margin-bottom: 1em; +} + +.p-navigation { + margin-bottom: 0em; +} + +label { + margin-bottom: 0.5em; + display: inline-block; +} diff --git a/www/src/js/controllers/init.js b/www/src/js/controllers/webconf.js similarity index 89% rename from www/src/js/controllers/init.js rename to www/src/js/controllers/webconf.js index 113a8d97..02726dde 100644 --- a/www/src/js/controllers/init.js +++ b/www/src/js/controllers/webconf.js @@ -3,7 +3,7 @@ var Backbone = require('backbone'); Backbone.$ = $; var Marionette = require('backbone.marionette'); var Radio = require('backbone.radio'); -var InitLayoutView = require('../views/init.js'); +var InitLayoutView = require('../views/webconf.js'); var CreateUserModel = require('../models/create-user.js'); module.exports = { diff --git a/www/src/js/models/create-user.js b/www/src/js/models/create-user.js index bd2623ae..f545f55b 100644 --- a/www/src/js/models/create-user.js +++ b/www/src/js/models/create-user.js @@ -5,7 +5,7 @@ var Marionette = require('backbone.marionette'); var CONFIG = require('../config.js'); module.exports = Backbone.Model.extend({ - url: CONFIG.CREATE_USER, + url: '/api/v2/create-user', // forces POST requests on every model update isNew: function() { @@ -21,5 +21,5 @@ module.exports = Backbone.Model.extend({ return 'Invalid email'; } }, - + }); diff --git a/www/src/js/routers/router.js b/www/src/js/routers/router.js index cb05d7df..51f393cf 100644 --- a/www/src/js/routers/router.js +++ b/www/src/js/routers/router.js @@ -4,7 +4,6 @@ var Backbone = require('backbone'); var Marionette = require('backbone.marionette'); var homeController = require('../controllers/home.js'); -// var initController = require('../controllers/init.js'); var searchController = require('../controllers/search.js'); var storeController = require('../controllers/store.js'); var settingsController = require('../controllers/settings.js'); diff --git a/www/src/js/routers/webconf-router.js b/www/src/js/routers/webconf-router.js index f3c2f72f..e68f9bf8 100644 --- a/www/src/js/routers/webconf-router.js +++ b/www/src/js/routers/webconf-router.js @@ -3,57 +3,15 @@ var Backbone = require('backbone'); var Marionette = require('backbone.marionette'); -var homeController = require('../controllers/home.js'); -// var initController = require('../controllers/init.js'); -var searchController = require('../controllers/search.js'); -var storeController = require('../controllers/store.js'); -var settingsController = require('../controllers/settings.js'); -var snapController = require('../controllers/snaps.js'); -var tokenController = require('../controllers/token.js'); +var webconfController = require('../controllers/webconf.js'); module.exports = { home: new Marionette.AppRouter({ - controller: homeController, + controller: webconfController, appRoutes: { '': 'index' } }), - token: new Marionette.AppRouter({ - controller: tokenController, - appRoutes: { - 'access-control': 'index' - } - }), - - store: new Marionette.AppRouter({ - controller: storeController, - appRoutes: { - 'store': 'index', - 'store/section/:section': 'section', - 'search?q=': 'index' - } - }), - - settings: new Marionette.AppRouter({ - controller: settingsController, - appRoutes: { - 'settings': 'index' - } - }), - - snap: new Marionette.AppRouter({ - controller: snapController, - appRoutes: { - 'snap/:id': 'snap', - } - }), - - search: new Marionette.AppRouter({ - controller: searchController, - appRoutes: { - 'search?q=:query': 'query', - } - }) }; diff --git a/www/src/js/templates/webconf-layout.hbs b/www/src/js/templates/webconf-layout.hbs new file mode 100644 index 00000000..a778096b --- /dev/null +++ b/www/src/js/templates/webconf-layout.hbs @@ -0,0 +1,17 @@ +
+
+ +
+
+ diff --git a/www/src/js/templates/first-boot.hbs b/www/src/js/templates/webconf.hbs similarity index 72% rename from www/src/js/templates/first-boot.hbs rename to www/src/js/templates/webconf.hbs index bae50ab7..8a682670 100644 --- a/www/src/js/templates/first-boot.hbs +++ b/www/src/js/templates/webconf.hbs @@ -1,23 +1,16 @@ -
+
-

Ubuntu Core

-

- - Setup an administrator account on this all-snap Ubuntu Core system. After this setup process you will have secure web or command access to the system. -

+

Ubuntu Core

+

+ Setup an administrator account on this all-snap Ubuntu Core system. After this setup process you will have secure web or command access to the system. +

+
+

Create an account

-
-
-

- - -   - - -

+

Create a local system user with the username and SSH keys registered on the store account identified by the provided email address.

@@ -36,7 +29,6 @@

-
diff --git a/www/src/js/views/init.js b/www/src/js/views/create-user.js similarity index 89% rename from www/src/js/views/init.js rename to www/src/js/views/create-user.js index ac0bf3d3..2afbce55 100644 --- a/www/src/js/views/init.js +++ b/www/src/js/views/create-user.js @@ -9,8 +9,8 @@ module.exports = Backbone.Marionette.LayoutView.extend({ className: 'b-layout__container', ui: { - statusmessage: '.statusmessage', - btncreate: '.btn-create', + statusmessage: ".statusmessage", + btncreate: ".btn-create", }, events: { @@ -30,8 +30,8 @@ module.exports = Backbone.Marionette.LayoutView.extend({ this.ui.statusmessage.show(); }, 'success': function(model, response) { - this.model.set({ipaddress: location.hostname}); - this.model.set({username: response.result.username}); + this.model.set({ ipaddress: location.hostname }); + this.model.set({ username: response.result.username }); this.$('#firstboot-step-1').hide(); this.$('#firstboot-step-2').show(); }, diff --git a/www/src/js/views/webconf-layout.js b/www/src/js/views/webconf-layout.js index 4984beae..03723868 100644 --- a/www/src/js/views/webconf-layout.js +++ b/www/src/js/views/webconf-layout.js @@ -7,10 +7,9 @@ var Marionette = require('backbone.marionette'); var React = require('react'); var ReactDOM = require('react-dom'); var Radio = require('backbone.radio'); -var BannerView = require('./layout-banner.js'); var FooterView = require('./layout-footer.js'); var NotificationsView = require('./alerts.js'); -var template = require('../templates/layout.hbs'); +var template = require('../templates/webconf-layout.hbs'); var chan = Radio.channel('root'); module.exports = Marionette.LayoutView.extend({ @@ -27,7 +26,6 @@ module.exports = Marionette.LayoutView.extend({ }, onRender: function() { - this.showChildView('bannerRegion', new BannerView()); this.showChildView('footerRegion', new FooterView()); }, diff --git a/www/src/js/views/webconf.js b/www/src/js/views/webconf.js new file mode 100644 index 00000000..6dcfa644 --- /dev/null +++ b/www/src/js/views/webconf.js @@ -0,0 +1,73 @@ +var Backbone = require('backbone'); +var Marionette = require('backbone.marionette'); +var template = require('../templates/webconf.hbs'); + +module.exports = Backbone.Marionette.LayoutView.extend({ + + className: 'b-layout__container', + + ui: { + statusmessage: '.statusmessage', + btncreate: '.btn-create', + }, + + events: { + 'click #btn-create': 'handleCreate', + }, + + modelEvents: { + 'status-update': function(msg) { + this.ui.statusmessage.html(msg); + this.ui.statusmessage.removeClass('has-error'); + this.ui.statusmessage.removeClass('has-warning'); + this.ui.statusmessage.show(); + }, + 'invalid': function(model, error) { + this.ui.statusmessage.text(error); + this.ui.statusmessage.addClass('has-error'); + this.ui.statusmessage.show(); + }, + 'success': function(model, response) { + this.model.set({ipaddress: location.hostname}); + this.model.set({username: response.result.username}); + this.$('#firstboot-step-1').hide(); + this.$('#firstboot-step-2').show(); + }, + 'change': function() { + this.render(); + }, + }, + + handleCreate: function(event) { + event.preventDefault(); + this.model.set({ + email: this.$('#emailSSO').val(), + sudoer: true, + }); + if (this.model.isValid()) { + this.model.trigger('status-update', 'Contacting store...'); // via snapd... + this.model.save({}, { + success: function(model, response) { + model.trigger('success', model, response); + }, + error: function(model, response) { + var message = ""; + var resp = eval(response); + if (resp && resp.responseJSON && + resp.responseJSON.result) { + message = resp.responseJSON.result.message; + } else { + message = response.responseText; + } + + model.trigger('invalid', model, message); + } + }); + } + }, + + template : function(model) { + return template(model); + }, + +}); From b3e7c5b11ea7e9af063c18ac3d70090de7f0a584 Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 26 Jan 2017 17:44:12 +0100 Subject: [PATCH 20/55] lock down web-conf again; update spread tests --- cmd/web-conf/main.go | 2 +- spread.yaml | 5 +++-- spread/webconf/snapweb-not/task.yaml | 19 +++++++++++++++++++ .../webconf-runs}/task.yaml | 8 ++++---- 4 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 spread/webconf/snapweb-not/task.yaml rename spread/{firstboot/firstboot-runs => webconf/webconf-runs}/task.yaml (71%) diff --git a/cmd/web-conf/main.go b/cmd/web-conf/main.go index ed63aea6..30229972 100644 --- a/cmd/web-conf/main.go +++ b/cmd/web-conf/main.go @@ -167,7 +167,7 @@ func main() { if IsDeviceManaged() { log.Println("web-conf does not run on managed devices") - // os.Exit(0) + os.Exit(0) } initURLHandlers(logger) diff --git a/spread.yaml b/spread.yaml index 46cccbfc..3199fc56 100644 --- a/spread.yaml +++ b/spread.yaml @@ -44,6 +44,7 @@ exclude: - key.pem - node_modules - snapweb + - web-conf - tests - releases - generate_token @@ -61,8 +62,8 @@ suites: . $TESTSLIB/prepare.sh restore-each: | . $TESTSLIB/restore-each.sh - spread/firstboot/: - summary: Full-system tests for the firstboot variant of snapweb + spread/webconf/: + summary: Full-system tests for the webconf variant of snapweb environment: FIRSTBOOT: yes systems: diff --git a/spread/webconf/snapweb-not/task.yaml b/spread/webconf/snapweb-not/task.yaml new file mode 100644 index 00000000..72d41165 --- /dev/null +++ b/spread/webconf/snapweb-not/task.yaml @@ -0,0 +1,19 @@ +summary: Verify that webconf will start on an un-managed system by default +environment: + SEED_DIR: /var/lib/snapd/seed +prepare: | + systemctl stop snapd.service + rm -f /var/lib/snapd/state.json +restore: | + systemctl start snapd.service +execute: | + echo "Start the daemon with an empty state, this will make it import " + echo "assertions from the $SEED_DIR/assertions subdirectory." + systemctl start snapd.service + + echo "The system should not be managed yet" + test `snap managed` = 'false' + + echo "Verifying that snapweb is NOT running as normal" + test `ps ax | grep -- -linux-gnu/snapweb | grep -v grep | grep -v web-conf | wc -l` -eq 0 + \ No newline at end of file diff --git a/spread/firstboot/firstboot-runs/task.yaml b/spread/webconf/webconf-runs/task.yaml similarity index 71% rename from spread/firstboot/firstboot-runs/task.yaml rename to spread/webconf/webconf-runs/task.yaml index ad59a10d..ce000d0a 100644 --- a/spread/firstboot/firstboot-runs/task.yaml +++ b/spread/webconf/webconf-runs/task.yaml @@ -1,4 +1,4 @@ -summary: Verify that firstboot will start on an un-managed system by default +summary: Verify that webconf will start on an un-managed system by default environment: SEED_DIR: /var/lib/snapd/seed prepare: | @@ -15,8 +15,8 @@ execute: | test `snap managed` = 'false' echo "Verifying that snapweb is NOT running as normal" - test `ps ax | grep -- -linux-gnu/snapweb | grep -v grep | grep -v firstboot | wc -l` -eq 0 + test `ps ax | grep -- -linux-gnu/snapweb | grep -v grep | grep -v web-conf | wc -l` -eq 0 - echo "Verifying that firstboot is running" - test `ps ax | grep -- -linux-gnu/firstboot | grep -v grep | wc -l` -eq 1 + echo "Verifying that webconf is running" + test `ps ax | grep -- -linux-gnu/web-conf | grep -v grep | wc -l` -eq 1 \ No newline at end of file From 61a8590e9551084d86b7c750bdb88e579ce7da38 Mon Sep 17 00:00:00 2001 From: David Barth Date: Mon, 30 Jan 2017 16:58:27 +0100 Subject: [PATCH 21/55] add missing wrapper for starting webconf; adjust command names --- pkg/meta/snap.yaml | 4 ++-- pkg/webconf | 26 ++++++++++++++++++++++++++ spread.yaml | 2 +- spread/webconf/webconf-runs/task.yaml | 9 --------- 4 files changed, 29 insertions(+), 12 deletions(-) create mode 100755 pkg/webconf diff --git a/pkg/meta/snap.yaml b/pkg/meta/snap.yaml index 51aea3c2..8fb49e88 100644 --- a/pkg/meta/snap.yaml +++ b/pkg/meta/snap.yaml @@ -16,9 +16,9 @@ apps: daemon: simple command: snapweb plugs: [network, network-bind, snapd-control, timeserver-control, timezone-control] - web-conf: + webconf: daemon: simple - command: web-conf + command: webconf plugs: [network, network-bind, snapd-control, timeserver-control] generate-token: command: generate-token diff --git a/pkg/webconf b/pkg/webconf new file mode 100755 index 00000000..d69a7c02 --- /dev/null +++ b/pkg/webconf @@ -0,0 +1,26 @@ +#!/bin/sh +set -e + +case $SNAP_ARCH in + amd64|x86_64) + plat_abi=x86_64-linux-gnu + ;; + armhf) + plat_abi=arm-linux-gnueabihf + ;; + arm64) + plat_abi=aarch64-linux-gnu + ;; + i386) + plat_abi=i686-linux-gnu + ;; + *) + echo "unknown platform for snappy-magic: $SNAP_ARCH. remember to file a bug or better yet: fix it :)" + exit 1 + ;; +esac + +exec $SNAP/bin/$plat_abi/web-conf + +# never reach this +exit 1 diff --git a/spread.yaml b/spread.yaml index 3199fc56..86193742 100644 --- a/spread.yaml +++ b/spread.yaml @@ -68,7 +68,7 @@ suites: FIRSTBOOT: yes systems: - ubuntu-core-16-embryonic - prepare: | + prepare: . $TESTSLIB/prepare.sh restore-each: | . $TESTSLIB/restore-each.sh diff --git a/spread/webconf/webconf-runs/task.yaml b/spread/webconf/webconf-runs/task.yaml index ce000d0a..37007df5 100644 --- a/spread/webconf/webconf-runs/task.yaml +++ b/spread/webconf/webconf-runs/task.yaml @@ -1,16 +1,7 @@ summary: Verify that webconf will start on an un-managed system by default environment: SEED_DIR: /var/lib/snapd/seed -prepare: | - systemctl stop snapd.service - rm -f /var/lib/snapd/state.json -restore: | - systemctl start snapd.service execute: | - echo "Start the daemon with an empty state, this will make it import " - echo "assertions from the $SEED_DIR/assertions subdirectory." - systemctl start snapd.service - echo "The system should not be managed yet" test `snap managed` = 'false' From a6518a44d482fdf649d177ce5da5c9f7c65ef99f Mon Sep 17 00:00:00 2001 From: David Barth Date: Tue, 31 Jan 2017 16:10:00 +0100 Subject: [PATCH 22/55] don't panic, just wait for a signal from webconf --- cmd/snapweb/main.go | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/cmd/snapweb/main.go b/cmd/snapweb/main.go index 68875e97..acbed7ad 100644 --- a/cmd/snapweb/main.go +++ b/cmd/snapweb/main.go @@ -21,7 +21,11 @@ import ( "log" "net/http" "os" + "os/signal" "path/filepath" + "sync" + "syscall" + "strings" "github.com/snapcore/snapweb/avahi" "github.com/snapcore/snapweb/snappy/app" @@ -43,11 +47,31 @@ func init() { } } +func redir(w http.ResponseWriter, req *http.Request) { + http.Redirect(w, req, + "https://"+strings.Replace(req.Host, httpAddr, httpsAddr, -1), + http.StatusSeeOther) +} + +func WaitForWebConfSignal() { + var waiter sync.WaitGroup + waiter.Add(1) + var sigchan chan os.Signal + sigchan = make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGHUP) + go func() { + <-sigchan + waiter.Done() + }() + waiter.Wait() +} + func main() { config := readConfig() - if ! IsManaged() { - panic("Snapweb does not run on un-managed devices") + for ! IsDeviceManaged() { + logger.Println("Snapweb cannot run until the device is managed...") + WaitForWebConfSignal() } GenerateCertificate() From d8d6f011b04b0c041c842e2f82f215b15e27f98a Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 2 Feb 2017 17:02:21 +0100 Subject: [PATCH 23/55] gofmt --- cmd/snapweb/main.go | 4 ++-- cmd/web-conf/main.go | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cmd/snapweb/main.go b/cmd/snapweb/main.go index acbed7ad..55beb8da 100644 --- a/cmd/snapweb/main.go +++ b/cmd/snapweb/main.go @@ -23,9 +23,9 @@ import ( "os" "os/signal" "path/filepath" + "strings" "sync" "syscall" - "strings" "github.com/snapcore/snapweb/avahi" "github.com/snapcore/snapweb/snappy/app" @@ -63,7 +63,7 @@ func WaitForWebConfSignal() { <-sigchan waiter.Done() }() - waiter.Wait() + waiter.Wait() } func main() { diff --git a/cmd/web-conf/main.go b/cmd/web-conf/main.go index 30229972..1ccec4e2 100644 --- a/cmd/web-conf/main.go +++ b/cmd/web-conf/main.go @@ -27,8 +27,8 @@ import ( "strings" "text/template" - "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/client" + "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapweb/avahi" ) @@ -36,8 +36,7 @@ import ( var logger *log.Logger const ( - httpAddr string = ":4200" - // httpsAddr string = ":4201" + httpAddr string = ":4200" ) func init() { @@ -147,9 +146,9 @@ func makeMainPageHandler() http.HandlerFunc { if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Println(err) - return; + return } - + } } From 202e1fd3a6efa0f6e79502a2c23b19a434246f0f Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 2 Feb 2017 20:28:00 +0100 Subject: [PATCH 24/55] webconf now signals snapweb when it is done --- cmd/snapweb/main.go | 23 +++++++++++++++++++++-- cmd/web-conf/main.go | 27 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/cmd/snapweb/main.go b/cmd/snapweb/main.go index 55beb8da..631807cf 100644 --- a/cmd/snapweb/main.go +++ b/cmd/snapweb/main.go @@ -18,6 +18,7 @@ package main import ( + "fmt" "log" "net/http" "os" @@ -26,6 +27,7 @@ import ( "strings" "sync" "syscall" + "time" "github.com/snapcore/snapweb/avahi" "github.com/snapcore/snapweb/snappy/app" @@ -53,7 +55,21 @@ func redir(w http.ResponseWriter, req *http.Request) { http.StatusSeeOther) } -func WaitForWebConfSignal() { +func writePidFile() { + var err error + + pidFilePath := filepath.Join(os.Getenv("SNAP_DATA"), "snapweb.pid") + + if f, err := os.OpenFile(pidFilePath, os.O_CREATE|os.O_RDWR, os.ModeTemporary|0640); err == nil { + fmt.Fprintf(f, "%d\n", os.Getpid()) + } + if err != nil { + log.Println(err) + } + +} + +func waitForSigHup() { var waiter sync.WaitGroup waiter.Add(1) var sigchan chan os.Signal @@ -71,7 +87,10 @@ func main() { for ! IsDeviceManaged() { logger.Println("Snapweb cannot run until the device is managed...") - WaitForWebConfSignal() + writePidFile() + waitForSigHup() + // wait futher more, to let webconf release the 4200 port + time.Sleep(1000) } GenerateCertificate() diff --git a/cmd/web-conf/main.go b/cmd/web-conf/main.go index 1ccec4e2..79e67c17 100644 --- a/cmd/web-conf/main.go +++ b/cmd/web-conf/main.go @@ -18,6 +18,7 @@ package main import ( + "fmt" "io" "log" "net" @@ -25,6 +26,7 @@ import ( "os" "path/filepath" "strings" + "syscall" "text/template" "github.com/snapcore/snapd/client" @@ -152,9 +154,34 @@ func makeMainPageHandler() http.HandlerFunc { } } +func sendSignalToSnapweb() { + var pid int + var err error + + pidFilePath := filepath.Join(os.Getenv("SNAP_DATA"), "snapweb.pid") + + if f, err := os.Open(pidFilePath); err == nil { + if _, err = fmt.Fscanf(f, "%d\n", &pid); err == nil { + p, _ := os.FindProcess(pid) + err = p.Signal(syscall.Signal(syscall.SIGHUP)) + } + } + if err != nil { + log.Println(err) + } +} + +func doneHandler() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + logger.Fatal("webconf done") + } +} + func initURLHandlers(log *log.Logger) { // API http.Handle("/api/", makePassthroughHandler(dirs.SnapdSocket, "/api/")) + http.HandleFunc("/done", doneHandler()) // Resources http.Handle("/public/", loggingHandler(http.FileServer(http.Dir(filepath.Join(os.Getenv("SNAP"), "www"))))) From b9f8bfb5859aa7d359742f0714698417a2ee428f Mon Sep 17 00:00:00 2001 From: David Barth Date: Fri, 3 Feb 2017 17:04:08 +0100 Subject: [PATCH 25/55] send signal to snapweb, send /done url; build webconf with gulp by default --- cmd/web-conf/main.go | 2 +- gulpfile.js | 2 +- www/src/js/templates/webconf.hbs | 2 +- www/tests/{firstBootSpec.js => webconfSpec.js} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename www/tests/{firstBootSpec.js => webconfSpec.js} (100%) diff --git a/cmd/web-conf/main.go b/cmd/web-conf/main.go index 79e67c17..9d7c545d 100644 --- a/cmd/web-conf/main.go +++ b/cmd/web-conf/main.go @@ -173,7 +173,7 @@ func sendSignalToSnapweb() { func doneHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - + sendSignalToSnapweb() logger.Fatal("webconf done") } } diff --git a/gulpfile.js b/gulpfile.js index 22878969..4af49fd3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -141,4 +141,4 @@ gulp.task('install', ['default'], function() { .pipe(gulp.dest('../install')); }); -gulp.task('default', ['js:build', 'styles', 'images']); +gulp.task('default', ['js:build', 'js:build:webconf', 'styles', 'images']); diff --git a/www/src/js/templates/webconf.hbs b/www/src/js/templates/webconf.hbs index 8a682670..ac84342d 100644 --- a/www/src/js/templates/webconf.hbs +++ b/www/src/js/templates/webconf.hbs @@ -48,7 +48,7 @@ ssh {{username}}@{{ipaddress}}

- + Manage your device

diff --git a/www/tests/firstBootSpec.js b/www/tests/webconfSpec.js similarity index 100% rename from www/tests/firstBootSpec.js rename to www/tests/webconfSpec.js From 7ec6d53f265da99412353165a51202088abd3a97 Mon Sep 17 00:00:00 2001 From: David Barth Date: Tue, 7 Feb 2017 13:59:32 +0100 Subject: [PATCH 26/55] stop webconf and let snapweb resume --- cmd/web-conf/main.go | 13 ++++++++++--- spread/webconf/snapweb-not/task.yaml | 19 ------------------- spread/webconf/webconf-runs/task.yaml | 8 +++----- 3 files changed, 13 insertions(+), 27 deletions(-) delete mode 100644 spread/webconf/snapweb-not/task.yaml diff --git a/cmd/web-conf/main.go b/cmd/web-conf/main.go index 9d7c545d..bc329385 100644 --- a/cmd/web-conf/main.go +++ b/cmd/web-conf/main.go @@ -27,6 +27,7 @@ import ( "path/filepath" "strings" "syscall" + "time" "text/template" "github.com/snapcore/snapd/client" @@ -36,6 +37,7 @@ import ( ) var logger *log.Logger +var server net.Listener const ( httpAddr string = ":4200" @@ -174,7 +176,7 @@ func sendSignalToSnapweb() { func doneHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { sendSignalToSnapweb() - logger.Fatal("webconf done") + server.Close() } } @@ -190,7 +192,8 @@ func initURLHandlers(log *log.Logger) { } func main() { - + var err error + if IsDeviceManaged() { log.Println("web-conf does not run on managed devices") os.Exit(0) @@ -201,8 +204,12 @@ func main() { go avahi.InitMDNS(logger) // open a plain HTTP end-point on the "usual" 4200 port - if err := http.ListenAndServe(httpAddr, nil); err != nil { + if server, err = net.Listen("tcp", httpAddr); err != nil { logger.Fatalf("%v", err) } + http.Serve(server, nil) + + // let snapweb start + time.Sleep(2 * time.Second) } diff --git a/spread/webconf/snapweb-not/task.yaml b/spread/webconf/snapweb-not/task.yaml deleted file mode 100644 index 72d41165..00000000 --- a/spread/webconf/snapweb-not/task.yaml +++ /dev/null @@ -1,19 +0,0 @@ -summary: Verify that webconf will start on an un-managed system by default -environment: - SEED_DIR: /var/lib/snapd/seed -prepare: | - systemctl stop snapd.service - rm -f /var/lib/snapd/state.json -restore: | - systemctl start snapd.service -execute: | - echo "Start the daemon with an empty state, this will make it import " - echo "assertions from the $SEED_DIR/assertions subdirectory." - systemctl start snapd.service - - echo "The system should not be managed yet" - test `snap managed` = 'false' - - echo "Verifying that snapweb is NOT running as normal" - test `ps ax | grep -- -linux-gnu/snapweb | grep -v grep | grep -v web-conf | wc -l` -eq 0 - \ No newline at end of file diff --git a/spread/webconf/webconf-runs/task.yaml b/spread/webconf/webconf-runs/task.yaml index 37007df5..4c9cf926 100644 --- a/spread/webconf/webconf-runs/task.yaml +++ b/spread/webconf/webconf-runs/task.yaml @@ -1,13 +1,11 @@ summary: Verify that webconf will start on an un-managed system by default -environment: - SEED_DIR: /var/lib/snapd/seed execute: | echo "The system should not be managed yet" test `snap managed` = 'false' - echo "Verifying that snapweb is NOT running as normal" - test `ps ax | grep -- -linux-gnu/snapweb | grep -v grep | grep -v web-conf | wc -l` -eq 0 - echo "Verifying that webconf is running" test `ps ax | grep -- -linux-gnu/web-conf | grep -v grep | wc -l` -eq 1 + + echo "Verifying that webconf is controlling port 4200" + printf "GET / HTTP/1.1\nHost:localhost\nUser-Agent:nc\n\n" | nc localhost 4200 | grep "script src" | grep webconf.js \ No newline at end of file From 4341aaef2318d55d8f8f151340448125e3edb968 Mon Sep 17 00:00:00 2001 From: David Barth Date: Mon, 13 Feb 2017 17:48:08 +0100 Subject: [PATCH 27/55] verify the transition from webconf to snapweb --- spread/webconf/transition-to-snapweb/task.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 spread/webconf/transition-to-snapweb/task.yaml diff --git a/spread/webconf/transition-to-snapweb/task.yaml b/spread/webconf/transition-to-snapweb/task.yaml new file mode 100644 index 00000000..7fbb2c19 --- /dev/null +++ b/spread/webconf/transition-to-snapweb/task.yaml @@ -0,0 +1,18 @@ +summary: Verify that webconf will transition to snapweb once done +execute: | + echo "The system should not be managed yet" + test `snap managed` = 'false' + + echo "Finish the configuration" + snap create-user david.barth@canonical.com + printf "GET /done HTTP/1.1\nHost:localhost\nUser-Agent:nc\n\n" | nc localhost 4200 + + # wait a bit + sleep 2 + + echo "Verifying that snapweb now serves on port 4200" + printf "GET / HTTP/1.1\nHost:localhost\nUser-Agent:nc\n\n" | nc localhost 4201 + + # echo "The system should be managed now" + # test `snap managed` = 'false' + \ No newline at end of file From f8f9644e4e762da1134e1fc0c22e34ae1083225f Mon Sep 17 00:00:00 2001 From: David Barth Date: Wed, 15 Feb 2017 19:23:07 +0100 Subject: [PATCH 28/55] rename leftover bits to webconf, no dash --- build.sh | 2 +- cmd/{web-conf => webconf}/main.go | 0 pkg/webconf | 2 +- spread/webconf/webconf-runs/task.yaml | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename cmd/{web-conf => webconf}/main.go (100%) diff --git a/build.sh b/build.sh index 43ea5254..b305c545 100755 --- a/build.sh +++ b/build.sh @@ -60,7 +60,7 @@ gobuild() { mkdir -p $output_dir cd $output_dir GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -ldflags "-extld=${plat_abi}-gcc" github.com/snapcore/snapweb/cmd/snapweb - GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -ldflags "-extld=${plat_abi}-gcc" github.com/snapcore/snapweb/cmd/web-conf + GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -ldflags "-extld=${plat_abi}-gcc" github.com/snapcore/snapweb/cmd/webconf GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -o generate-token -ldflags "-extld=${plat_abi}-gcc" $srcdir/cmd/generate-token/main.go cp generate-token ../../ cd - > /dev/null diff --git a/cmd/web-conf/main.go b/cmd/webconf/main.go similarity index 100% rename from cmd/web-conf/main.go rename to cmd/webconf/main.go diff --git a/pkg/webconf b/pkg/webconf index d69a7c02..7f6cdb67 100755 --- a/pkg/webconf +++ b/pkg/webconf @@ -20,7 +20,7 @@ case $SNAP_ARCH in ;; esac -exec $SNAP/bin/$plat_abi/web-conf +exec $SNAP/bin/$plat_abi/webconf # never reach this exit 1 diff --git a/spread/webconf/webconf-runs/task.yaml b/spread/webconf/webconf-runs/task.yaml index 4c9cf926..53a54cdb 100644 --- a/spread/webconf/webconf-runs/task.yaml +++ b/spread/webconf/webconf-runs/task.yaml @@ -4,7 +4,7 @@ execute: | test `snap managed` = 'false' echo "Verifying that webconf is running" - test `ps ax | grep -- -linux-gnu/web-conf | grep -v grep | wc -l` -eq 1 + test `ps ax | grep -- -linux-gnu/webconf | grep -v grep | wc -l` -eq 1 echo "Verifying that webconf is controlling port 4200" printf "GET / HTTP/1.1\nHost:localhost\nUser-Agent:nc\n\n" | nc localhost 4200 | grep "script src" | grep webconf.js From dbfbdc2e02b28faae3a9982b4f40aa303958d291 Mon Sep 17 00:00:00 2001 From: David Barth Date: Wed, 15 Feb 2017 19:42:44 +0100 Subject: [PATCH 29/55] simplify run-checks by offloading all the npm setup to a travis-specifc script --- run-checks | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/run-checks b/run-checks index ce00d03f..11c8f910 100755 --- a/run-checks +++ b/run-checks @@ -70,6 +70,13 @@ append_go_coverage() { fi } +<<<<<<< 971c1ef288302ac7e8707cc5f01e2a6b5bae014e +======= +if [ ! -z "$STATIC_GO" ] || [ ! -z "$UNIT_GO" ]; then + sh ./get-go-deps.sh +fi + +>>>>>>> simplify run-checks by offloading all the npm setup to a travis-specifc script if [ ! -z "$STATIC_GO" ]; then # Run go static tests. From 755c6403fed4ac9bb45d7919d0fa786f53f5a5f5 Mon Sep 17 00:00:00 2001 From: David Barth Date: Wed, 15 Feb 2017 19:52:39 +0100 Subject: [PATCH 30/55] do the same for godeps --- run-checks | 7 ------- 1 file changed, 7 deletions(-) diff --git a/run-checks b/run-checks index 11c8f910..ce00d03f 100755 --- a/run-checks +++ b/run-checks @@ -70,13 +70,6 @@ append_go_coverage() { fi } -<<<<<<< 971c1ef288302ac7e8707cc5f01e2a6b5bae014e -======= -if [ ! -z "$STATIC_GO" ] || [ ! -z "$UNIT_GO" ]; then - sh ./get-go-deps.sh -fi - ->>>>>>> simplify run-checks by offloading all the npm setup to a travis-specifc script if [ ! -z "$STATIC_GO" ]; then # Run go static tests. From e6c31c841cd4e59c8efb747754398dc95cc79772 Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 16 Feb 2017 14:41:12 +0100 Subject: [PATCH 31/55] refactor common functions into a helper module; add tests for it --- cmd/webconf/main.go | 93 +++----------------------- snappy/app/fake_snapd_client.go | 8 +++ snappy/helpers.go | 104 +++++++++++++++++++++++++++++ snappy/helpers_test.go | 115 ++++++++++++++++++++++++++++++++ 4 files changed, 237 insertions(+), 83 deletions(-) create mode 100644 snappy/helpers.go create mode 100644 snappy/helpers_test.go diff --git a/cmd/webconf/main.go b/cmd/webconf/main.go index bc329385..645631c9 100644 --- a/cmd/webconf/main.go +++ b/cmd/webconf/main.go @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2017 Canonical Ltd + * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -19,21 +19,19 @@ package main import ( "fmt" - "io" "log" "net" "net/http" "os" "path/filepath" - "strings" "syscall" - "time" "text/template" + "time" - "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapweb/avahi" + "github.com/snapcore/snapweb/snappy" ) var logger *log.Logger @@ -44,78 +42,7 @@ const ( ) func init() { - logger = log.New(os.Stderr, "web-conf: ", log.Ldate|log.Ltime|log.Lshortfile) -} - -// IsDeviceManaged determines if the device is in the 'managed' state -func IsDeviceManaged() bool { - client := client.New(nil) - - sysInfo, err := client.SysInfo() - if err != nil { - panic(err) - } - - if sysInfo.OnClassic || sysInfo.Managed { - return true - } - - return false -} - -func unixDialer(socketPath string) func(string, string) (net.Conn, error) { - file, err := os.OpenFile(socketPath, os.O_RDWR, 0666) - if err == nil { - file.Close() - } - - return func(_, _ string) (net.Conn, error) { - return net.Dial("unix", socketPath) - } -} - -func makePassthroughHandler(socketPath string, prefix string) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c := &http.Client{ - Transport: &http.Transport{Dial: unixDialer(socketPath)}, - } - - log.Println(r.Method, r.URL.Path) - - // need to remove the RequestURI field - // and remove the /api prefix from snapweb URLs - r.URL.Scheme = "http" - r.URL.Host = "localhost" - r.URL.Path = strings.TrimPrefix(r.URL.Path, prefix) - - outreq, err := http.NewRequest(r.Method, r.URL.String(), r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resp, err := c.Do(outreq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Note: the client.Do method above only returns JSON responses - // even if it doesn't say so - hdr := w.Header() - hdr.Set("Content-Type", "application/json") - w.WriteHeader(resp.StatusCode) - - io.Copy(w, resp.Body) - - }) -} - -func loggingHandler(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.URL.Path) - h.ServeHTTP(w, r) - }) + logger = log.New(os.Stderr, "webconf: ", log.Ldate|log.Ltime|log.Lshortfile) } type branding struct { @@ -182,20 +109,20 @@ func doneHandler() http.HandlerFunc { func initURLHandlers(log *log.Logger) { // API - http.Handle("/api/", makePassthroughHandler(dirs.SnapdSocket, "/api/")) + http.Handle("/api/", snappy.MakePassthroughHandler(dirs.SnapdSocket, "/api/")) http.HandleFunc("/done", doneHandler()) // Resources - http.Handle("/public/", loggingHandler(http.FileServer(http.Dir(filepath.Join(os.Getenv("SNAP"), "www"))))) + http.Handle("/public/", snappy.LoggingHandler(http.FileServer(http.Dir(filepath.Join(os.Getenv("SNAP"), "www"))))) http.HandleFunc("/", makeMainPageHandler()) } func main() { var err error - - if IsDeviceManaged() { - log.Println("web-conf does not run on managed devices") + + if snappy.IsDeviceManaged() { + log.Println("webconf does not run on managed devices") os.Exit(0) } @@ -210,6 +137,6 @@ func main() { http.Serve(server, nil) - // let snapweb start + // prepare to exit, but let snapweb start before that time.Sleep(2 * time.Second) } diff --git a/snappy/app/fake_snapd_client.go b/snappy/app/fake_snapd_client.go index 9cf16705..731246dc 100644 --- a/snappy/app/fake_snapd_client.go +++ b/snappy/app/fake_snapd_client.go @@ -156,6 +156,14 @@ func (f *FakeSnapdClient) Disable(name string, options *client.SnapOptions) (str func (f *FakeSnapdClient) Abort(id string) (*client.Change, error) { f.AbortedChangeID = id return nil, nil + +// Query system information +func (f *FakeSnapdClient) SysInfo() (*client.SysInfo, error) { + managed := &client.SysInfo{ + OnClassic: true, + Managed: true, + } + return managed, nil } var _ snapdclient.SnapdClient = (*FakeSnapdClient)(nil) diff --git a/snappy/helpers.go b/snappy/helpers.go new file mode 100644 index 00000000..84fc4703 --- /dev/null +++ b/snappy/helpers.go @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2017 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/* This module gathers a set of helper functions and structures shared + by the various commands in snapweb +*/ + +package snappy + +import ( + "io" + "log" + "net" + "net/http" + "os" + "strings" + + "github.com/snapcore/snapd/client" +) + +// IsDeviceManaged determines if the device is in the 'managed' state +func IsDeviceManaged() bool { + client := client.New(nil) + + sysInfo, err := client.SysInfo() + if err != nil { + return false + } + + if sysInfo.OnClassic || sysInfo.Managed { + return true + } + + return false +} + +func unixDialer(socketPath string) func(string, string) (net.Conn, error) { + file, err := os.OpenFile(socketPath, os.O_RDWR, 0666) + if err == nil { + file.Close() + } + + return func(_, _ string) (net.Conn, error) { + return net.Dial("unix", socketPath) + } +} + +func MakePassthroughHandler(socketPath string, prefix string) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c := &http.Client{ + Transport: &http.Transport{Dial: unixDialer(socketPath)}, + } + + log.Println(r.Method, r.URL.Path) + + // need to remove the RequestURI field + // and remove the /api prefix from snapweb URLs + r.URL.Scheme = "http" + r.URL.Host = "localhost" + r.URL.Path = strings.TrimPrefix(r.URL.Path, prefix) + + outreq, err := http.NewRequest(r.Method, r.URL.String(), r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + resp, err := c.Do(outreq) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Note: the client.Do method above only returns JSON responses + // even if it doesn't say so + hdr := w.Header() + hdr.Set("Content-Type", "application/json") + w.WriteHeader(resp.StatusCode) + + io.Copy(w, resp.Body) + + }) +} + +func LoggingHandler(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.URL.Path) + h.ServeHTTP(w, r) + }) +} diff --git a/snappy/helpers_test.go b/snappy/helpers_test.go new file mode 100644 index 00000000..f9428079 --- /dev/null +++ b/snappy/helpers_test.go @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2016 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package snappy + +import ( + "bytes" + "fmt" + "log" + "net" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapweb/snappy/app" +) + +func Test(t *testing.T) { TestingT(t) } + +type HandlersSuite struct { + c *snappy.FakeSnapdClient +} + +var _ = Suite(&HandlersSuite{}) + +func (s *HandlersSuite) SetUpTest(c *C) { + s.c = &snappy.FakeSnapdClient{} + + s.c.Version.Version = "1000" + s.c.Version.Series = "16" + + s.c.Err = nil +} + +func (s *HandlersSuite) TearDownTest(c *C) { +} + +func (s *HandlersSuite) TestIsDeviceManaged(c *C) { + c.Assert(IsDeviceManaged(), Equals, true) +} + +func (s *HandlersSuite) TestLoggingHandler(c *C) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + logged := LoggingHandler(handler) + + var output bytes.Buffer + log.SetOutput(&output) + defer func() { + log.SetOutput(os.Stdout) + }() + + rec := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/foo", nil) + c.Assert(err, IsNil) + + logged.ServeHTTP(rec, req) + + c.Assert(output.String(), Matches, ".*GET /foo\n") +} + +func (s *HandlersSuite) TestPassthroughHandler(c *C) { + socketPath := "/tmp/snapd-test.socket" + c.Assert(os.MkdirAll(filepath.Dir(socketPath), 0755), IsNil) + l, err := net.Listen("unix", socketPath) + if err != nil { + c.Fatalf("unable to listen on %q: %v", socketPath, err) + } + + f := func(w http.ResponseWriter, r *http.Request) { + c.Check(r.URL.Path, Equals, "/v2/system-info") + c.Check(r.URL.RawQuery, Equals, "") + + fmt.Fprintln(w, `{"type":"sync", "result":{"series":"42"}}`) + } + + srv := &httptest.Server{ + Listener: l, + Config: &http.Server{Handler: http.HandlerFunc(f)}, + } + srv.Start() + defer srv.Close() + + handler := http.HandlerFunc(MakePassthroughHandler(socketPath, "/api")) + + rec := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/api/v2/system-info", nil) + c.Assert(err, IsNil) + + // req.AddCookie(&http.Cookie{Name: SnapwebCookieName, Value: "1234"}) + + handler(rec, req) + body := rec.Body.String() + c.Assert(rec.Code, Equals, http.StatusOK) + c.Check(strings.Contains(body, "42"), Equals, true) + // TODO: check that we receive Content-Type: json/application +} From ea4b776b7149ea5834739acf8cba7246f555d4f6 Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 16 Feb 2017 17:35:22 +0100 Subject: [PATCH 32/55] refactor code shared between webconf and snapweb; add more tests --- cmd/snapweb/main.go | 35 +------------ cmd/webconf/main.go | 20 ++++---- cmd/webconf/main_test.go | 91 +++++++++++++++++++++++++++++++++ snappy/app/fake_snapd_client.go | 2 +- snappy/helpers.go | 33 ++++++++++++ snappy/helpers_test.go | 6 +++ 6 files changed, 143 insertions(+), 44 deletions(-) create mode 100644 cmd/webconf/main_test.go diff --git a/cmd/snapweb/main.go b/cmd/snapweb/main.go index 631807cf..7451d6ce 100644 --- a/cmd/snapweb/main.go +++ b/cmd/snapweb/main.go @@ -18,15 +18,11 @@ package main import ( - "fmt" "log" "net/http" "os" - "os/signal" "path/filepath" "strings" - "sync" - "syscall" "time" "github.com/snapcore/snapweb/avahi" @@ -55,40 +51,13 @@ func redir(w http.ResponseWriter, req *http.Request) { http.StatusSeeOther) } -func writePidFile() { - var err error - - pidFilePath := filepath.Join(os.Getenv("SNAP_DATA"), "snapweb.pid") - - if f, err := os.OpenFile(pidFilePath, os.O_CREATE|os.O_RDWR, os.ModeTemporary|0640); err == nil { - fmt.Fprintf(f, "%d\n", os.Getpid()) - } - if err != nil { - log.Println(err) - } - -} - -func waitForSigHup() { - var waiter sync.WaitGroup - waiter.Add(1) - var sigchan chan os.Signal - sigchan = make(chan os.Signal, 1) - signal.Notify(sigchan, syscall.SIGHUP) - go func() { - <-sigchan - waiter.Done() - }() - waiter.Wait() -} - func main() { config := readConfig() for ! IsDeviceManaged() { logger.Println("Snapweb cannot run until the device is managed...") - writePidFile() - waitForSigHup() + snappy.WritePidFile() + snappy.WaitForSigHup() // wait futher more, to let webconf release the 4200 port time.Sleep(1000) } diff --git a/cmd/webconf/main.go b/cmd/webconf/main.go index 645631c9..3d6c894f 100644 --- a/cmd/webconf/main.go +++ b/cmd/webconf/main.go @@ -35,7 +35,6 @@ import ( ) var logger *log.Logger -var server net.Listener const ( httpAddr string = ":4200" @@ -100,17 +99,19 @@ func sendSignalToSnapweb() { } } -func doneHandler() http.HandlerFunc { +func doneHandler(server net.Listener) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { sendSignalToSnapweb() - server.Close() + if server != nil { + server.Close() + } } } -func initURLHandlers(log *log.Logger) { +func initURLHandlers(log *log.Logger, server net.Listener) { // API http.Handle("/api/", snappy.MakePassthroughHandler(dirs.SnapdSocket, "/api/")) - http.HandleFunc("/done", doneHandler()) + http.HandleFunc("/done", doneHandler(server)) // Resources http.Handle("/public/", snappy.LoggingHandler(http.FileServer(http.Dir(filepath.Join(os.Getenv("SNAP"), "www"))))) @@ -119,22 +120,21 @@ func initURLHandlers(log *log.Logger) { } func main() { - var err error - if snappy.IsDeviceManaged() { log.Println("webconf does not run on managed devices") os.Exit(0) } - initURLHandlers(logger) - go avahi.InitMDNS(logger) // open a plain HTTP end-point on the "usual" 4200 port - if server, err = net.Listen("tcp", httpAddr); err != nil { + server, err := net.Listen("tcp", httpAddr) + if err != nil { logger.Fatalf("%v", err) } + initURLHandlers(logger, server) + http.Serve(server, nil) // prepare to exit, but let snapweb start before that diff --git a/cmd/webconf/main_test.go b/cmd/webconf/main_test.go new file mode 100644 index 00000000..e35dbebf --- /dev/null +++ b/cmd/webconf/main_test.go @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package main + +import ( + "log" + "net" + "net/http" + "net/http/httptest" + "os" + "os/signal" + "path/filepath" + "strings" + "testing" + "syscall" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapweb/snappy" +) + +func Test(t *testing.T) { TestingT(t) } + +type WebconfSuite struct {} + +var _ = Suite(&WebconfSuite{}) + +func (s *WebconfSuite) SetUpTest(c *C) { + cwd, err := os.Getwd() + c.Assert(err, IsNil) + os.Setenv("SNAP", filepath.Join(cwd, "..", "..")) +} + +func (s *WebconfSuite) TestURLHandlers(c *C) { + initURLHandlers(log.New(os.Stdout, "", 0), nil) + defer func() { + http.DefaultServeMux = http.NewServeMux() + }() + + rec := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/", nil) + c.Assert(err, IsNil) + + http.DefaultServeMux.ServeHTTP(rec, req) + c.Assert(rec.Code, Equals, http.StatusOK) + + body := rec.Body.String() + c.Check(strings.Contains(body, "'Ubuntu'"), Equals, true) +} + +func (s *WebconfSuite) TestDoneHandler(c *C) { + tmp := c.MkDir() + os.Setenv("SNAP_DATA", tmp) + snappy.WritePidFile() + + done := false + var sigchan chan os.Signal + sigchan = make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGHUP) + + server, _ := net.Listen("tcp", httpAddr) + handler := doneHandler(server) + + req, _ := http.NewRequest("GET", "/done", nil) + rec := httptest.NewRecorder() + + handler.ServeHTTP(rec, req) + + select { + case <-sigchan: + done = true + } + + c.Assert(done, Equals, true) +} + diff --git a/snappy/app/fake_snapd_client.go b/snappy/app/fake_snapd_client.go index 731246dc..23262a50 100644 --- a/snappy/app/fake_snapd_client.go +++ b/snappy/app/fake_snapd_client.go @@ -161,7 +161,7 @@ func (f *FakeSnapdClient) Abort(id string) (*client.Change, error) { func (f *FakeSnapdClient) SysInfo() (*client.SysInfo, error) { managed := &client.SysInfo{ OnClassic: true, - Managed: true, + Managed: true, } return managed, nil } diff --git a/snappy/helpers.go b/snappy/helpers.go index 84fc4703..970d851b 100644 --- a/snappy/helpers.go +++ b/snappy/helpers.go @@ -22,12 +22,17 @@ package snappy import ( + "fmt" "io" "log" "net" "net/http" "os" + "os/signal" + "path/filepath" "strings" + "sync" + "syscall" "github.com/snapcore/snapd/client" ) @@ -102,3 +107,31 @@ func LoggingHandler(h http.Handler) http.Handler { h.ServeHTTP(w, r) }) } + +func WritePidFile() { + var err error + + pidFilePath := filepath.Join(os.Getenv("SNAP_DATA"), "snapweb.pid") + + if f, err := os.OpenFile(pidFilePath, os.O_CREATE|os.O_RDWR, os.ModeTemporary|0640); err == nil { + fmt.Fprintf(f, "%d\n", os.Getpid()) + } + if err != nil { + log.Println(err) + } + +} + +func WaitForSigHup() { + var waiter sync.WaitGroup + waiter.Add(1) + var sigchan chan os.Signal + sigchan = make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGHUP) + go func() { + <-sigchan + waiter.Done() + }() + waiter.Wait() +} + diff --git a/snappy/helpers_test.go b/snappy/helpers_test.go index f9428079..6bdaf808 100644 --- a/snappy/helpers_test.go +++ b/snappy/helpers_test.go @@ -113,3 +113,9 @@ func (s *HandlersSuite) TestPassthroughHandler(c *C) { c.Check(strings.Contains(body, "42"), Equals, true) // TODO: check that we receive Content-Type: json/application } + +func (s *HandlersSuite) TestWritePidFile(c *C) { +} + +func (s *HandlersSuite) TestWaitForSigHup(c *C) { +} From 31820d5d86113d73520d930cda9bdd1ad3338e71 Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 16 Feb 2017 17:42:25 +0100 Subject: [PATCH 33/55] gofmt --- cmd/webconf/main_test.go | 7 +++---- snappy/app/fake_snapd_client.go | 2 +- snappy/helpers.go | 5 ++++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cmd/webconf/main_test.go b/cmd/webconf/main_test.go index e35dbebf..f48902ba 100644 --- a/cmd/webconf/main_test.go +++ b/cmd/webconf/main_test.go @@ -26,8 +26,8 @@ import ( "os/signal" "path/filepath" "strings" - "testing" "syscall" + "testing" . "gopkg.in/check.v1" @@ -36,7 +36,7 @@ import ( func Test(t *testing.T) { TestingT(t) } -type WebconfSuite struct {} +type WebconfSuite struct{} var _ = Suite(&WebconfSuite{}) @@ -73,7 +73,7 @@ func (s *WebconfSuite) TestDoneHandler(c *C) { sigchan = make(chan os.Signal, 1) signal.Notify(sigchan, syscall.SIGHUP) - server, _ := net.Listen("tcp", httpAddr) + server, _ := net.Listen("tcp", httpAddr) handler := doneHandler(server) req, _ := http.NewRequest("GET", "/done", nil) @@ -88,4 +88,3 @@ func (s *WebconfSuite) TestDoneHandler(c *C) { c.Assert(done, Equals, true) } - diff --git a/snappy/app/fake_snapd_client.go b/snappy/app/fake_snapd_client.go index 23262a50..edc6d3ad 100644 --- a/snappy/app/fake_snapd_client.go +++ b/snappy/app/fake_snapd_client.go @@ -157,7 +157,7 @@ func (f *FakeSnapdClient) Abort(id string) (*client.Change, error) { f.AbortedChangeID = id return nil, nil -// Query system information +// SysInfo returns system information func (f *FakeSnapdClient) SysInfo() (*client.SysInfo, error) { managed := &client.SysInfo{ OnClassic: true, diff --git a/snappy/helpers.go b/snappy/helpers.go index 970d851b..955067fa 100644 --- a/snappy/helpers.go +++ b/snappy/helpers.go @@ -64,6 +64,7 @@ func unixDialer(socketPath string) func(string, string) (net.Conn, error) { } } +// MakePassthroughHandler maps a snapd API to a snapweb HTTP handler func MakePassthroughHandler(socketPath string, prefix string) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c := &http.Client{ @@ -101,6 +102,7 @@ func MakePassthroughHandler(socketPath string, prefix string) http.HandlerFunc { }) } +// LoggingHandler adds HTTP logs to a handler func LoggingHandler(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.URL.Path) @@ -108,6 +110,7 @@ func LoggingHandler(h http.Handler) http.Handler { }) } +// WritePidFile writes the PID of the current process to snapweb.pid func WritePidFile() { var err error @@ -122,6 +125,7 @@ func WritePidFile() { } +// WaitForSigHup waits for the reception of the SIGHUP signal func WaitForSigHup() { var waiter sync.WaitGroup waiter.Add(1) @@ -134,4 +138,3 @@ func WaitForSigHup() { }() waiter.Wait() } - From 3c38c44383bf9292321a0a0b2da5a85bf4767aac Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 16 Feb 2017 17:53:52 +0100 Subject: [PATCH 34/55] init.js has been renamed --- www/tests/webconfSpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/tests/webconfSpec.js b/www/tests/webconfSpec.js index eed45c3a..8239671f 100644 --- a/www/tests/webconfSpec.js +++ b/www/tests/webconfSpec.js @@ -1,7 +1,7 @@ var _ = require('lodash'); var CreateUser = require('../src/js/models/create-user.js'); var Backbone = require('backbone'); -var InitView = require('../src/js/views/init.js'); +var InitView = require('../src/js/views/webconf.js'); var CONF = require('../src/js/config.js'); describe('FirstBoot', function() { From 58b06b1098f753a9820d521b8b6331b17d101302 Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 16 Feb 2017 18:32:52 +0100 Subject: [PATCH 35/55] update js unit test suite --- www/src/js/models/create-user.js | 1 - www/tests/webconfSpec.js | 20 +++++++------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/www/src/js/models/create-user.js b/www/src/js/models/create-user.js index f545f55b..358faa87 100644 --- a/www/src/js/models/create-user.js +++ b/www/src/js/models/create-user.js @@ -2,7 +2,6 @@ var Backbone = require('backbone'); var Marionette = require('backbone.marionette'); -var CONFIG = require('../config.js'); module.exports = Backbone.Model.extend({ url: '/api/v2/create-user', diff --git a/www/tests/webconfSpec.js b/www/tests/webconfSpec.js index 8239671f..bb653b8f 100644 --- a/www/tests/webconfSpec.js +++ b/www/tests/webconfSpec.js @@ -1,33 +1,27 @@ var _ = require('lodash'); var CreateUser = require('../src/js/models/create-user.js'); var Backbone = require('backbone'); -var InitView = require('../src/js/views/webconf.js'); -var CONF = require('../src/js/config.js'); +var MainView = require('../src/js/views/webconf.js'); -describe('FirstBoot', function() { +describe('WebConf', function() { describe('create-user model', function() { beforeEach(function() { - jasmine.Ajax.install(); this.model = new CreateUser({}); spyOn(this.model, 'save').and.callThrough(); spyOn(this.model, 'validate').and.callThrough(); }); afterEach(function() { - jasmine.Ajax.uninstall(); - delete this.model; }); it('should be an instance of Backbone.Model', function() { - expect(CreateUser).toBeDefined(); expect(this.model).toEqual(jasmine.any(Backbone.Model)); }); - it('should have urlRoot prop from config', function() { - var expectedPath = CONF.CREATE_USER; - expect(this.model.url).toBe(CONF.CREATE_USER); + it('should have urlRoot prop', function() { + expect(this.model.url).toBe('/api/v2/create-user'); }); it('should block empty or invalid email', function() { @@ -44,11 +38,11 @@ describe('FirstBoot', function() { }); }); - describe('InitView', function() { + describe('MainView', function() { beforeEach(function() { this.model = new CreateUser({}); - this.view = new InitView({ + this.view = new MainView({ model: this.model }); this.view.render(); @@ -65,7 +59,7 @@ describe('FirstBoot', function() { }); it('should be an instance of Backbone.View', function() { - expect(InitView).toBeDefined(); + expect(MainView).toBeDefined(); expect(this.view).toEqual(jasmine.any(Backbone.View)); }); From c6ccf0bd24512228838d14d80eb454c3ee351c31 Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 16 Feb 2017 18:34:57 +0100 Subject: [PATCH 36/55] fix spread test use of a variable --- run-spread-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-spread-tests.sh b/run-spread-tests.sh index 0f90edcb..981d8c3e 100755 --- a/run-spread-tests.sh +++ b/run-spread-tests.sh @@ -80,7 +80,7 @@ if [ ! -e $SPREAD_QEMU_PATH/$image_name2 ] || [ $force_new_image -eq 1 ] ; then echo "INFO: Creating new qemu test image(2) ..." (cd spread/image ; sudo ./create-image-embryonic.sh $channel) mkdir -p $SPREAD_QEMU_PATH - mv -f spread/image/ubuntu-core-16-embryonic.img $SPREAD_QEMU_PATH/$image_name2 + mv -f spread/image/$image_name2 $SPREAD_QEMU_PATH/$image_name2 fi # We currently only run spread tests but we could do other things From 90e5d7ccf4b356e96df4b46ed31bceda6964df52 Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 16 Feb 2017 18:35:39 +0100 Subject: [PATCH 37/55] fix copyright date --- cmd/snapweb/state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/snapweb/state.go b/cmd/snapweb/state.go index 78692801..4cadf80c 100644 --- a/cmd/snapweb/state.go +++ b/cmd/snapweb/state.go @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2016 Canonical Ltd + * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as From 9a5063b26d19e48f54cd72ba80f6f683dad53ebd Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 16 Feb 2017 18:36:37 +0100 Subject: [PATCH 38/55] fix copyright date --- spread/image/create-image-embryonic.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spread/image/create-image-embryonic.sh b/spread/image/create-image-embryonic.sh index ec764256..b68ec05a 100755 --- a/spread/image/create-image-embryonic.sh +++ b/spread/image/create-image-embryonic.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright (C) 2016 Canonical Ltd +# Copyright (C) 2017 Canonical Ltd # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3 as From 0234f2562d7cc422156939ee9bd88c7828f42517 Mon Sep 17 00:00:00 2001 From: David Barth Date: Fri, 17 Feb 2017 16:08:14 +0100 Subject: [PATCH 39/55] gofmt again --- cmd/snapweb/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/snapweb/main.go b/cmd/snapweb/main.go index 7451d6ce..9913bda2 100644 --- a/cmd/snapweb/main.go +++ b/cmd/snapweb/main.go @@ -54,7 +54,7 @@ func redir(w http.ResponseWriter, req *http.Request) { func main() { config := readConfig() - for ! IsDeviceManaged() { + for !IsDeviceManaged() { logger.Println("Snapweb cannot run until the device is managed...") snappy.WritePidFile() snappy.WaitForSigHup() From c0af22c69136d5b20e92929c1ec57c2b97156d21 Mon Sep 17 00:00:00 2001 From: David Barth Date: Mon, 20 Feb 2017 11:20:58 +0100 Subject: [PATCH 40/55] adjust kvm utils --- tests/kvm-exec.sh | 2 +- tests/remote-install-snap.sh | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/kvm-exec.sh b/tests/kvm-exec.sh index 4c111f90..635c4836 100755 --- a/tests/kvm-exec.sh +++ b/tests/kvm-exec.sh @@ -34,7 +34,7 @@ start_vm() { qemu-system-x86_64 \ -enable-kvm -snapshot \ -m 500 \ - -net nic -net user,hostfwd=tcp::$PORT_SSH-:22,hostfwd=tcp::4201-:4201 \ + -net nic -net user,hostfwd=tcp::$PORT_SSH-:22,hostfwd=tcp::4200-:4200,hostfwd=tcp::4201-:4201 \ -drive file=$IMAGE_BOOTABLE,format=raw \ -pidfile $FILE_PID \ -monitor unix:$FILE_MONITOR,server,nowait \ diff --git a/tests/remote-install-snap.sh b/tests/remote-install-snap.sh index 4a81da5b..8f132b7f 100755 --- a/tests/remote-install-snap.sh +++ b/tests/remote-install-snap.sh @@ -11,7 +11,9 @@ fi snap_name="${snap##*/}" -SSH_OPTS="-o StrictHostKeyChecking=no" +# ssh-keygen -f "~/.ssh/known_hosts" -R [localhost]:8022 + +SSH_OPTS="-o StrictHostKeyChecking=no -o PreferredAuthentications=\"password\"" SSH_OPTS="$SSH_OPTS -p $port $user@$host" ssh ${SSH_OPTS} "if [ -d tmpsnaps ]; then rm -rf tmpsnaps; fi; mkdir tmpsnaps;" From 11f528c4c131a72977e9edff3dcec7362f37c9fb Mon Sep 17 00:00:00 2001 From: David Barth Date: Tue, 21 Feb 2017 19:19:07 +0100 Subject: [PATCH 41/55] improve spread tests --- spread/webconf/transition-to-snapweb/task.yaml | 7 +++---- spread/webconf/webconf-runs/task.yaml | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/spread/webconf/transition-to-snapweb/task.yaml b/spread/webconf/transition-to-snapweb/task.yaml index 7fbb2c19..a177ccdb 100644 --- a/spread/webconf/transition-to-snapweb/task.yaml +++ b/spread/webconf/transition-to-snapweb/task.yaml @@ -10,9 +10,8 @@ execute: | # wait a bit sleep 2 - echo "Verifying that snapweb now serves on port 4200" + echo "Verifying that snapweb now serves on port 4201" printf "GET / HTTP/1.1\nHost:localhost\nUser-Agent:nc\n\n" | nc localhost 4201 - # echo "The system should be managed now" - # test `snap managed` = 'false' - \ No newline at end of file + echo "The system should be managed now" + test `snap managed` = 'true' diff --git a/spread/webconf/webconf-runs/task.yaml b/spread/webconf/webconf-runs/task.yaml index 53a54cdb..713a343e 100644 --- a/spread/webconf/webconf-runs/task.yaml +++ b/spread/webconf/webconf-runs/task.yaml @@ -4,8 +4,8 @@ execute: | test `snap managed` = 'false' echo "Verifying that webconf is running" - test `ps ax | grep -- -linux-gnu/webconf | grep -v grep | wc -l` -eq 1 + test `ps h -C webconf` -eq 1 echo "Verifying that webconf is controlling port 4200" printf "GET / HTTP/1.1\nHost:localhost\nUser-Agent:nc\n\n" | nc localhost 4200 | grep "script src" | grep webconf.js - \ No newline at end of file + From ddcdcee07f38eba9a448236f0ae2a9bece7b1efc Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 23 Feb 2017 11:36:27 +0100 Subject: [PATCH 42/55] only expose the create-user API --- cmd/webconf/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/webconf/main.go b/cmd/webconf/main.go index 3d6c894f..d51920e7 100644 --- a/cmd/webconf/main.go +++ b/cmd/webconf/main.go @@ -110,7 +110,8 @@ func doneHandler(server net.Listener) http.HandlerFunc { func initURLHandlers(log *log.Logger, server net.Listener) { // API - http.Handle("/api/", snappy.MakePassthroughHandler(dirs.SnapdSocket, "/api/")) + http.Handle("/api/v2/create-user", + snappy.MakePassthroughHandler(dirs.SnapdSocket, "/api/v2/create-user")) http.HandleFunc("/done", doneHandler(server)) // Resources From 47b236e2ace9db59cead0dc1e4daf69dfb7a1d30 Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 23 Feb 2017 16:49:20 +0100 Subject: [PATCH 43/55] rebase with master; improve function name for creating a cert if needed --- cmd/snapweb/cert.go | 4 ++-- cmd/snapweb/cert_test.go | 6 +++--- cmd/snapweb/main.go | 4 +--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/cmd/snapweb/cert.go b/cmd/snapweb/cert.go index a4ababb3..d52b9c34 100644 --- a/cmd/snapweb/cert.go +++ b/cmd/snapweb/cert.go @@ -32,8 +32,8 @@ import ( "time" ) -// DumpCertificate create the certificate & key files -func DumpCertificate() { +// CreateCertificateIfNeeded creates the certificate & key files, if they don't exist yet +func CreateCertificateIfNeeded() { certFilename := filepath.Join(os.Getenv("SNAP_DATA"), "cert.pem") keyFilename := filepath.Join(os.Getenv("SNAP_DATA"), "key.pem") diff --git a/cmd/snapweb/cert_test.go b/cmd/snapweb/cert_test.go index 56019501..6443c530 100644 --- a/cmd/snapweb/cert_test.go +++ b/cmd/snapweb/cert_test.go @@ -45,17 +45,17 @@ func (s *CertSuite) TearDownTest(c *C) { os.RemoveAll(s.snapdata) } -func (s *CertSuite) TestDumpCertificate(c *C) { +func (s *CertSuite) TestCreateCertificateIfNeeded(c *C) { c.Assert(ioutil.WriteFile(s.certFilename, nil, 0600), IsNil) c.Assert(ioutil.WriteFile(s.keyFilename, nil, 0600), IsNil) - DumpCertificate() + CreateCertificateIfNeeded() certData, err := ioutil.ReadFile(s.certFilename) c.Assert(err, IsNil) keyData, err := ioutil.ReadFile(s.keyFilename) c.Assert(err, IsNil) - DumpCertificate() + CreateCertificateIfNeeded() certData2, err := ioutil.ReadFile(s.certFilename) c.Assert(err, IsNil) keyData2, err := ioutil.ReadFile(s.keyFilename) diff --git a/cmd/snapweb/main.go b/cmd/snapweb/main.go index 9913bda2..42b86d50 100644 --- a/cmd/snapweb/main.go +++ b/cmd/snapweb/main.go @@ -62,8 +62,6 @@ func main() { time.Sleep(1000) } - GenerateCertificate() - // TODO set warning for too hazardous config? config, err := snappy.ReadConfig() if err != nil { @@ -78,7 +76,7 @@ func main() { logger.Println("Snapweb starting...") if !config.DisableHTTPS { - DumpCertificate() + CreateCertificateIfNeeded() go func() { certFile := filepath.Join(os.Getenv("SNAP_DATA"), "cert.pem") From f8e7d65e7a62a1859ab5e6f9882568a0be4083fa Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 2 Mar 2017 21:34:18 +0100 Subject: [PATCH 44/55] improve unit tests; address review remarks --- cmd/snapweb/main.go | 14 ++++++--- cmd/snapweb/main_test.go | 68 ++++++++++++++++++++++++++++++++++++++++ cmd/snapweb/state.go | 38 ---------------------- cmd/webconf/main.go | 19 +---------- snappy/helpers.go | 27 +++++++++++----- snappy/helpers_test.go | 40 ++++++++++++++++++----- 6 files changed, 129 insertions(+), 77 deletions(-) create mode 100644 cmd/snapweb/main_test.go delete mode 100644 cmd/snapweb/state.go diff --git a/cmd/snapweb/main.go b/cmd/snapweb/main.go index 42b86d50..f4bfc993 100644 --- a/cmd/snapweb/main.go +++ b/cmd/snapweb/main.go @@ -51,16 +51,20 @@ func redir(w http.ResponseWriter, req *http.Request) { http.StatusSeeOther) } -func main() { - config := readConfig() +type blockerFn func() bool - for !IsDeviceManaged() { - logger.Println("Snapweb cannot run until the device is managed...") - snappy.WritePidFile() +func blockOn(managedCondition blockerFn) { + snappy.WritePidFile() + for managedCondition() == false { snappy.WaitForSigHup() // wait futher more, to let webconf release the 4200 port time.Sleep(1000) } +} + + +func main() { + blockOn(snappy.IsDeviceManaged) // TODO set warning for too hazardous config? config, err := snappy.ReadConfig() diff --git a/cmd/snapweb/main_test.go b/cmd/snapweb/main_test.go new file mode 100644 index 00000000..588c8644 --- /dev/null +++ b/cmd/snapweb/main_test.go @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package main + +import ( + "os" + "time" + + "github.com/snapcore/snapweb/snappy" + + . "gopkg.in/check.v1" +) + +type MainSuite struct{} + +var _ = Suite(&MainSuite{}) + +var mockManagedState = false + +func mockIsDeviceManaged() bool { + return mockManagedState +} + +func (s *MainSuite) TestBlockUntilManaged(c *C) { + os.Setenv("SNAP_DATA", c.MkDir()) + + ready := make(chan bool) + done := make(chan bool) + + go func() { + ready <- true + blockOn(mockIsDeviceManaged) + done <- true + }() + + // a signal cannot unblock snapweb until the system is managed + mockManagedState = false + <-ready + time.Sleep(1000) // give time to install the signal handler + snappy.SendSignalToSnapweb() + select { + case <-done: + c.Fail() + default: + } + + // try to unblock once the system has changed + mockManagedState = true + snappy.SendSignalToSnapweb() + result := <-done + + c.Assert(result, Equals, true) +} diff --git a/cmd/snapweb/state.go b/cmd/snapweb/state.go deleted file mode 100644 index 4cadf80c..00000000 --- a/cmd/snapweb/state.go +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2017 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package main - -import ( - "github.com/snapcore/snapd/client" -) - -// IsDeviceManaged determines if the device is in the 'managed' state -func IsDeviceManaged() bool { - client := client.New(nil) - - sysInfo, err := client.SysInfo() - if err != nil { - panic(err) - } - - if sysInfo.OnClassic || sysInfo.Managed { - return true - } - - return false -} diff --git a/cmd/webconf/main.go b/cmd/webconf/main.go index d51920e7..54d4888b 100644 --- a/cmd/webconf/main.go +++ b/cmd/webconf/main.go @@ -82,26 +82,9 @@ func makeMainPageHandler() http.HandlerFunc { } } -func sendSignalToSnapweb() { - var pid int - var err error - - pidFilePath := filepath.Join(os.Getenv("SNAP_DATA"), "snapweb.pid") - - if f, err := os.Open(pidFilePath); err == nil { - if _, err = fmt.Fscanf(f, "%d\n", &pid); err == nil { - p, _ := os.FindProcess(pid) - err = p.Signal(syscall.Signal(syscall.SIGHUP)) - } - } - if err != nil { - log.Println(err) - } -} - func doneHandler(server net.Listener) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - sendSignalToSnapweb() + snappy.SendSignalToSnapweb() if server != nil { server.Close() } diff --git a/snappy/helpers.go b/snappy/helpers.go index 955067fa..9db37993 100644 --- a/snappy/helpers.go +++ b/snappy/helpers.go @@ -31,7 +31,6 @@ import ( "os/signal" "path/filepath" "strings" - "sync" "syscall" "github.com/snapcore/snapd/client" @@ -127,14 +126,26 @@ func WritePidFile() { // WaitForSigHup waits for the reception of the SIGHUP signal func WaitForSigHup() { - var waiter sync.WaitGroup - waiter.Add(1) var sigchan chan os.Signal sigchan = make(chan os.Signal, 1) signal.Notify(sigchan, syscall.SIGHUP) - go func() { - <-sigchan - waiter.Done() - }() - waiter.Wait() + defer signal.Stop(sigchan) + <-sigchan +} + +func SendSignalToSnapweb() { + var pid int + + pidFilePath := filepath.Join(os.Getenv("SNAP_DATA"), "snapweb.pid") + + if f, err := os.Open(pidFilePath); err == nil { + if _, err = fmt.Fscanf(f, "%d\n", &pid); err == nil { + p, _ := os.FindProcess(pid) + err = p.Signal(syscall.Signal(syscall.SIGHUP)) + } else { + log.Println(err) + } + } else { + log.Println(err) + } } diff --git a/snappy/helpers_test.go b/snappy/helpers_test.go index 6bdaf808..81657d99 100644 --- a/snappy/helpers_test.go +++ b/snappy/helpers_test.go @@ -28,7 +28,8 @@ import ( "path/filepath" "strings" "testing" - + "time" + . "gopkg.in/check.v1" "github.com/snapcore/snapweb/snappy/app" @@ -54,10 +55,6 @@ func (s *HandlersSuite) SetUpTest(c *C) { func (s *HandlersSuite) TearDownTest(c *C) { } -func (s *HandlersSuite) TestIsDeviceManaged(c *C) { - c.Assert(IsDeviceManaged(), Equals, true) -} - func (s *HandlersSuite) TestLoggingHandler(c *C) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) logged := LoggingHandler(handler) @@ -114,8 +111,35 @@ func (s *HandlersSuite) TestPassthroughHandler(c *C) { // TODO: check that we receive Content-Type: json/application } -func (s *HandlersSuite) TestWritePidFile(c *C) { -} +func (s *HandlersSuite) TestSnapwebSignaling(c *C) { + os.Setenv("SNAP_DATA", c.MkDir()) + + WritePidFile() + + ready := make(chan bool) + done := make(chan bool) + + // the thread where we test the function + go func() { + for ; ; { + ready <- true + WaitForSigHup() + // say the test passed + done <- true + } + }() + + // send the signal, *once* the function is ready to be tested + <-ready + time.Sleep(1000) + SendSignalToSnapweb() + + c.Assert(<-done, Equals, true) + + // do it a second time + <-ready + time.Sleep(1000) + SendSignalToSnapweb() -func (s *HandlersSuite) TestWaitForSigHup(c *C) { + c.Assert(<-done, Equals, true) } From 1e22c95bed63e3717b9175f640356e18fe64d636 Mon Sep 17 00:00:00 2001 From: David Barth Date: Mon, 6 Mar 2017 17:43:43 +0100 Subject: [PATCH 45/55] gofmt/lint et al --- cmd/snapweb/main.go | 3 ++- cmd/snapweb/main_test.go | 6 +++--- cmd/webconf/main.go | 2 -- snappy/helpers.go | 1 + snappy/helpers_test.go | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/snapweb/main.go b/cmd/snapweb/main.go index f4bfc993..2c659766 100644 --- a/cmd/snapweb/main.go +++ b/cmd/snapweb/main.go @@ -62,10 +62,11 @@ func blockOn(managedCondition blockerFn) { } } - func main() { blockOn(snappy.IsDeviceManaged) + config := readConfig() + // TODO set warning for too hazardous config? config, err := snappy.ReadConfig() if err != nil { diff --git a/cmd/snapweb/main_test.go b/cmd/snapweb/main_test.go index 588c8644..5371faca 100644 --- a/cmd/snapweb/main_test.go +++ b/cmd/snapweb/main_test.go @@ -41,7 +41,7 @@ func (s *MainSuite) TestBlockUntilManaged(c *C) { ready := make(chan bool) done := make(chan bool) - + go func() { ready <- true blockOn(mockIsDeviceManaged) @@ -58,11 +58,11 @@ func (s *MainSuite) TestBlockUntilManaged(c *C) { c.Fail() default: } - + // try to unblock once the system has changed mockManagedState = true snappy.SendSignalToSnapweb() result := <-done - + c.Assert(result, Equals, true) } diff --git a/cmd/webconf/main.go b/cmd/webconf/main.go index 54d4888b..f3b5f71d 100644 --- a/cmd/webconf/main.go +++ b/cmd/webconf/main.go @@ -18,13 +18,11 @@ package main import ( - "fmt" "log" "net" "net/http" "os" "path/filepath" - "syscall" "text/template" "time" diff --git a/snappy/helpers.go b/snappy/helpers.go index 9db37993..741c2e70 100644 --- a/snappy/helpers.go +++ b/snappy/helpers.go @@ -133,6 +133,7 @@ func WaitForSigHup() { <-sigchan } +// SendSignalToSnapweb informs snapweb that the webconf process is finished, via a SIGHUP signal func SendSignalToSnapweb() { var pid int diff --git a/snappy/helpers_test.go b/snappy/helpers_test.go index 81657d99..6eaca98b 100644 --- a/snappy/helpers_test.go +++ b/snappy/helpers_test.go @@ -29,7 +29,7 @@ import ( "strings" "testing" "time" - + . "gopkg.in/check.v1" "github.com/snapcore/snapweb/snappy/app" @@ -115,13 +115,13 @@ func (s *HandlersSuite) TestSnapwebSignaling(c *C) { os.Setenv("SNAP_DATA", c.MkDir()) WritePidFile() - + ready := make(chan bool) done := make(chan bool) // the thread where we test the function go func() { - for ; ; { + for { ready <- true WaitForSigHup() // say the test passed From 635e5b58f051b23aa364deff9af82fa96a6d5b70 Mon Sep 17 00:00:00 2001 From: David Barth Date: Fri, 10 Mar 2017 16:16:07 +0100 Subject: [PATCH 46/55] gulp and build.sh support for optional webconf build option --- build.sh | 35 ++++++++++++++++++++++++++++++----- gulpfile.js | 5 ++++- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/build.sh b/build.sh index b305c545..afb01940 100755 --- a/build.sh +++ b/build.sh @@ -7,13 +7,36 @@ # amd64, arm64, armhf and i386. # # [--ups]: Build the ubuntu-personal-store snap as well. +# +# [--with-webconf]: Adds the new webconf service to the snapweb snap. +# set -e -if [ "$#" -eq 0 ] || ([ "$#" -eq 1 ] && [ "$1" = "--ups" ]); then +BUILD_UPS_SNAP= +WITH_WEBCONF= + +while [ "$1" != "" ]; do + case $1 in + --ups) + BUILD_UPS_SNAP=1 + ;; + --with-webconf) + WITH_WEBCONF=1 + ;; + amd64|i386|arm64|armhf) + architectures+=("$1") + ;; + *) + # print usage + head -13 $0 | tail -12 + exit + esac + shift +done + +if [ "$architectures" = "" ]; then architectures=( amd64 arm64 armhf i386 ) -else - architectures=( "$@" ) fi AVAHI_VERSION="0.6.31-4ubuntu4snap2" @@ -60,7 +83,9 @@ gobuild() { mkdir -p $output_dir cd $output_dir GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -ldflags "-extld=${plat_abi}-gcc" github.com/snapcore/snapweb/cmd/snapweb - GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -ldflags "-extld=${plat_abi}-gcc" github.com/snapcore/snapweb/cmd/webconf + if [ $WITH_WEBCONF ]; then + GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -ldflags "-extld=${plat_abi}-gcc" github.com/snapcore/snapweb/cmd/webconf + fi GOARCH=$arch GOARM=7 CGO_ENABLED=1 CC=${plat_abi}-gcc go build -o generate-token -ldflags "-extld=${plat_abi}-gcc" $srcdir/cmd/generate-token/main.go cp generate-token ../../ cd - > /dev/null @@ -113,7 +138,7 @@ for ARCH in "${architectures[@]}"; do snapcraft snap $builddir # TODO: We only support amd64 ups builds for now -Need a cross-compile fix for "after: [desktop-qt5]" - if [[ $* == *--ups* ]] && [ $ARCH = amd64 ]; then + if [ $BUILD_UPS_SNAP ] && [ $ARCH = amd64 ]; then HTTP_ADDR="127.0.0.1:5200" HTTPS_ADDR="127.0.0.1:5201" diff --git a/gulpfile.js b/gulpfile.js index 4af49fd3..09a02216 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -141,4 +141,7 @@ gulp.task('install', ['default'], function() { .pipe(gulp.dest('../install')); }); -gulp.task('default', ['js:build', 'js:build:webconf', 'styles', 'images']); +gulp.task('default', + ['js:build', + process.env.WITH_WEBCONF === '1' ? 'js:build:webconf' : 'js:clean:webconf', + 'styles', 'images']); From 0ae6319ff42fd80f99184b3a53d0bc6580efa387 Mon Sep 17 00:00:00 2001 From: David Barth Date: Wed, 22 Mar 2017 11:26:39 +0100 Subject: [PATCH 47/55] adjust packages and tests after master rebase --- cmd/snapweb/main.go | 2 -- cmd/snapweb/main_test.go | 2 +- cmd/webconf/main.go | 2 +- cmd/webconf/main_test.go | 2 +- snappy/app/fake_snapd_client.go | 1 + snappy/{ => app}/helpers.go | 0 snappy/{ => app}/helpers_test.go | 29 +++++------------------------ 7 files changed, 9 insertions(+), 29 deletions(-) rename snappy/{ => app}/helpers.go (100%) rename snappy/{ => app}/helpers_test.go (83%) diff --git a/cmd/snapweb/main.go b/cmd/snapweb/main.go index 2c659766..0649a1af 100644 --- a/cmd/snapweb/main.go +++ b/cmd/snapweb/main.go @@ -65,8 +65,6 @@ func blockOn(managedCondition blockerFn) { func main() { blockOn(snappy.IsDeviceManaged) - config := readConfig() - // TODO set warning for too hazardous config? config, err := snappy.ReadConfig() if err != nil { diff --git a/cmd/snapweb/main_test.go b/cmd/snapweb/main_test.go index 5371faca..eae6f794 100644 --- a/cmd/snapweb/main_test.go +++ b/cmd/snapweb/main_test.go @@ -21,7 +21,7 @@ import ( "os" "time" - "github.com/snapcore/snapweb/snappy" + "github.com/snapcore/snapweb/snappy/app" . "gopkg.in/check.v1" ) diff --git a/cmd/webconf/main.go b/cmd/webconf/main.go index f3b5f71d..dca4c104 100644 --- a/cmd/webconf/main.go +++ b/cmd/webconf/main.go @@ -29,7 +29,7 @@ import ( "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapweb/avahi" - "github.com/snapcore/snapweb/snappy" + "github.com/snapcore/snapweb/snappy/app" ) var logger *log.Logger diff --git a/cmd/webconf/main_test.go b/cmd/webconf/main_test.go index f48902ba..447518e2 100644 --- a/cmd/webconf/main_test.go +++ b/cmd/webconf/main_test.go @@ -31,7 +31,7 @@ import ( . "gopkg.in/check.v1" - "github.com/snapcore/snapweb/snappy" + "github.com/snapcore/snapweb/snappy/app" ) func Test(t *testing.T) { TestingT(t) } diff --git a/snappy/app/fake_snapd_client.go b/snappy/app/fake_snapd_client.go index edc6d3ad..95744d7c 100644 --- a/snappy/app/fake_snapd_client.go +++ b/snappy/app/fake_snapd_client.go @@ -156,6 +156,7 @@ func (f *FakeSnapdClient) Disable(name string, options *client.SnapOptions) (str func (f *FakeSnapdClient) Abort(id string) (*client.Change, error) { f.AbortedChangeID = id return nil, nil +} // SysInfo returns system information func (f *FakeSnapdClient) SysInfo() (*client.SysInfo, error) { diff --git a/snappy/helpers.go b/snappy/app/helpers.go similarity index 100% rename from snappy/helpers.go rename to snappy/app/helpers.go diff --git a/snappy/helpers_test.go b/snappy/app/helpers_test.go similarity index 83% rename from snappy/helpers_test.go rename to snappy/app/helpers_test.go index 6eaca98b..f20e61b8 100644 --- a/snappy/helpers_test.go +++ b/snappy/app/helpers_test.go @@ -27,35 +27,16 @@ import ( "os" "path/filepath" "strings" - "testing" "time" . "gopkg.in/check.v1" - - "github.com/snapcore/snapweb/snappy/app" ) -func Test(t *testing.T) { TestingT(t) } - -type HandlersSuite struct { - c *snappy.FakeSnapdClient -} - -var _ = Suite(&HandlersSuite{}) - -func (s *HandlersSuite) SetUpTest(c *C) { - s.c = &snappy.FakeSnapdClient{} +type HelpersSuite struct {} - s.c.Version.Version = "1000" - s.c.Version.Series = "16" - - s.c.Err = nil -} - -func (s *HandlersSuite) TearDownTest(c *C) { -} +var _ = Suite(&HelpersSuite{}) -func (s *HandlersSuite) TestLoggingHandler(c *C) { +func (s *HelpersSuite) TestLoggingHandler(c *C) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) logged := LoggingHandler(handler) @@ -74,7 +55,7 @@ func (s *HandlersSuite) TestLoggingHandler(c *C) { c.Assert(output.String(), Matches, ".*GET /foo\n") } -func (s *HandlersSuite) TestPassthroughHandler(c *C) { +func (s *HelpersSuite) TestPassthroughHandler(c *C) { socketPath := "/tmp/snapd-test.socket" c.Assert(os.MkdirAll(filepath.Dir(socketPath), 0755), IsNil) l, err := net.Listen("unix", socketPath) @@ -111,7 +92,7 @@ func (s *HandlersSuite) TestPassthroughHandler(c *C) { // TODO: check that we receive Content-Type: json/application } -func (s *HandlersSuite) TestSnapwebSignaling(c *C) { +func (s *HelpersSuite) TestSnapwebSignaling(c *C) { os.Setenv("SNAP_DATA", c.MkDir()) WritePidFile() From ea4e8dee930d66a57228607244b6626b3b8be1a7 Mon Sep 17 00:00:00 2001 From: David Barth Date: Wed, 22 Mar 2017 18:05:08 +0100 Subject: [PATCH 48/55] ensure systemd won't try to restart the webconf service --- cmd/webconf/main.go | 1 + pkg/meta/snap.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/cmd/webconf/main.go b/cmd/webconf/main.go index dca4c104..749e9908 100644 --- a/cmd/webconf/main.go +++ b/cmd/webconf/main.go @@ -104,6 +104,7 @@ func initURLHandlers(log *log.Logger, server net.Listener) { func main() { if snappy.IsDeviceManaged() { log.Println("webconf does not run on managed devices") + // this tells systemd to not restart this service os.Exit(0) } diff --git a/pkg/meta/snap.yaml b/pkg/meta/snap.yaml index 8fb49e88..1ec44236 100644 --- a/pkg/meta/snap.yaml +++ b/pkg/meta/snap.yaml @@ -18,6 +18,7 @@ apps: plugs: [network, network-bind, snapd-control, timeserver-control, timezone-control] webconf: daemon: simple + restart-condition: never command: webconf plugs: [network, network-bind, snapd-control, timeserver-control] generate-token: From 947266d8b60dee3cf2e2b85f7d5a44ffaa6db0f3 Mon Sep 17 00:00:00 2001 From: David Barth Date: Wed, 22 Mar 2017 19:01:07 +0100 Subject: [PATCH 49/55] adjust spread tests --- spread/main/{firstboot-not => webconf-not}/task.yaml | 8 ++++---- spread/webconf/transition-to-snapweb/task.yaml | 3 +++ spread/webconf/webconf-runs/task.yaml | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) rename spread/main/{firstboot-not => webconf-not}/task.yaml (71%) diff --git a/spread/main/firstboot-not/task.yaml b/spread/main/webconf-not/task.yaml similarity index 71% rename from spread/main/firstboot-not/task.yaml rename to spread/main/webconf-not/task.yaml index 272982c6..5fa8f33a 100644 --- a/spread/main/firstboot-not/task.yaml +++ b/spread/main/webconf-not/task.yaml @@ -12,11 +12,11 @@ execute: | echo "assertions from the $SEED_DIR/assertions subdirectory." systemctl start snapd.service - echo "The system should be managed by now" + echo "The system should be managed already" test `snap managed` - echo "Verifying that firstboot is NOT running" - test `ps axu | grep snapweb.firstboot | grep -v grep | wc -l` -eq 0 + echo "Verifying that webconf is NOT running" + test -z "`ps h -C webconf`" echo "Verifying that snapweb is running as normal" - test `ps axu | grep snapweb | grep -v grep | wc -l` -eq 1 + test -n "`ps h -C snapweb`" diff --git a/spread/webconf/transition-to-snapweb/task.yaml b/spread/webconf/transition-to-snapweb/task.yaml index a177ccdb..f65ecdec 100644 --- a/spread/webconf/transition-to-snapweb/task.yaml +++ b/spread/webconf/transition-to-snapweb/task.yaml @@ -15,3 +15,6 @@ execute: | echo "The system should be managed now" test `snap managed` = 'true' + + echo "And webconf does not run anymore" + test -z "`ps h -C webconf`" diff --git a/spread/webconf/webconf-runs/task.yaml b/spread/webconf/webconf-runs/task.yaml index 713a343e..8be1261c 100644 --- a/spread/webconf/webconf-runs/task.yaml +++ b/spread/webconf/webconf-runs/task.yaml @@ -4,7 +4,7 @@ execute: | test `snap managed` = 'false' echo "Verifying that webconf is running" - test `ps h -C webconf` -eq 1 + test -n "`ps h -C webconf`" echo "Verifying that webconf is controlling port 4200" printf "GET / HTTP/1.1\nHost:localhost\nUser-Agent:nc\n\n" | nc localhost 4200 | grep "script src" | grep webconf.js From 4cb36643bde52003d5d78b7866602f1bc7335738 Mon Sep 17 00:00:00 2001 From: David Barth Date: Wed, 22 Mar 2017 19:44:40 +0100 Subject: [PATCH 50/55] move the default filter handler to the shared snappy package --- cmd/snapweb/handlers.go | 30 ++---------------------------- snappy/app/netfilter.go | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/cmd/snapweb/handlers.go b/cmd/snapweb/handlers.go index 23c087c6..3ffe214b 100644 --- a/cmd/snapweb/handlers.go +++ b/cmd/snapweb/handlers.go @@ -250,7 +250,7 @@ func initURLHandlers(log *log.Logger, config snappy.Config) http.Handler { handler.HandleFunc("/", makeMainPageHandler()) - return NewFilterHandlerFromConfig(handler, config) + return snappy.NewFilterHandlerFromConfig(handler, config) } // Name of the cookie transporting the access token @@ -341,31 +341,5 @@ func redirHandler(config snappy.Config) http.Handler { http.StatusSeeOther) }) - return NewFilterHandlerFromConfig(redir, config) -} - -// NewFilterHandlerFromConfig creates a new http.Handler with an integrated NetFilter -func NewFilterHandlerFromConfig(handler http.Handler, config snappy.Config) http.Handler { - if config.DisableIPFilter { - return handler - } - - f := snappy.NewFilter() - - for _, net := range config.AllowNetworks { - f.AllowNetwork(net) - } - - for _, ifname := range config.AllowInterfaces { - f.AddLocalNetworkForInterface(ifname) - } - - // if nothing was specified, default to allowing all local networks - if (len(config.AllowNetworks) == 0) && - (len(config.AllowInterfaces) == 0) { - logger.Println("Allowing local network access by default") - f.AddLocalNetworks() - } - - return f.FilterHandler(handler) + return snappy.NewFilterHandlerFromConfig(redir, config) } diff --git a/snappy/app/netfilter.go b/snappy/app/netfilter.go index c551948f..e7f46290 100644 --- a/snappy/app/netfilter.go +++ b/snappy/app/netfilter.go @@ -132,3 +132,29 @@ func (f *NetFilter) FilterHandler(handler http.Handler) http.Handler { }) } + +// NewFilterHandlerFromConfig creates a new http.Handler with an integrated NetFilter +func NewFilterHandlerFromConfig(handler http.Handler, config Config) http.Handler { + if config.DisableIPFilter { + return handler + } + + f := NewFilter() + + for _, net := range config.AllowNetworks { + f.AllowNetwork(net) + } + + for _, ifname := range config.AllowInterfaces { + f.AddLocalNetworkForInterface(ifname) + } + + // if nothing was specified, default to allowing all local networks + if (len(config.AllowNetworks) == 0) && + (len(config.AllowInterfaces) == 0) { + log.Println("Allowing local network access by default") + f.AddLocalNetworks() + } + + return f.FilterHandler(handler) +} From 6b41e6ff73fcb9131a93999f1304a383933144d0 Mon Sep 17 00:00:00 2001 From: David Barth Date: Wed, 22 Mar 2017 19:58:37 +0100 Subject: [PATCH 51/55] add support for the new netfilter; accept local networks by default --- cmd/webconf/main.go | 23 ++++++++++++++++------- cmd/webconf/main_test.go | 6 ++++-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/cmd/webconf/main.go b/cmd/webconf/main.go index 749e9908..13459d99 100644 --- a/cmd/webconf/main.go +++ b/cmd/webconf/main.go @@ -89,16 +89,25 @@ func doneHandler(server net.Listener) http.HandlerFunc { } } -func initURLHandlers(log *log.Logger, server net.Listener) { +func initURLHandlers(log *log.Logger, server net.Listener) http.Handler { + handler := http.NewServeMux() + // API - http.Handle("/api/v2/create-user", + handler.Handle("/api/v2/create-user", snappy.MakePassthroughHandler(dirs.SnapdSocket, "/api/v2/create-user")) - http.HandleFunc("/done", doneHandler(server)) + handler.HandleFunc("/done", doneHandler(server)) // Resources - http.Handle("/public/", snappy.LoggingHandler(http.FileServer(http.Dir(filepath.Join(os.Getenv("SNAP"), "www"))))) + handler.Handle("/public/", snappy.LoggingHandler(http.FileServer(http.Dir(filepath.Join(os.Getenv("SNAP"), "www"))))) + + handler.HandleFunc("/", makeMainPageHandler()) - http.HandleFunc("/", makeMainPageHandler()) + config, err := snappy.ReadConfig() + if err != nil { + logger.Fatal("Configuration error", err) + } + + return snappy.NewFilterHandlerFromConfig(handler, config) } func main() { @@ -116,9 +125,9 @@ func main() { logger.Fatalf("%v", err) } - initURLHandlers(logger, server) + handler := initURLHandlers(logger, server) - http.Serve(server, nil) + http.Serve(server, handler) // prepare to exit, but let snapweb start before that time.Sleep(2 * time.Second) diff --git a/cmd/webconf/main_test.go b/cmd/webconf/main_test.go index 447518e2..2c9377f4 100644 --- a/cmd/webconf/main_test.go +++ b/cmd/webconf/main_test.go @@ -47,16 +47,17 @@ func (s *WebconfSuite) SetUpTest(c *C) { } func (s *WebconfSuite) TestURLHandlers(c *C) { - initURLHandlers(log.New(os.Stdout, "", 0), nil) + handler := initURLHandlers(log.New(os.Stdout, "", 0), nil) defer func() { http.DefaultServeMux = http.NewServeMux() }() rec := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) + req.RemoteAddr = "127.0.0.1:80" c.Assert(err, IsNil) - http.DefaultServeMux.ServeHTTP(rec, req) + handler.ServeHTTP(rec, req) c.Assert(rec.Code, Equals, http.StatusOK) body := rec.Body.String() @@ -77,6 +78,7 @@ func (s *WebconfSuite) TestDoneHandler(c *C) { handler := doneHandler(server) req, _ := http.NewRequest("GET", "/done", nil) + req.RemoteAddr = "127.0.0.1:80" rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) From f077af80bfbf99bf5c4b5832f3c7c64b5c680514 Mon Sep 17 00:00:00 2001 From: David Barth Date: Wed, 22 Mar 2017 19:59:42 +0100 Subject: [PATCH 52/55] gofmt --- cmd/webconf/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/webconf/main.go b/cmd/webconf/main.go index 13459d99..0a6094e6 100644 --- a/cmd/webconf/main.go +++ b/cmd/webconf/main.go @@ -106,7 +106,7 @@ func initURLHandlers(log *log.Logger, server net.Listener) http.Handler { if err != nil { logger.Fatal("Configuration error", err) } - + return snappy.NewFilterHandlerFromConfig(handler, config) } From a54570db9e6a82cd40c5b83c748cbe143221a363 Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 23 Mar 2017 17:30:07 +0100 Subject: [PATCH 53/55] verify that webconf is protected by the netfilter feature --- spread/webconf/netfilter/task.yaml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 spread/webconf/netfilter/task.yaml diff --git a/spread/webconf/netfilter/task.yaml b/spread/webconf/netfilter/task.yaml new file mode 100644 index 00000000..0a2e438e --- /dev/null +++ b/spread/webconf/netfilter/task.yaml @@ -0,0 +1,25 @@ +summary: Control that remote IP connections are not accepted +execute: | + echo "setting up test interface" + modprobe dummy + sleep 1 + + echo "add a network filter to the configuration" + ip link set name eth10 dev dummy0 + sleep 1 + ip address add 192.168.100.199/24 dev eth10 + ip link set eth10 up + + echo "Verifying that a remote request is not accepted" + echo "{ \"allowNetworks\": [\"127.0.0.1/24\"] }" > /var/snap/snapweb/common/settings.json + systemctl restart snap.snapweb.webconf.service + sleep 1 + RES=`printf "GET / HTTP/1.1\nHost:localhost\nUser-Agent:nc\n\n" | nc -s 192.168.100.199 127.0.0.1 4200 | grep "HTTP/1.1 403 Forbidden"` + test -n "$RES" + + echo "cleanup" + ip address del 192.168.100.199/24 dev eth10 + ip link set eth10 down + rm /var/snap/snapweb/common/settings.json + systemctl restart snap.snapweb.webconf.service + \ No newline at end of file From 9d69fe62fc832c62808e49f5ff8747c85932a625 Mon Sep 17 00:00:00 2001 From: David Barth Date: Thu, 23 Mar 2017 18:06:01 +0100 Subject: [PATCH 54/55] add spread test to verify if the netfilter feature protects snapweb --- spread/main/netfilter/task.yaml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 spread/main/netfilter/task.yaml diff --git a/spread/main/netfilter/task.yaml b/spread/main/netfilter/task.yaml new file mode 100644 index 00000000..4d78d9ae --- /dev/null +++ b/spread/main/netfilter/task.yaml @@ -0,0 +1,25 @@ +summary: Control that remote IP connections are not accepted +execute: | + echo "setting up test interface" + modprobe dummy + sleep 1 + + echo "add a network filter to the configuration" + ip link set name eth10 dev dummy0 + sleep 1 + ip address add 192.168.100.199/24 dev eth10 + ip link set eth10 up + + echo "Verifying that a remote request is not accepted" + echo "{ \"allowNetworks\": [\"127.0.0.1/24\"] }" > /var/snap/snapweb/common/settings.json + systemctl restart snap.snapweb.snapweb.service + sleep 1 + RES=`printf "GET / HTTP/1.1\nHost:localhost\nUser-Agent:nc\n\n" | nc -s 192.168.100.199 127.0.0.1 4200 | grep "HTTP/1.1 403 Forbidden"` + test -n "$RES" + + echo "cleanup" + ip address del 192.168.100.199/24 dev eth10 + ip link set eth10 down + rm /var/snap/snapweb/common/settings.json + systemctl restart snap.snapweb.snapweb.service + \ No newline at end of file From 6d7d2c54c6230fa5253830752022a0f09d84e68a Mon Sep 17 00:00:00 2001 From: David Barth Date: Fri, 24 Mar 2017 12:03:58 +0100 Subject: [PATCH 55/55] add a timer to de-activate webconf after 2 minutes --- cmd/webconf/main.go | 26 +++++++++++++++++++------- cmd/webconf/main_test.go | 7 +++---- snappy/app/configuration.go | 1 + snappy/app/helpers_test.go | 2 +- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/cmd/webconf/main.go b/cmd/webconf/main.go index 0a6094e6..1d9821df 100644 --- a/cmd/webconf/main.go +++ b/cmd/webconf/main.go @@ -33,6 +33,7 @@ import ( ) var logger *log.Logger +var timer *time.Timer const ( httpAddr string = ":4200" @@ -70,6 +71,9 @@ func makeMainPageHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + // reset the timer, and give 3 minutes to complete the setup process + timer.Reset(time.Second * 180) + err = t.Execute(w, &data) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -89,7 +93,7 @@ func doneHandler(server net.Listener) http.HandlerFunc { } } -func initURLHandlers(log *log.Logger, server net.Listener) http.Handler { +func initURLHandlers(log *log.Logger, server net.Listener, config snappy.Config) http.Handler { handler := http.NewServeMux() // API @@ -102,11 +106,6 @@ func initURLHandlers(log *log.Logger, server net.Listener) http.Handler { handler.HandleFunc("/", makeMainPageHandler()) - config, err := snappy.ReadConfig() - if err != nil { - logger.Fatal("Configuration error", err) - } - return snappy.NewFilterHandlerFromConfig(handler, config) } @@ -117,6 +116,11 @@ func main() { os.Exit(0) } + config, err := snappy.ReadConfig() + if err != nil { + logger.Fatal("Configuration error", err) + } + go avahi.InitMDNS(logger) // open a plain HTTP end-point on the "usual" 4200 port @@ -125,7 +129,15 @@ func main() { logger.Fatalf("%v", err) } - handler := initURLHandlers(logger, server) + handler := initURLHandlers(logger, server, config) + + timer = time.NewTimer(time.Second * 120) + go func() { + <-timer.C + logger.Println("disabling webconf automatically after 2 minutes") + server.Close() + os.Exit(0) + }() http.Serve(server, handler) diff --git a/cmd/webconf/main_test.go b/cmd/webconf/main_test.go index 2c9377f4..d07b9b4d 100644 --- a/cmd/webconf/main_test.go +++ b/cmd/webconf/main_test.go @@ -28,6 +28,7 @@ import ( "strings" "syscall" "testing" + "time" . "gopkg.in/check.v1" @@ -47,10 +48,8 @@ func (s *WebconfSuite) SetUpTest(c *C) { } func (s *WebconfSuite) TestURLHandlers(c *C) { - handler := initURLHandlers(log.New(os.Stdout, "", 0), nil) - defer func() { - http.DefaultServeMux = http.NewServeMux() - }() + timer = time.NewTimer(time.Second * 120) + handler := initURLHandlers(log.New(os.Stdout, "", 0), nil, snappy.Config{}) rec := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) diff --git a/snappy/app/configuration.go b/snappy/app/configuration.go index 0fd562b4..2a80725e 100644 --- a/snappy/app/configuration.go +++ b/snappy/app/configuration.go @@ -36,6 +36,7 @@ type Config struct { DisableIPFilter bool `json:"disableIPFilter,omitempty"` AllowNetworks []string `json:"allowNetworks,omitempty"` AllowInterfaces []string `json:"allowInterfaces,omitempty"` + WebconfTimeout []string `json:"webconfTimeout,omitempty"` } var readFile = ioutil.ReadFile diff --git a/snappy/app/helpers_test.go b/snappy/app/helpers_test.go index f20e61b8..b01cc2a2 100644 --- a/snappy/app/helpers_test.go +++ b/snappy/app/helpers_test.go @@ -32,7 +32,7 @@ import ( . "gopkg.in/check.v1" ) -type HelpersSuite struct {} +type HelpersSuite struct{} var _ = Suite(&HelpersSuite{})