From 357a58765333ff60c4216d9b7e8a066ba68213ff Mon Sep 17 00:00:00 2001 From: Sung Won Cho Date: Mon, 22 Jul 2019 13:41:09 +1000 Subject: [PATCH] Implement server binary (#223) --- .gitignore | 1 + CONTRIBUTING.md | 97 +++++++------ Gopkg.lock | 91 +++++++++++-- Gopkg.toml | 12 +- Makefile | 95 ++++++++++--- SELF_HOSTING.md | 101 ++++++++++++++ pkg/cli/Makefile | 12 -- pkg/cli/install.sh | 17 +-- pkg/cli/scripts/build.sh | 116 +++++++--------- pkg/cli/scripts/release.sh | 58 -------- pkg/server/{job => }/.env.dev | 2 +- pkg/server/README.md | 2 +- pkg/server/api/.gitignore | 1 + pkg/server/api/handlers/health.go | 1 + pkg/server/api/main.go | 103 -------------- pkg/server/api/scripts/build.sh | 7 + pkg/server/api/scripts/dev.sh | 13 -- pkg/server/database/{main.go => database.go} | 5 + pkg/server/database/migrate.go | 28 ++++ pkg/server/database/migrate.yml | 8 -- pkg/server/database/migrate/main.go | 2 + .../database/scripts/create-migration.sh | 2 +- pkg/server/database/sql-migrate.yml | 8 ++ pkg/server/job/.gitignore | 3 - pkg/server/job/{digest => }/digest.go | 12 +- pkg/server/job/{main.go => job.go} | 46 +------ pkg/server/job/scripts/dev.sh | 1 - pkg/server/mailer/mailer.go | 38 ++++-- pkg/server/main.go | 127 ++++++++++++++++++ pkg/server/scripts/build.sh | 60 +++++++++ scripts/release.sh | 54 ++++++++ web/.env.dev | 1 - web/main.go | 72 ---------- web/package-lock.json | 41 ++---- web/scripts/build-prod.sh | 9 +- web/scripts/build.sh | 14 +- web/scripts/dev.sh | 23 +++- web/scripts/placeholder.js | 29 ++-- web/scripts/placeholder.sh | 27 ---- web/scripts/start.sh | 6 - web/scripts/webpack-dev.sh | 2 +- 41 files changed, 757 insertions(+), 590 deletions(-) create mode 100644 SELF_HOSTING.md delete mode 100644 pkg/cli/Makefile delete mode 100755 pkg/cli/scripts/release.sh rename pkg/server/{job => }/.env.dev (84%) delete mode 100644 pkg/server/api/main.go create mode 100755 pkg/server/api/scripts/build.sh delete mode 100755 pkg/server/api/scripts/dev.sh rename pkg/server/database/{main.go => database.go} (94%) create mode 100644 pkg/server/database/migrate.go delete mode 100644 pkg/server/database/migrate.yml create mode 100644 pkg/server/database/sql-migrate.yml delete mode 100644 pkg/server/job/.gitignore rename pkg/server/job/{digest => }/digest.go (93%) rename pkg/server/job/{main.go => job.go} (57%) delete mode 100755 pkg/server/job/scripts/dev.sh create mode 100644 pkg/server/main.go create mode 100755 pkg/server/scripts/build.sh create mode 100755 scripts/release.sh delete mode 100644 web/.env.dev delete mode 100644 web/main.go mode change 100644 => 100755 web/scripts/placeholder.js delete mode 100755 web/scripts/placeholder.sh delete mode 100755 web/scripts/start.sh diff --git a/.gitignore b/.gitignore index 61ead866..90ab5fde 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /vendor +/build diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d06e4700..6bdbd118 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,51 +2,61 @@ This repository contains the server side and the client side code for Dnote. -## Set up +* [Setting up](#setting-up) +* [Command Linux Interface](#command-line-interface) +* [Server](#server) -1. Download and setup the [Go programming language](https://golang.org/dl/). -2. Download the project +## Setting up + +1. Install the following prerequisites if necessary: + +* [Go programming language](https://golang.org/dl/) 1.12+ +* [Node.js](https://nodejs.org/) 10.16+ +* Postgres 10.9+ + +2. Get the Dnote code: ```sh go get github.com/dnote/dnote ``` -## CLI +3. Run `make` to install dependencies -### Set up +## Command Line Interface -Download dependencies using [dep](https://github.com/golang/dep). +### Build -```sh -dep ensure +You can build either a development version or a production version: + +``` +# Build a development version for your platform and place it in your `PATH`. +make debug=true build-cli + +# Build a production version +make version=v0.1.0 build-cli ``` ### Test -Run: +* Run all tests for the command line interface: -```sh -./cli/scripts/test.sh +``` +make test-cli ``` ### Debug -Run Dnote with `DNOTE_DEBUG=1` to print debugging statements. +Run Dnote with `DNOTE_DEBUG=1` to print debugging statements. For instance: + +``` +DNOTE_DEBUG=1 dnote sync +``` ### Release -* Build for all target platforms, tag, push tags -* Release on GitHub and [Dnote Homebrew tap](https://github.com/dnote/homebrew-dnote). - -```sh -VERSION=0.4.8 make release -``` - -* Build, without releasing, for all target platforms - -```sh -VERSION=0.4.8 make -``` +* Run `make version=v0.1.0 release-cli` to achieve the following: + * Build for all target platforms, create a git tag, push all tags to the repository + * Create a release on GitHub and [Dnote Homebrew tap](https://github.com/dnote/homebrew-dnote). **Note** @@ -54,39 +64,24 @@ VERSION=0.4.8 make - disable the homebrew release by commenting out relevant code in the release script. - mark release as pre-release on GitHub release -## Web - -### Set up - -Download dependencies using [dep](https://github.com/golang/dep) and npm. - -```sh -dep ensure -npm install -``` - -### Test - -Run: - -``` -npm run test -``` - ## Server -### Set up +The server consists of the frontend web application and a web server. -Download dependencies using [dep](https://github.com/golang/dep). +### Development -```sh -dep ensure -``` +* Create a postgres database by running `createdb -O postgres dnote` +* If the role does not exist, you can create it by running `sudo -u postgres createuser postgres` + +* Run `make dev-server` to start a local server ### Test -Run: +```bash +# Run tests for the frontend web application +make test-web +# Run tests for API +make test-api ``` -./server/api/scripts/test-local.sh -``` + diff --git a/Gopkg.lock b/Gopkg.lock index b50d93d1..bc8e5a53 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -45,6 +45,51 @@ revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4" version = "v1.7.0" +[[projects]] + digest = "1:d29ee5ef14a7e0253facd0bcebe6a69a7a4e02a67eb24d2aacd8ccb4a7cea6fc" + name = "github.com/gobuffalo/envy" + packages = ["."] + pruneopts = "UT" + revision = "043cb4b8af871b49563291e32c66bb84378a60ac" + version = "v1.7.0" + +[[projects]] + digest = "1:6c9088827dcc7a807c97592a55f190e35e0fdaf237c5a074256c260925c59428" + name = "github.com/gobuffalo/logger" + packages = ["."] + pruneopts = "UT" + revision = "7c291b53e05b81d77bd43109b4a3c6f84e45c8e1" + version = "v1.0.1" + +[[projects]] + digest = "1:40849e8495ef81a84ff335ef65e23d33671b61e60e9db464fbab55f19f43f120" + name = "github.com/gobuffalo/packd" + packages = [ + ".", + "internal/takeon/github.com/markbates/errx", + ] + pruneopts = "UT" + revision = "54ea459691466cfb630ccc276723fe3963f3e9d5" + version = "v0.3.0" + +[[projects]] + digest = "1:d5be00af71142774ee6738480e580ae975e8e97158f9334b13af41a77f2f6b0c" + name = "github.com/gobuffalo/packr" + packages = [ + "v2", + "v2/file", + "v2/file/resolver", + "v2/file/resolver/encoding/hex", + "v2/internal/takeon/github.com/markbates/errx", + "v2/internal/takeon/github.com/markbates/oncer", + "v2/internal/takeon/github.com/markbates/safe", + "v2/jam/parser", + "v2/plog", + ] + pruneopts = "UT" + revision = "9eb7a3d310e89e471c2cdf1ea3ec8d7fc1ab969c" + version = "v2.5.2" + [[projects]] digest = "1:318f1c959a8a740366fce4b1e1eb2fd914036b4af58fbd0a003349b305f118ad" name = "github.com/golang/protobuf" @@ -141,6 +186,22 @@ revision = "23d116af351c84513e1946b527c88823e476be13" version = "v1.3.0" +[[projects]] + digest = "1:77857b3205f936bdc6928ef347b682ab549cf99454d6c0ca04a49f8df9e418f3" + name = "github.com/karrick/godirwalk" + packages = ["."] + pruneopts = "UT" + revision = "73c17a9b9528eb3ce857b782a2816c0cda581e62" + version = "v1.10.12" + +[[projects]] + digest = "1:31e761d97c76151dde79e9d28964a812c46efc5baee4085b86f68f0c654450de" + name = "github.com/konsorten/go-windows-terminal-sequences" + packages = ["."] + pruneopts = "UT" + revision = "f55edac94c9bbba5d6182a4be46d86a2c9b5b50e" + version = "v1.0.2" + [[projects]] digest = "1:bdd53b87de8185da386bae179c84d4848854c6870bacacf6a154fe63e2e750f7" name = "github.com/lib/pq" @@ -154,13 +215,11 @@ version = "v1.1.1" [[projects]] - digest = "1:639aaff480d288c3dbc99ae68f00d58ae43c283a85725ae3afaada7b5810c6be" + digest = "1:1241b137a50b99f7f395e6d3d917cafa4330bd17c55dacef18bfa8d87707533a" name = "github.com/markbates/goth" packages = [ ".", "gothic", - "providers/github", - "providers/gplus", ] pruneopts = "UT" revision = "3b8012093d951beedd026d120be1792db01a08f6" @@ -207,7 +266,18 @@ version = "v1.2.0" [[projects]] - branch = "master" + digest = "1:e09ada96a5a41deda4748b1659cc8953961799e798aea557257b56baee4ecaf3" + name = "github.com/rogpeppe/go-internal" + packages = [ + "modfile", + "module", + "semver", + ] + pruneopts = "UT" + revision = "438578804ca6f31be148c27683afc419ce47c06e" + version = "v1.3.0" + +[[projects]] digest = "1:83eb06141fc25c7fd89f8b39717962ac930a7480796aab3d46c7e88507a69173" name = "github.com/rubenv/sql-migrate" packages = [ @@ -233,6 +303,14 @@ revision = "1744e2970ca51c86172c8190fadad617561ed6e7" version = "v1.0.0" +[[projects]] + digest = "1:04457f9f6f3ffc5fea48e71d62f2ca256637dee0a04d710288e27e05c8b41976" + name = "github.com/sirupsen/logrus" + packages = ["."] + pruneopts = "UT" + revision = "839c75faf7f98a33d445d181f3018b5c3409a45e" + version = "v1.4.2" + [[projects]] digest = "1:e096613fb7cf34743d49af87d197663cfccd61876e2219853005a57baedfa562" name = "github.com/spf13/cobra" @@ -385,17 +463,14 @@ "github.com/aymerick/douceur/inliner", "github.com/dnote/actions", "github.com/dnote/color", + "github.com/gobuffalo/packr/v2", "github.com/google/go-github/github", "github.com/gorilla/mux", - "github.com/gorilla/securecookie", - "github.com/gorilla/sessions", "github.com/jinzhu/gorm", "github.com/joho/godotenv", "github.com/lib/pq", "github.com/markbates/goth", "github.com/markbates/goth/gothic", - "github.com/markbates/goth/providers/github", - "github.com/markbates/goth/providers/gplus", "github.com/mattn/go-sqlite3", "github.com/pkg/errors", "github.com/robfig/cron", diff --git a/Gopkg.toml b/Gopkg.toml index 11322922..a44f73cc 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -45,14 +45,6 @@ name = "github.com/gorilla/mux" version = "1.7.2" -[[constraint]] - name = "github.com/gorilla/securecookie" - version = "1.1.1" - -[[constraint]] - name = "github.com/gorilla/sessions" - version = "1.1.3" - [[constraint]] name = "github.com/jinzhu/gorm" version = "1.9.9" @@ -120,3 +112,7 @@ [[constraint]] revision = "f4d34eae5a5cf210693e81c604e6bac5f6727927" name = "github.com/rubenv/sql-migrate" + +[[constraint]] + name = "github.com/gobuffalo/packr" + version = "2.5.2" diff --git a/Makefile b/Makefile index cb6e2fef..4e09fd5b 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ DEP := $(shell command -v dep 2> /dev/null) +PACKR2 := $(shell command -v packr2 2> /dev/null) NPM := $(shell command -v npm 2> /dev/null) +HUB := $(shell command -v hub 2> /dev/null) ## installation install: install-go install-js @@ -11,6 +13,11 @@ ifndef DEP @curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh endif +ifndef PACKR2 + @echo "==> installing packr2" + @go get -u github.com/gobuffalo/packr/v2/packr2 +endif + @echo "==> installing go dependencies" @dep ensure .PHONY: install-go @@ -30,6 +37,9 @@ endif .PHONY: install-js ## test +test: test-cli test-api test-web +.PHONY: test + test-cli: @echo "==> running CLI test" @${GOPATH}/src/github.com/dnote/dnote/pkg/cli/scripts/test.sh @@ -45,26 +55,81 @@ test-web: @(cd ${GOPATH}/src/github.com/dnote/dnote/web && npm run test) .PHONY: test-web -test: test-cli test-api test-web -.PHONY: test +# development +dev-server: + @echo "==> running dev environment" + @(cd ${GOPATH}/src/github.com/dnote/dnote/web && ./scripts/dev.sh) +.PHONY: dev-server ## build build-web: @echo "==> building web" - @${GOPATH}/src/github.com/dnote/dnote/web/scripts/build-prod.sh + @(cd ${GOPATH}/src/github.com/dnote/dnote/web && ./scripts/build-prod.sh) .PHONY: build-web -build-dev-cli: - @echo "==> building dev cli" - @${GOPATH}/src/github.com/dnote/dnote/pkg/cli/scripts/dev.sh -.PHONY: build-dev-cli - -## migrate -migrate: -ifndef GO_ENV - $(error "environment variable GO_ENV is required.") +build-server: build-web +ifndef version + $(error version is required. Usage: make version=v0.1.0 build-server) endif - @echo "==> running migrations" - @(cd ${GOPATH}/src/github.com/dnote/dnote/pkg/server/database/migrate && go run *.go --migrationDir ../migrations) -.PHONY: migrate + @echo "==> building server" + @(cd ${GOPATH}/src/github.com/dnote/dnote/pkg/server && ./scripts/build.sh $(version)) +.PHONY: build-server + +build-cli: +ifeq ($(debug), true) + @echo "==> building cli in dev mode" + @${GOPATH}/src/github.com/dnote/dnote/pkg/cli/scripts/dev.sh +else + +ifndef version + $(error version is required. Usage: make version=v0.1.0 build-cli) +endif + + @echo "==> building cli" + @${GOPATH}/src/github.com/dnote/dnote/pkg/cli/scripts/build.sh $(version) +endif +.PHONY: build-cli + +## release +release-cli: build-cli +ifndef version + $(error version is required. Usage: make version=v0.1.0 release-cli) +endif +ifndef HUB + $(error please install hub) +endif + + @homebrewRepoDir=${GOPATH}/src/github.com/dnote/homebrew-dnote + if [ ! -d ${homebrewRepoDir} ]; then + @echo "homebrew-dnote not found locally. did you clone it?" + @exit 1 + fi + + @echo "==> releasing cli" + @outputDir=${GOPATH}/src/github.com/dnote/dnote/build/cli + @${GOPATH}/src/github.com/dnote/dnote/pkg/cli/scripts/release.sh cli $(version) ${outputDir} + + @echo "===> releading on Homebrew" + @homebrew_sha256=$(shasum -a 256 "${outputDir}/dnote_$(version)_darwin_amd64.tar.gz" | cut -d ' ' -f 1) + @(cd "${homebrewRepoDir}" && ./release.sh "$(version)" "${homebrew_sha256}") +.PHONY: release-cli + +release-server: build-server +ifndef version + $(error version is required. Usage: make version=v0.1.0 release-server) +endif +ifndef HUB + $(error please install hub) +endif + + @echo "==> releasing server" + @outputDir=${GOPATH}/src/github.com/dnote/dnote/build/server + @${GOPATH}/src/github.com/dnote/dnote/pkg/server/scripts/release.sh server $(version) ${outputDir} +.PHONY: release-server + +clean: + @git clean -f + @rm -rf build + @rm -rf web/public +.PHONY: clean diff --git a/SELF_HOSTING.md b/SELF_HOSTING.md new file mode 100644 index 00000000..ac77f343 --- /dev/null +++ b/SELF_HOSTING.md @@ -0,0 +1,101 @@ +# Hosting Dnote On Your Machine + +This guide documents the steps for installing the Dnote server on your own machine. + +**Please note that self hosted version of Dnote server is currently in beta.** + +## Installation + +1. Install Postgres 10+. +2. Create a `dnote` database by running `createdb dnote` +2. Download the official Dnote server release. +3. Extract the archive and move the `dnote-server` executable to `/usr/local/bin`. + +```bash +tar -xzf dnote-server-$version-$os.tar.gz +chmod +x ./dnote-server/dnote-server +mv ./dnote-server/dnote-server /usr/local/bin +``` + +4. Run Dnote + +```bash +GO_ENV=PRODUCTION \ +DBHost=localhost \ +DBPort=5432 \ +DBName=dnote \ +DBUser=$user \ +DBPassword=$password \ + dnote-server start +``` + +Replace $user and $password with the credentials of the Postgres user that owns the `dnote` database. + +By default, dnote server will run on the port 8080. + +## Configuration + +By now, Dnote is fully functional in your machine. The API, frontend app, and the background tasks are all in the single binary. Let's take a few more steps to configure Dnote. + +### Configure Nginx + +To make it accessible from the Internet, you need to configure Nginx. + +1. Create a new file in `/etc/nginx/sites-enabled/dnote` with the following contents: + +``` +server { + server_name my-dnote-server.com; + + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header Host $host; + proxy_pass http://127.0.0.1:8080; + } +} +``` + +2. Reload the nginx configuration by running the following: + +``` +sudo service nginx reload +``` + +Now you can access the Dnote frontend application on `http://my-dnote-server.com`, and the API on `http://my-dnote-server.com/api`. + +### Configure TLS by using LetsEncrypt + +TODO + +### Run Dnote As a Daemon + +We can use `systemctl` to run Dnote in the background as a Daemon, and automatically start it on system reboot. + +1. Create a new file at `/etc/systemd/system/dnote.service` with the following content: + +``` +[Unit] +Description=Starts the dnote server +Requires=network.target +After=network.target + +[Service] +Type=simple +User=$user +Restart=always +RestartSec=3 +WorkingDirectory=/home/$user +ExecStart=/home/$user/dnote-server start +Environment=GO_ENV=PRODUCTION DBHost=localhost DBPort=5432 DBName=dnote DBUser=$DBUser +Environment=DBPassword=$DBPassword + +[Install] +WantedBy=multi-user.target +``` + +Replace `$user`, `$DBUser`, and `$DBPassword` with the actual values. + +2. Reload the change by running `sudo systemctl daemon-reload`. +3. Enable the Daemon by running `sudo systemctl enable dnote`.` +4. Start the Daemon by running `sudo systemctl start dnote` diff --git a/pkg/cli/Makefile b/pkg/cli/Makefile deleted file mode 100644 index 1cfaed29..00000000 --- a/pkg/cli/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -all: - ./scripts/build.sh $(VERSION) -.PHONY: all - -release: - ./scripts/build.sh $(VERSION) - ./scripts/release.sh $(VERSION) -.PHONY: release - -clean: - @git clean -f -.PHONY: clean diff --git a/pkg/cli/install.sh b/pkg/cli/install.sh index b933942c..3eb19d14 100755 --- a/pkg/cli/install.sh +++ b/pkg/cli/install.sh @@ -113,18 +113,19 @@ hash_sha256() { } verify_checksum() { - binary_path=$1 - filename=$2 - checksums=$3 + filepath=$1 + checksums=$2 + + filename=$(basename "$filepath") want=$(grep "${filename}" "${checksums}" 2>/dev/null | cut -d ' ' -f 1) if [ -z "$want" ]; then print_error "unable to find checksum for '${filename}' in '${checksums}'" exit 1 fi - got=$(hash_sha256 "$binary_path") + got=$(hash_sha256 "$filepath") if [ "$want" != "$got" ]; then - print_error "checksum for '$binary_path' did not verify ${want} vs $got" + print_error "checksum for '$filepath' did not verify ${want} vs $got" exit 1 fi } @@ -173,13 +174,13 @@ install_dnote() { print_step "Downloading the checksum file for v$version" http_download "$tmpdir/$checksum" "$checksum_url" + print_step "Comparing checksums for binaries." + verify_checksum "$tmpdir/$tarball" "$tmpdir/$checksum" + # unzip tar print_step "Inflating the binary." (cd "${tmpdir}" && tar -xzf "${tarball}") - print_step "Comparing checksums for binaries." - verify_checksum "${tmpdir}/${binary}" "$filename" "$tmpdir/$checksum" - install -d "${bindir}" install "${tmpdir}/${binary}" "${bindir}/" diff --git a/pkg/cli/scripts/build.sh b/pkg/cli/scripts/build.sh index 6fdbb2f1..680173bf 100755 --- a/pkg/cli/scripts/build.sh +++ b/pkg/cli/scripts/build.sh @@ -1,16 +1,15 @@ #!/bin/bash # -# build.sh compiles dnote binary for target platforms -# it is resonsible for creating distributable files that can -# be released by a human or a script +# build.sh compiles dnote binary for target platforms. It is resonsible for creating +# distributable files that can be released by a human or a script. # use: ./scripts/build.sh 0.4.8 -set -eu +set -eux -version="$1" +version=$1 projectDir="$GOPATH/src/github.com/dnote/dnote" basedir="$GOPATH/src/github.com/dnote/dnote/pkg/cli" -TMP="$basedir/build" +outputDir="$projectDir/build/cli" command_exists () { command -v "$1" >/dev/null 2>&1; @@ -29,78 +28,55 @@ if [[ $1 == v* ]]; then exit 1 fi +goVersion=1.12.x + +get_binary_name() { + platform=$1 + + if [ "$platform" == "windows" ]; then + echo "dnote.exe" + else + echo "dnote" + fi +} + build() { - # init build dir - rm -rf "$TMP" - mkdir "$TMP" + platform=$1 + arch=$2 - # fetch tool - go get -u github.com/karalabe/xgo + # build binary + destDir="$outputDir/$platform-$arch" - pushd "$basedir" + mkdir -p "$destDir" + xgo \ + -go "$goVersion" \ + -ldflags "-X main.apiEndpoint=https://api.dnote.io -X main.versionTag=$version" \ + --targets="$platform/$arch" \ + --tags "fts5" \ + --dest="$destDir" \ + "$basedir" - # build linux - xgo --targets="linux/amd64"\ - --tags "linux fts5"\ - -ldflags "-X main.apiEndpoint=https://api.dnote.io -X main.versionTag=$version" . - mkdir "$TMP/linux" - mv cli-linux-amd64 "$TMP/linux/dnote" + binaryName=$(get_binary_name "$platform") + mv "$destDir/cli-${platform}-"* "$destDir/$binaryName" - # build darwin - xgo --targets="darwin/amd64"\ - --tags "darwin fts5"\ - -ldflags "-X main.apiEndpoint=https://api.dnote.io -X main.versionTag=$version" . - mkdir "$TMP/darwin" - mv cli-darwin-10.6-amd64 "$TMP/darwin/dnote" + # build tarball + tarballName="dnote_${version}_${platform}_${arch}.tar.gz" + tarballPath="$outputDir/$tarballName" - # build windows - xgo --targets="windows/amd64"\ - --tags "fts5"\ - -ldflags "-X main.apiEndpoint=https://api.dnote.io -X main.versionTag=$version" . - mkdir "$TMP/windows" - mv cli-windows-4.0-amd64.exe "$TMP/windows/dnote.exe" + cp "$projectDir/licenses/GPLv3.txt" "$destDir" + cp "$basedir/README.md" "$destDir" + tar -C "$destDir" -zcvf "$tarballPath" "." + rm -rf "$destDir" + # calculate checksum + pushd "$outputDir" + shasum -a 256 "$tarballName" >> "$outputDir/dnote_${version}_checksums.txt" popd } -get_buildname() { - os=$1 - - echo "dnote_${version}_${os}_amd64" -} - -calc_checksum() { - os=$1 - - pushd "$TMP/$os" - - buildname=$(get_buildname "$os") - mv dnote "$buildname" - shasum -a 256 "$buildname" >> "$TMP/dnote_${version}_checksums.txt" - mv "$buildname" dnote - - popd -} - -build_tarball() { - os=$1 - buildname=$(get_buildname "$os") - - pushd "$TMP/$os" - - cp "$projectDir/licenses/GPLv3.txt" . - cp "$basedir/README.md" . - tar -zcvf "../${buildname}.tar.gz" ./* - - popd -} - -build - -calc_checksum darwin -calc_checksum linux - -build_tarball windows -build_tarball darwin -build_tarball linux +# fetch tool +go get -u github.com/karalabe/xgo +build linux amd64 +build darwin amd64 +build windows amd64 diff --git a/pkg/cli/scripts/release.sh b/pkg/cli/scripts/release.sh deleted file mode 100755 index 160a5fa2..00000000 --- a/pkg/cli/scripts/release.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash -# -# release.sh releases the tarballs and checksum in the build directory -# to GitHub and brew. It is important to build those files using build.sh -# use: ./scripts/release.sh v0.4.8 - -set -eu - -homebrewRepoDir="$GOPATH"/src/github.com/dnote/homebrew-dnote - -command_exists () { - command -v "$1" >/dev/null 2>&1; -} - -if [ $# -eq 0 ]; then - echo "no version specified." - exit 1 -fi -if [[ $1 == v* ]]; then - echo "do not prefix version with v" - exit 1 -fi - -if ! command_exists hub; then - echo "please install hub" - exit 1 -fi - -if [ ! -d "$homebrewRepoDir" ]; then - echo "homebrew-dnote not found locally. did you clone it?" - exit 1 -fi - -# 1. push tag -version=$1 -version_tag="cli-v$version" - -echo "* tagging and pushing the tag" -git tag -a "$version_tag" -m "Release $version_tag" -git push --tags - -# 2. release on GitHub -files=(./build/*.tar.gz ./build/*.txt) -file_args=() -for file in "${files[@]}"; do - file_args+=("--attach=$file") -done - -echo "* creating release" -set -x -hub release create \ - "${file_args[@]}" \ - --message="$version_tag"\ - "$version_tag" - -# 3. Release on Homebrew -homebrew_sha256=$(shasum -a 256 "./build/dnote_${version}_darwin_amd64.tar.gz" | cut -d ' ' -f 1) -(cd "$homebrewRepoDir" && ./release.sh "$version" "$homebrew_sha256") diff --git a/pkg/server/job/.env.dev b/pkg/server/.env.dev similarity index 84% rename from pkg/server/job/.env.dev rename to pkg/server/.env.dev index 64565a29..bea40d2d 100644 --- a/pkg/server/job/.env.dev +++ b/pkg/server/.env.dev @@ -1,6 +1,6 @@ DB_HOST=localhost -POSTGRES_USER=sung POSTGRES_DB=dnote +POSTGRES_USER=postgres SmtpUsername=mock-SmtpUsername SmtpPassword=mock-SmtpPassword diff --git a/pkg/server/README.md b/pkg/server/README.md index c41db34f..1f10f8f0 100644 --- a/pkg/server/README.md +++ b/pkg/server/README.md @@ -1,3 +1,3 @@ # Dnote Server -The server side infrastructure for Dnote. +The Dnote server containing the web interface, the web API, and the background jobs. diff --git a/pkg/server/api/.gitignore b/pkg/server/api/.gitignore index 3e3dcbd0..78d0d862 100644 --- a/pkg/server/api/.gitignore +++ b/pkg/server/api/.gitignore @@ -4,3 +4,4 @@ application.zip test-api /dump api +/build diff --git a/pkg/server/api/handlers/health.go b/pkg/server/api/handlers/health.go index eb46107e..1cdc03ef 100644 --- a/pkg/server/api/handlers/health.go +++ b/pkg/server/api/handlers/health.go @@ -24,4 +24,5 @@ import ( func (a *App) checkHealth(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) + w.Write([]byte("ok")) } diff --git a/pkg/server/api/main.go b/pkg/server/api/main.go deleted file mode 100644 index 819a326a..00000000 --- a/pkg/server/api/main.go +++ /dev/null @@ -1,103 +0,0 @@ -/* Copyright (C) 2019 Monomax Software Pty Ltd - * - * This file is part of Dnote. - * - * Dnote is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Dnote 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Dnote. If not, see . - */ - -package main - -import ( - "flag" - "fmt" - "log" - "net/http" - "os" - - "github.com/dnote/dnote/pkg/server/api/clock" - "github.com/dnote/dnote/pkg/server/api/handlers" - "github.com/dnote/dnote/pkg/server/api/logger" - "github.com/dnote/dnote/pkg/server/database" - "github.com/dnote/dnote/pkg/server/mailer" - - "github.com/gorilla/mux" - "github.com/gorilla/securecookie" - "github.com/gorilla/sessions" - "github.com/markbates/goth" - "github.com/markbates/goth/gothic" - "github.com/markbates/goth/providers/github" - "github.com/markbates/goth/providers/gplus" - "github.com/pkg/errors" -) - -var ( - emailTemplateDir = flag.String("emailTemplateDir", "../mailer/templates/src", "the path to the template directory") -) - -func getOauthCallbackURL(provider string) string { - if os.Getenv("GO_ENV") == "PRODUCTION" { - return fmt.Sprintf("%s/api/auth/%s/callback", os.Getenv("WebHost"), provider) - } - - return fmt.Sprintf("%s:%s/api/auth/%s/callback", os.Getenv("Host"), os.Getenv("WebPort"), provider) -} - -func init() { - // Set up Oauth - gothic.Store = sessions.NewCookieStore(securecookie.GenerateRandomKey(32), securecookie.GenerateRandomKey(32)) - goth.UseProviders( - github.New( - os.Getenv("GithubClientID"), - os.Getenv("GithubClientSecret"), - getOauthCallbackURL("github"), - ), - gplus.New( - os.Getenv("GoogleClientID"), - os.Getenv("GoogleClientSecret"), - getOauthCallbackURL("gplus"), - "https://www.googleapis.com/auth/plus.me", - ), - ) - - gothic.GetProviderName = func(r *http.Request) (name string, err error) { - vars := mux.Vars(r) - name = vars["provider"] - return - } -} - -func main() { - flag.Parse() - - mailer.InitTemplates(*emailTemplateDir) - - database.InitDB() - database.InitSchema() - defer database.CloseDB() - - if err := logger.Init(); err != nil { - log.Println(errors.Wrap(err, "initializing logger")) - } - - app := handlers.App{ - Clock: clock.New(), - StripeAPIBackend: nil, - } - r := handlers.NewRouter(&app) - - port := os.Getenv("PORT") - logger.Notice("API listening on port %s", port) - - log.Println(http.ListenAndServe(":"+port, r)) -} diff --git a/pkg/server/api/scripts/build.sh b/pkg/server/api/scripts/build.sh new file mode 100755 index 00000000..77a332f9 --- /dev/null +++ b/pkg/server/api/scripts/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -eux + +basePath="$GOPATH/src/github.com/dnote/dnote/pkg/server/api" + +cd "$basePath" +GOOS=linux GOARCH=amd64 go build -o "$basePath/build/api" main.go diff --git a/pkg/server/api/scripts/dev.sh b/pkg/server/api/scripts/dev.sh deleted file mode 100755 index 5293b7ee..00000000 --- a/pkg/server/api/scripts/dev.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -# dev.sh starts a development environment for the api. -# usage: DOTENV_PATH=path_to_dotenv_file EMAIL_TEMPLATE_DIR=path_to_email_templates ./dev.sh -set -eux - -basePath="$GOPATH/src/github.com/dnote/dnote/pkg/server" - -# load env -export $(cat "$DOTENV_PATH" | xargs) - -PORT=5000 CompileDaemon \ - -directory="$basePath"/api \ - -command="$basePath/api/api --emailTemplateDir $basePath/mailer/templates/src" diff --git a/pkg/server/database/main.go b/pkg/server/database/database.go similarity index 94% rename from pkg/server/database/main.go rename to pkg/server/database/database.go index 7f446f40..0c474935 100644 --- a/pkg/server/database/main.go +++ b/pkg/server/database/database.go @@ -28,6 +28,11 @@ import ( _ "github.com/lib/pq" ) +var ( + // MigrationTableName is the name of the table that keeps track of migrations + MigrationTableName = "migrations" +) + func getPGConnectionString() string { if os.Getenv("GO_ENV") == "PRODUCTION" { return fmt.Sprintf( diff --git a/pkg/server/database/migrate.go b/pkg/server/database/migrate.go new file mode 100644 index 00000000..8bff7002 --- /dev/null +++ b/pkg/server/database/migrate.go @@ -0,0 +1,28 @@ +package database + +import ( + "log" + + "github.com/gobuffalo/packr/v2" + "github.com/pkg/errors" + "github.com/rubenv/sql-migrate" +) + +// Migrate runs the migrations +func Migrate() error { + migrations := &migrate.PackrMigrationSource{ + Box: packr.New("migrations", "../database/migrations/"), + } + + migrate.SetTable(MigrationTableName) + + db := DBConn.DB() + n, err := migrate.Exec(db, "postgres", migrations, migrate.Up) + if err != nil { + return errors.Wrap(err, "running migrations") + } + + log.Printf("Performed %d migrations", n) + + return nil +} diff --git a/pkg/server/database/migrate.yml b/pkg/server/database/migrate.yml deleted file mode 100644 index fa4ddb1b..00000000 --- a/pkg/server/database/migrate.yml +++ /dev/null @@ -1,8 +0,0 @@ -# a configuration for sql-migrate tool. We don't need to configure production, -# because we programmatically connect to the database and run migrations. -# Its main purpose is to generate migrations using `sql-migrate new` - -development: - dialect: postgres - datasource: dbname=dnote sslmode=disable - dir: ./migrations diff --git a/pkg/server/database/migrate/main.go b/pkg/server/database/migrate/main.go index cf1207d5..c36dbd98 100644 --- a/pkg/server/database/migrate/main.go +++ b/pkg/server/database/migrate/main.go @@ -55,6 +55,8 @@ func main() { Dir: *migrationDir, } + migrate.SetTable("migrations") + n, err := migrate.Exec(db.DB(), "postgres", migrations, migrate.Up) if err != nil { panic(errors.Wrap(err, "executing migrations")) diff --git a/pkg/server/database/scripts/create-migration.sh b/pkg/server/database/scripts/create-migration.sh index e45af691..f185be82 100755 --- a/pkg/server/database/scripts/create-migration.sh +++ b/pkg/server/database/scripts/create-migration.sh @@ -18,4 +18,4 @@ if [ "$#" == 0 ]; then fi filename=$1 -sql-migrate new -config=migrate.yml "$filename" +sql-migrate new -config=./sql-migrate.yml "$filename" diff --git a/pkg/server/database/sql-migrate.yml b/pkg/server/database/sql-migrate.yml new file mode 100644 index 00000000..f9c90d83 --- /dev/null +++ b/pkg/server/database/sql-migrate.yml @@ -0,0 +1,8 @@ +# A configuration for sql-migrate tool for generating migrations +# using `sql-migrate new`. This file is not actually used for running +# migrations because we run them programmatically. + +development: + dialect: postgres + datasource: dbname=dnote sslmode=disable + dir: ./migrations diff --git a/pkg/server/job/.gitignore b/pkg/server/job/.gitignore deleted file mode 100644 index 1ecf06f3..00000000 --- a/pkg/server/job/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/tmp -/dnote-job.tar.gz -job diff --git a/pkg/server/job/digest/digest.go b/pkg/server/job/digest.go similarity index 93% rename from pkg/server/job/digest/digest.go rename to pkg/server/job/digest.go index 5446eb0d..6fb1bb54 100644 --- a/pkg/server/job/digest/digest.go +++ b/pkg/server/job/digest.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package digest +package job import ( "log" @@ -27,8 +27,8 @@ import ( "github.com/pkg/errors" ) -// Make builds a weekly digest email -func Make(user database.User, emailAddr string) (*mailer.Email, error) { +// makeDigest builds a weekly digest email +func makeDigest(user database.User, emailAddr string) (*mailer.Email, error) { log.Printf("Sending for %s", emailAddr) db := database.DBConn @@ -92,8 +92,8 @@ func Make(user database.User, emailAddr string) (*mailer.Email, error) { return email, nil } -// Send sends the weekly digests to users -func Send() error { +// sendDigest sends the weekly digests to users +func sendDigest() error { db := database.DBConn var users []database.User @@ -111,7 +111,7 @@ func Send() error { continue } - email, err := Make(user, account.Email.String) + email, err := makeDigest(user, account.Email.String) if err != nil { log.Printf("Error occurred while sending to %s: %s", account.Email.String, err.Error()) continue diff --git a/pkg/server/job/main.go b/pkg/server/job/job.go similarity index 57% rename from pkg/server/job/main.go rename to pkg/server/job/job.go index 93f95bae..82a63de0 100644 --- a/pkg/server/job/main.go +++ b/pkg/server/job/job.go @@ -16,42 +16,15 @@ * along with Dnote. If not, see . */ -package main +package job import ( - "flag" "log" - "os" - "github.com/dnote/dnote/pkg/server/database" - "github.com/dnote/dnote/pkg/server/job/digest" - "github.com/dnote/dnote/pkg/server/mailer" - - "github.com/joho/godotenv" - _ "github.com/lib/pq" "github.com/pkg/errors" "github.com/robfig/cron" ) -var ( - emailTemplateDir = flag.String("emailTemplateDir", "../mailer/templates/src", "the path to the template directory") -) - -func init() { - // Load env - if os.Getenv("GO_ENV") == "PRODUCTION" { - err := godotenv.Load(".env") - if err != nil { - panic(err) - } - } else { - err := godotenv.Load(".env.dev") - if err != nil { - panic(err) - } - } -} - func scheduleJob(c *cron.Cron, spec string, cmd func()) { s, err := cron.ParseStandard(spec) if err != nil { @@ -61,22 +34,13 @@ func scheduleJob(c *cron.Cron, spec string, cmd func()) { c.Schedule(s, cron.FuncJob(cmd)) } -func main() { - flag.Parse() - - mailer.InitTemplates(*emailTemplateDir) - - database.InitDB() - defer database.CloseDB() - - // Run jobs on initial start - log.Println("Job is running") +// Run starts the background tasks and blocks forever. +func Run() { + log.Println("Started background tasks") // Schedule jobs c := cron.New() - - scheduleJob(c, "0 20 * * 5", func() { digest.Send() }) - + scheduleJob(c, "0 20 * * 5", func() { sendDigest() }) c.Start() // Block forever diff --git a/pkg/server/job/scripts/dev.sh b/pkg/server/job/scripts/dev.sh deleted file mode 100755 index 2d22dcde..00000000 --- a/pkg/server/job/scripts/dev.sh +++ /dev/null @@ -1 +0,0 @@ -CompileDaemon -directory=. -command="./job" diff --git a/pkg/server/mailer/mailer.go b/pkg/server/mailer/mailer.go index 793ace53..c14b4c88 100644 --- a/pkg/server/mailer/mailer.go +++ b/pkg/server/mailer/mailer.go @@ -27,6 +27,7 @@ import ( "path" "github.com/aymerick/douceur/inliner" + "github.com/gobuffalo/packr/v2" "github.com/pkg/errors" "gopkg.in/gomail.v2" ) @@ -54,26 +55,45 @@ func getTemplatePath(templateDirPath, filename string) string { // initTemplate returns a template instance by parsing the template with the // given name along with partials -func initTemplate(templateDirPath, templateFileName string) (*template.Template, error) { - templatePath := getTemplatePath(templateDirPath, templateFileName) - headerPath := getTemplatePath(templateDirPath, "header") - footerPath := getTemplatePath(templateDirPath, "footer") +func initTemplate(box *packr.Box, templateName string) (*template.Template, error) { + filename := fmt.Sprintf("%s.html", templateName) - t, err := template.ParseFiles(templatePath, headerPath, footerPath) + content, err := box.FindString(filename) if err != nil { - return nil, errors.Wrap(err, "parseing template") + return nil, errors.Wrap(err, "reading template") + } + headerContent, err := box.FindString("header.html") + if err != nil { + return nil, errors.Wrap(err, "reading header template") + } + footerContent, err := box.FindString("footer.html") + if err != nil { + return nil, errors.Wrap(err, "reading footer template") + } + + t := template.New(templateName) + if _, err = t.Parse(content); err != nil { + return nil, errors.Wrap(err, "parsing template") + } + if _, err = t.Parse(headerContent); err != nil { + return nil, errors.Wrap(err, "parsing template") + } + if _, err = t.Parse(footerContent); err != nil { + return nil, errors.Wrap(err, "parsing template") } return t, nil } // InitTemplates initializes templates -func InitTemplates(templateDirPath string) { - weeklyDigestTmpl, err := initTemplate(templateDirPath, EmailTypeWeeklyDigest) +func InitTemplates() { + box := packr.New("emailTemplates", "../mailer/templates/src") + + weeklyDigestTmpl, err := initTemplate(box, EmailTypeWeeklyDigest) if err != nil { panic(errors.Wrap(err, "initializing template")) } - emailVerificationTmpl, err := initTemplate(templateDirPath, EmailTypeEmailVerification) + emailVerificationTmpl, err := initTemplate(box, EmailTypeEmailVerification) if err != nil { panic(errors.Wrap(err, "initializing template")) } diff --git a/pkg/server/main.go b/pkg/server/main.go new file mode 100644 index 00000000..55e0a7c4 --- /dev/null +++ b/pkg/server/main.go @@ -0,0 +1,127 @@ +/* Copyright (C) 2019 Monomax Software Pty Ltd + * + * This file is part of Dnote. + * + * Dnote is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Dnote 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Dnote. If not, see . + */ + +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "strings" + + "github.com/dnote/dnote/pkg/server/api/clock" + "github.com/dnote/dnote/pkg/server/api/handlers" + "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/job" + "github.com/dnote/dnote/pkg/server/mailer" + + "github.com/gobuffalo/packr/v2" + "github.com/gorilla/mux" + "github.com/pkg/errors" +) + +var versionTag = "master" +var port = flag.String("port", "8080", "port to connect to") + +func init() { +} + +func getAppHandler() http.HandlerFunc { + box := packr.New("web", "../../web/public") + + fs := http.FileServer(box) + appShell, err := box.Find("index.html") + if err != nil { + panic(errors.Wrap(err, "getting index.html content")) + } + + return func(w http.ResponseWriter, r *http.Request) { + parts := strings.Split(r.URL.Path, "/") + if len(parts) >= 2 && parts[1] == "dist" { + fs.ServeHTTP(w, r) + return + } + + // All other requests should serve the index.html file + w.Write(appShell) + } +} + +func initServer() *mux.Router { + srv := mux.NewRouter() + + apiRouter := handlers.NewRouter(&handlers.App{ + Clock: clock.New(), + StripeAPIBackend: nil, + }) + + srv.PathPrefix("/api").Handler(http.StripPrefix("/api", apiRouter)) + srv.PathPrefix("/").HandlerFunc(getAppHandler()) + + return srv +} + +func startCmd() { + mailer.InitTemplates() + database.InitDB() + database.InitSchema() + defer database.CloseDB() + + // Perform database migration + if err := database.Migrate(); err != nil { + panic(errors.Wrap(err, "running migrations")) + } + + // Run job in the background + go job.Run() + + srv := initServer() + + log.Printf("Dnote version %s is running on port %s", versionTag, *port) + addr := fmt.Sprintf(":%s", *port) + log.Println(http.ListenAndServe(addr, srv)) +} + +func versionCmd() { + fmt.Printf("dnote-server-%s\n", versionTag) +} + +func main() { + flag.Parse() + cmd := flag.Arg(0) + + switch cmd { + case "": + fmt.Printf(`Dnote Server - A simple notebook for developers + +Usage: + dnote-server [command] + +Available commands: + start: Start the server + version: Print the version +`) + case "start": + startCmd() + case "version": + versionCmd() + default: + fmt.Printf("Unknown command %s", cmd) + } +} diff --git a/pkg/server/scripts/build.sh b/pkg/server/scripts/build.sh new file mode 100755 index 00000000..efd7d6b8 --- /dev/null +++ b/pkg/server/scripts/build.sh @@ -0,0 +1,60 @@ +#!/bin/bash +set -eux + +version=$1 +basePath="$GOPATH/src/github.com/dnote/dnote" +projectDir="$GOPATH/src/github.com/dnote/dnote" +basedir="$GOPATH/src/github.com/dnote/dnote/pkg/server" +outputDir="$projectDir/build/server" + +command_exists () { + command -v "$1" >/dev/null 2>&1; +} + +if ! command_exists shasum; then + echo "please install shasum" + exit 1 +fi +if [ $# -eq 0 ]; then + echo "no version specified." + exit 1 +fi +if [[ $1 == v* ]]; then + echo "do not prefix version with v" + exit 1 +fi + +build() { + platform=$1 + arch=$2 + + destDir="$outputDir/$platform-$arch" + mkdir -p "$destDir" + + # build binary + packr2 + + GOOS="$platform" \ + GOARCH="$arch" go build \ + -o "$destDir/dnote-server" \ + -ldflags "-X main.versionTag=$version" \ + "$basePath"/pkg/server/*.go + + packr2 clean + + # build tarball + tarballName="dnote_server_${version}_${platform}_${arch}.tar.gz" + tarballPath="$outputDir/$tarballName" + + cp "$projectDir/licenses/AGPLv3.txt" "$destDir" + cp "$basedir/README.md" "$destDir" + tar -C "$destDir" -zcvf "$tarballPath" "." + rm -rf "$destDir" + + # calculate checksum + pushd "$outputDir" + shasum -a 256 "$tarballName" >> "$outputDir/dnote_${version}_checksums.txt" + popd +} + +build linux amd64 diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 00000000..aa72ea23 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# +# release.sh releases the tarballs and checksum in the build directory +# to GitHub and brew. A prerequisite is to build those files using build.sh. +# use: ./scripts/release.sh cli v0.4.8 path/to/assets + +set -euxo pipefail + +project=$1 +version=$2 +assetPath=$3 + +if [ "$project" != "cli" ] || [ "$project" != "server" ]; then + echo "unrecognized project '$project'" + exit 1 +fi +if [ -z "$version" ]; then + echo "no version specified." + exit 1 +fi +if [[ $version == v* ]]; then + echo "do not prefix version with v" + exit 1 +fi + +# 1. push tag +version=$1 +version_tag="$project-v$version" + +echo "* tagging and pushing the tag" +git tag -a "$version_tag" -m "Release $version_tag" +git push --tags + +# 2. release on GitHub +files=("$assetPath"/*) +file_flags=() +for file in "${files[@]}"; do + file_flags+=("--attach=$file") +done + +# mark as prerelease if version is not in a form of major.minor.patch +# e.g. 1.0.1-beta.1 +flags=() +if [[ ! "$version" =~ ^[0-9]+.[0-9]+.[0-9]+$ ]]; then + flags+=("--prerelease") +fi + +echo "* creating release" +set -x +hub release create \ + "${file_flags[@]}" \ + "${flags[@]}" \ + --message="$version_tag"\ + "$version_tag" diff --git a/web/.env.dev b/web/.env.dev deleted file mode 100644 index e954c9d9..00000000 --- a/web/.env.dev +++ /dev/null @@ -1 +0,0 @@ -API_HOST=http://localhost:5000/ diff --git a/web/main.go b/web/main.go deleted file mode 100644 index e44cf075..00000000 --- a/web/main.go +++ /dev/null @@ -1,72 +0,0 @@ -/* Copyright (C) 2019 Monomax Software Pty Ltd - * - * This file is part of Dnote. - * - * Dnote is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Dnote 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Dnote. If not, see . - */ - -package main - -import ( - "log" - "net/http" - "net/http/httputil" - "net/url" - "os" - "strings" - - "github.com/joho/godotenv" - "github.com/pkg/errors" -) - -var apiProxy *httputil.ReverseProxy - -func appHandler(w http.ResponseWriter, r *http.Request) { - parts := strings.Split(r.URL.Path, "/") - if len(parts) >= 2 && parts[1] == "dist" { - fs := http.StripPrefix("/dist/", http.FileServer(http.Dir("./public/dist"))) - - fs.ServeHTTP(w, r) - return - } - - // All other requests should go to index.html - http.ServeFile(w, r, "./public/index.html") -} - -func init() { - if os.Getenv("GO_ENV") != "PRODUCTION" { - err := godotenv.Load(".env.dev") - if err != nil { - panic(errors.Wrap(err, "loading env vars")) - } - } - - apiHostURL, err := url.Parse(os.Getenv("API_HOST")) - if err != nil { - panic(errors.Wrap(err, "parsing api host url")) - } - - apiProxy = httputil.NewSingleHostReverseProxy(apiHostURL) -} - -func main() { - http.HandleFunc("/", appHandler) - http.Handle("/api/", http.StripPrefix("/api/", apiProxy)) - - port := os.Getenv("PORT") - log.Printf("Web listening on port %s", port) - - log.Println(http.ListenAndServe(":"+port, nil)) -} diff --git a/web/package-lock.json b/web/package-lock.json index 96d718b6..c45e29b8 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -5084,8 +5084,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -5103,13 +5102,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5122,18 +5119,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -5236,8 +5230,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -5247,7 +5240,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5260,20 +5252,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5290,7 +5279,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5363,8 +5351,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -5374,7 +5361,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5450,8 +5436,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -5481,7 +5466,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5499,7 +5483,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5538,13 +5521,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, diff --git a/web/scripts/build-prod.sh b/web/scripts/build-prod.sh index 8ccb352d..0ce6a568 100755 --- a/web/scripts/build-prod.sh +++ b/web/scripts/build-prod.sh @@ -6,12 +6,8 @@ basePath="$GOPATH/src/github.com/dnote/dnote" publicPath="$basePath/web/public" compiledPath="$basePath/web/compiled" - -baseUrl=https://dnote.io -assetBaseUrl=https://dnote.io - -# baseUrl=http://localhost:3000 -# assetBaseUrl=http://localhost:3000 +baseUrl="" +assetBaseUrl="" STANDALONE=true \ BASE_URL=$baseUrl \ @@ -19,4 +15,3 @@ ASSET_BASE_URL=$assetBaseUrl \ PUBLIC_PATH=$publicPath \ COMPILED_PATH=$compiledPath \ "$basePath"/web/scripts/build.sh - diff --git a/web/scripts/build.sh b/web/scripts/build.sh index 37b8d8b2..08f95944 100755 --- a/web/scripts/build.sh +++ b/web/scripts/build.sh @@ -1,11 +1,15 @@ #!/bin/bash -# build.sh builds a production bundle -set -eux +# build.sh builds a bundle +set -ex basePath="$GOPATH/src/github.com/dnote/dnote" + standalone=${STANDALONE:-false} isTest=${IS_TEST:-false} +baseUrl=$BASE_URL +assetBaseUrl=$ASSET_BASE_URL +set -u rm -rf "$basePath/web/public" mkdir -p "$basePath/web/public/dist" @@ -23,11 +27,11 @@ pushd "$basePath/web" --config "$basePath"/web/webpack/prod.config.js NODE_ENV=PRODUCTION \ - BASE_URL=$BASE_URL \ - ASSET_BASE_URL=$ASSET_BASE_URL \ + BASE_URL=$baseUrl \ + ASSET_BASE_URL=$assetBaseUrl \ PUBLIC_PATH=$PUBLIC_PATH \ COMPILED_PATH=$COMPILED_PATH \ - "$basePath"/web/scripts/placeholder.sh + node "$basePath"/web/scripts/placeholder.js cp "$COMPILED_PATH"/*.js "$COMPILED_PATH"/*.css "$PUBLIC_PATH"/dist diff --git a/web/scripts/dev.sh b/web/scripts/dev.sh index dd949dba..e5da5127 100755 --- a/web/scripts/dev.sh +++ b/web/scripts/dev.sh @@ -1,11 +1,25 @@ #!/bin/bash +# shellcheck disable=SC1090 # dev.sh builds and starts development environment for standalone app set -eux -o pipefail -basePath="$GOPATH/src/github.com/dnote/dnote" -appPath="$basePath"/web +# clean up background processes +function cleanup { + kill "$devServerPID" +} +trap cleanup EXIT -# run webpack-dev-server for js +basePath="$GOPATH/src/github.com/dnote/dnote" +appPath="$basePath/web" +serverPath="$basePath/pkg/server" + +# load env +set -a +dotenvPath="$serverPath/.env.dev" +source "$dotenvPath" +set +a + +# run webpack-dev-server for js in the background ( cd "$appPath" && @@ -18,6 +32,7 @@ appPath="$basePath"/web IS_TEST=true \ "$appPath"/scripts/webpack-dev.sh ) & +devServerPID=$! # run server -(cd "$appPath" && PORT=3000 go run main.go) +(cd "$serverPath" && go run main.go) diff --git a/web/scripts/placeholder.js b/web/scripts/placeholder.js old mode 100644 new mode 100755 index 15b115ac..4ccfe8b4 --- a/web/scripts/placeholder.js +++ b/web/scripts/placeholder.js @@ -1,20 +1,4 @@ -/* Copyright (C) 2019 Monomax Software Pty Ltd - * - * This file is part of Dnote. - * - * Dnote is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Dnote 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Dnote. If not, see . - */ +#!/usr/bin/env node // placeholder.js replaces the placeholders in index.html with real values // It is needed to load assets whose paths are not fixed because they change @@ -23,8 +7,16 @@ const fs = require('fs'); const path = require('path'); +const baseURL = process.env.BASE_URL; +const assetBaseURL = process.env.ASSET_BASE_URL; const publicPath = process.env.PUBLIC_PATH; const compiledPath = process.env.COMPILED_PATH; +if (!publicPath) { + throw new Error('No PUBLIC_PATH environment variable found'); +} +if (!compiledPath) { + throw new Error('No COMPILED_PATH environment variable found'); +} const indexHtmlPath = `${publicPath}/index.html`; @@ -39,9 +31,6 @@ try { const isProduction = process.env.NODE_ENV === 'PRODUCTION'; -const baseURL = process.env.BASE_URL; -const assetBaseURL = process.env.ASSET_BASE_URL; - function getJSBundleTag() { let jsBundleUrl; if (isProduction) { diff --git a/web/scripts/placeholder.sh b/web/scripts/placeholder.sh deleted file mode 100755 index 940ed21e..00000000 --- a/web/scripts/placeholder.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -set -eux - -basePath="$GOPATH/src/github.com/dnote/dnote" - -if [ -z "$BASE_URL" ]; then - echo "BASE_URL environment variable is not set" - exit 1 -fi -if [ -z "$ASSET_BASE_URL" ]; then - echo "ASSET_BASE_URL environment variable is not set" - exit 1 -fi -if [ -z "$PUBLIC_PATH" ]; then - echo "PUBLIC_PATH environment variable is not set" - exit 1 -fi -if [ -z "$COMPILED_PATH" ]; then - echo "COMPILED_PATH environment variable is not set" - exit 1 -fi - -BASE_URL=$BASE_URL \ -ASSET_BASE_URL=$ASSET_BASE_URL \ -PUBLIC_PATH=$PUBLIC_PATH \ -COMPILED_PATH=$COMPILED_PATH \ - node "$basePath/web/scripts/placeholder.js" diff --git a/web/scripts/start.sh b/web/scripts/start.sh deleted file mode 100755 index 754a8e1c..00000000 --- a/web/scripts/start.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -# start.sh starts the server - -export PORT=3000 - -go run main.go diff --git a/web/scripts/webpack-dev.sh b/web/scripts/webpack-dev.sh index 85181e04..d839775e 100755 --- a/web/scripts/webpack-dev.sh +++ b/web/scripts/webpack-dev.sh @@ -15,7 +15,7 @@ appPath="$basePath"/web COMPILED_PATH=$COMPILED_PATH \ PUBLIC_PATH=$PUBLIC_PATH \ IS_TEST=true \ - "$appPath"/scripts/placeholder.sh && + node "$appPath"/scripts/placeholder.js && "$appPath"/node_modules/.bin/webpack-dev-server\ --env.standalone="$STANDALONE"\