├── .gitignore ├── Debian.inc ├── LICENSE ├── Makefile ├── README.md ├── checks.mk ├── config ├── dev.sys.config ├── dev.vm.args ├── shell.config ├── sys.config └── vm.args ├── debian ├── Makefile ├── changelog ├── compat ├── control ├── copyright ├── dirs ├── postinst ├── postrm ├── rules ├── scpf.links ├── scpf.manpages ├── scpf.service.src └── source │ └── format ├── doc ├── README-src.md ├── README.md ├── apns_cert.md ├── apns_erlv3_app.md ├── apns_erlv3_session.md ├── apns_erlv3_session_sup.md ├── apns_json.md ├── apns_jwt.md ├── apns_lib.md ├── apns_lib_http2.md ├── apns_recs.md ├── apns_types.md ├── gcm_erl.md ├── gcm_erl_app.md ├── gcm_erl_session.md ├── gcm_erl_session_sup.md ├── gcm_erl_util.md ├── gcm_json.md ├── gcm_req_sched.md ├── man │ ├── README │ ├── scpf.1 │ └── scpf.1.template ├── sc_config.md ├── sc_priority_queue.md ├── sc_push.md ├── sc_push_app.md ├── sc_push_lib.md ├── sc_push_lib_app.md ├── sc_push_lib_sup.md ├── sc_push_reg_api.md ├── sc_push_reg_db.md ├── sc_push_reg_db_mnesia.md ├── sc_push_reg_db_postgres.md ├── sc_push_reg_resource.md ├── sc_push_reg_wm_device.md ├── sc_push_reg_wm_service.md ├── sc_push_reg_wm_tag.md ├── sc_push_req_mgr.md ├── sc_push_sup.md ├── sc_push_svc_apnsv3.md ├── sc_push_svc_gcm.md ├── sc_push_svc_null.md ├── sc_push_svc_null_srv.md ├── sc_push_top.md ├── sc_push_wm_common.md ├── sc_push_wm_helper.md ├── sc_push_wm_send_device.md ├── sc_push_wm_send_svc_appid_tok.md ├── sc_push_wm_send_svc_tok.md ├── sc_push_wm_send_tag.md ├── sc_push_wm_sup.md ├── sc_push_wm_version.md ├── sc_types.md ├── sc_util.md ├── sc_util_app.md └── sc_util_srv.md ├── erts-version ├── examples ├── push_apns.escript ├── recreate_node_db.sh └── recreate_nodes.sh ├── files ├── backup_database.escript ├── mnesia_init.erl ├── nodetool └── scpf.src ├── get_apns_tools.sh ├── manifest.sh ├── markedoc.sed ├── overlay ├── dev.vars.config └── vars.config ├── pkg.mk ├── pre_common_test_hook.sh ├── rebar.config ├── rebar.config.script ├── rebar.lock ├── rebar3 ├── scpf.test.spec.src ├── scpf_version.sh ├── src ├── sc_push_top.erl └── scpf.app.src ├── template_nodename.sh ├── test ├── scpf_SUITE.erl ├── scpf_SUITE.hrl ├── scpf_SUITE_data │ └── dispatch.conf ├── scpf_test_support.erl └── test.config └── util.mk /.gitignore: -------------------------------------------------------------------------------- 1 | .*.sw? 2 | .rebar/ 3 | MANIFEST 4 | build 5 | _build/ 6 | debian/files 7 | debian/scpf.debhelper.log 8 | debian/scpf.postinst.debhelper 9 | debian/scpf.postrm.debhelper 10 | debian/scpf.prerm.debhelper 11 | debian/scpf.service 12 | debian/scpf.substvars 13 | debian/scpf/ 14 | distdir/ 15 | doc/*.html 16 | doc/api/ 17 | doc/edoc-info 18 | doc/erlang.png 19 | doc/overview.edoc 20 | doc/stylesheet.css 21 | ebin/ 22 | logs/ 23 | scpf*.tar.gz 24 | test/*.beam 25 | doc/man/scpf.1 26 | tools/ 27 | *.crashdump 28 | _checkouts/ 29 | Mnesia.*/ 30 | log/ 31 | certs/ 32 | sasl_error.log 33 | INSTALL_DIR 34 | _debian_build/ 35 | scpf.test.spec 36 | test/scpf_SUITE_data/*Fake*.pem 37 | CONFIG 38 | _dev_package 39 | -------------------------------------------------------------------------------- /Debian.inc: -------------------------------------------------------------------------------- 1 | # 2 | # Debian Packaging 3 | # 4 | PACKAGE := scpf 5 | THIS_INCFILE := $(lastword $(MAKEFILE_LIST)) 6 | 7 | # REBAR_PROFILE *must* be defined! 8 | $(call assert_nonempty_var,REBAR_PROFILE,q) 9 | $(info $(THIS_INCFILE) is using REBAR_PROFILE=$(REBAR_PROFILE)) 10 | 11 | RELDIR = $(CURDIR)/_build/$(REBAR_PROFILE)/rel 12 | $(info RELDIR=$(RELDIR)) 13 | $(call assert_dir_exists $(RELDIR)) 14 | 15 | # make package RELEASE=devel|testing|qa|production 16 | RELEASE ?= devel 17 | 18 | # SIGN details 19 | # 20 | # These defaults can be overridden as follows: 21 | # 22 | # make package SIGN_KEY_ID=16B9D175 SIGN_FULLNAME='Silent Circle Debian Package Builder' SIGN_EMAIL='build@silentcircle.com' 23 | SIGN_KEY_ID ?= 16B9D175 24 | SIGN_FULLNAME ?= Silent Circle Debian Package Builder 25 | SIGN_EMAIL ?= build@silentcircle.com 26 | 27 | DISTDIR = ./distdir 28 | PKG_VERSION := $(shell dpkg-parsechangelog --count 0 | awk '/^Version:/ { print $$2 }') 29 | ERTS_VSN := $(shell ./erts-version) 30 | 31 | # Temp files 32 | # mktemp has redundant template spec due to OS X 33 | BUILDDIR := $(shell mktemp -d /tmp/$(PACKAGE)_build.XXXXXXXXXX) 34 | STAGING_DIR := $(shell mktemp -d /tmp/$(PACKAGE)_staging.XXXXXXXXXX) 35 | 36 | GIT := $(shell which git) 37 | 38 | ifeq ($(GIT),) 39 | $(error git is required for this build but is not on the path) 40 | endif 41 | 42 | ifneq ($(wildcard ./.git),) 43 | DCH_COMMENT := $(shell $(GIT) log --oneline -1) 44 | EXTRA_VERSION := $(shell $(GIT) describe --long | sed -re 's/^.*-([0-9]+)-([^-]+)$$/\1.\2/') 45 | else 46 | DCH_COMMENT := No .git dir, probably automated build 47 | EXTRA_VERSION := 0 48 | endif 49 | 50 | DATE := $(shell date +'%Y-%m-%d') 51 | DATETIME := $(shell date --utc +'%Y%m%d%H%M%S') 52 | OSNAME := $(shell lsb_release --short --id) 53 | ARCH := $(shell dpkg-architecture -qDEB_BUILD_ARCH) 54 | VERSIONSTRING = $(PACKAGE) ($(PKG_VERSION) $(DATE)) $(OSNAME) $(ARCH) 55 | DCH_VERSION := $(PKG_VERSION)+0~$(DATETIME).$(EXTRA_VERSION) 56 | 57 | # Erlang release version 58 | REL_VSN = $(shell cut -f2 -d' ' $(RELDIR)/$(PACKAGE)/releases/start_erl.data) 59 | REL_TARBALL = $(RELDIR)/$(PACKAGE)/$(PACKAGE)-$(REL_VSN).tar.gz 60 | 61 | PKG_LIB_DIR := $(DESTDIR)/usr/lib/$(PACKAGE) 62 | PKG_ETC_DIR := $(DESTDIR)/etc/$(PACKAGE) 63 | 64 | # install expects a release tarball to have been built 65 | install: 66 | echo "INSTALL_DIR=$(DESTDIR)" > INSTALL_DIR 67 | mkdir -p $(PKG_LIB_DIR) 68 | mkdir -p $(PKG_ETC_DIR) 69 | mkdir -p $(PKG_ETC_DIR)/certs 70 | chmod 0755 $(PKG_ETC_DIR)/certs 71 | cd $(STAGING_DIR) && tar xfz $(REL_TARBALL) 72 | cp -R $(STAGING_DIR)/lib $(PKG_LIB_DIR) 73 | cp -R $(STAGING_DIR)/bin $(PKG_LIB_DIR) 74 | cp -R $(STAGING_DIR)/releases $(PKG_LIB_DIR) 75 | cp -R $(STAGING_DIR)/$(ERTS_VSN) $(PKG_LIB_DIR)/ 76 | chmod 0755 $(PKG_LIB_DIR)/$(ERTS_VSN)/bin/* 77 | chmod 0755 $(PKG_LIB_DIR)/bin/mnesia_init 78 | chmod 0755 $(PKG_LIB_DIR)/bin/nodetool 79 | chmod 0755 $(PKG_LIB_DIR)/bin/$(PACKAGE)* 80 | install -m644 MANIFEST $(PKG_LIB_DIR)/MANIFEST.txt 81 | install -m644 INSTALL_DIR $(PKG_LIB_DIR)/releases/$(REL_VSN)/INSTALL_DIR 82 | install -m644 $(STAGING_DIR)/bin/start_clean.boot $(PKG_LIB_DIR)/releases/$(REL_VSN)/start_clean.boot 83 | install -m640 $(STAGING_DIR)/releases/$(REL_VSN)/sys.config $(PKG_ETC_DIR)/sys.config 84 | install -m640 $(STAGING_DIR)/releases/$(REL_VSN)/vm.args $(PKG_ETC_DIR)/vm.args 85 | rm -rf $(STAGING_DIR) 86 | 87 | # Build unsigned debian package 88 | dev_package: pkgclean 89 | mkdir -p $(BUILDDIR)/$(PACKAGE) 90 | cp -Rp . $(BUILDDIR)/$(PACKAGE)/ 91 | export DEBFULLNAME="$(SIGN_FULLNAME)"; \ 92 | export DEBEMAIL="$(SIGN_EMAIL)"; \ 93 | dch --noquery -c $(BUILDDIR)/$(PACKAGE)/debian/changelog \ 94 | --force-distribution \ 95 | --distribution experimental \ 96 | -b -v "$(DCH_VERSION)" "Developer build" 97 | cd $(BUILDDIR)/$(PACKAGE) && \ 98 | dpkg-buildpackage -d -us -uc 99 | mkdir -p $(DISTDIR) 100 | cp -Rp $(BUILDDIR)/* $(DISTDIR)/ 101 | 102 | # Build signed debian package 103 | # Note that the 'git clone .' will omit any uncommitted 104 | # files. 105 | package: pkgclean 106 | mkdir -p $(DISTDIR) 107 | $(GIT) clone . $(DISTDIR)/$(PACKAGE) 108 | export DEBFULLNAME="$(SIGN_FULLNAME)"; \ 109 | export DEBEMAIL="$(SIGN_EMAIL)"; \ 110 | dch --noquery -c $(DISTDIR)/$(PACKAGE)/debian/changelog \ 111 | --force-distribution \ 112 | --distribution $(RELEASE) \ 113 | -b -v "$(DCH_VERSION)" "$(DCH_COMMENT)" 114 | cd $(DISTDIR)/$(PACKAGE) && \ 115 | debuild --no-lintian \ 116 | -e REVISION="$(PKG_VERSION)" \ 117 | -e RELEASE="$(RELEASE)" \ 118 | -e VERSIONSTRING="$(VERSIONSTRING)" \ 119 | -e REBAR_PROFILE="$(REBAR_PROFILE)" \ 120 | -b \ 121 | -k$(SIGN_KEY_ID) 122 | 123 | pkgclean: 124 | @rm -rf $(DISTDIR) 125 | 126 | include util.mk 127 | 128 | # vim: set filetype=make syntax=make noet ts=4 sts=4 sw=4 si: 129 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------------------------- 2 | # This Makefile works with rebar3 and the profiles that rebar3 supports. This 3 | # makefile will run with the 'default' profile unless REBAR_PROFILE is 4 | # provided, e.g. in bash, 5 | # 6 | # make rel REBAR_PROFILE=prod 7 | #---------------------------------------------------------------------------- 8 | .PHONY: all clean compile ct dev_rel dev_package \ 9 | dialyzer distclean doc docclean install help info \ 10 | profiles prod_rel rel relclean run vsn 11 | 12 | PACKAGE := scpf 13 | PKG_LIB_DIR := $(DESTDIR)/usr/lib/$(PACKAGE) 14 | PKG_ETC_DIR := $(DESTDIR)/etc/$(PACKAGE) 15 | 16 | PROD_REL_DIR := ./_build/prod/rel/$(PACKAGE) 17 | REL_VSN = $(shell cut -f2 -d' ' $(PROD_REL_DIR)/releases/start_erl.data) 18 | 19 | REBAR_PROFILE ?= default 20 | THIS_MAKEFILE := $(lastword $(MAKEFILE_LIST)) 21 | 22 | $(info $(THIS_MAKEFILE) is using REBAR_PROFILE=$(REBAR_PROFILE)) 23 | 24 | REBAR3_URL = https://s3.amazonaws.com/rebar3/rebar3 25 | 26 | # If there is a rebar in the current directory, use it 27 | ifeq ($(wildcard rebar3),rebar3) 28 | REBAR = $(CURDIR)/rebar3 29 | endif 30 | 31 | # Fallback to rebar on PATH 32 | REBAR ?= $(shell which rebar3) 33 | 34 | # And finally, prep to download rebar if all else fails 35 | ifeq ($(REBAR),) 36 | REBAR = $(CURDIR)/rebar3 37 | endif 38 | 39 | all: vsn compile 40 | 41 | # PKG_VERSION is based on the value in debian/changelog. 42 | # Example: "2.0.3" 43 | # This is what should be used for documentation versions. 44 | # 45 | # DCH_VERSION is the version that adds a timestamp and `git describe` 46 | # value to the PKG_VERSION. 47 | # Example: "2.0.3~rc3+0~20170420142017.108+jessie" 48 | # 49 | # This should be used to version scpf in the packaging system. That is, 50 | # it should be the output of `dpkg -l scpf` once it is installed. The 51 | # reason this numbering scheme is used is that it is Debian-compliant, 52 | # and compares correctly when used with `dpkg --compare-versions`, so 53 | # upgrades will be done correctly even if the base version numbers don't 54 | # change. 55 | include pkg.mk 56 | 57 | APP_VERSION = $(DCH_VERSION) 58 | MARKDOWN_PGM := pandoc 59 | 60 | vsn: 61 | @if [ ! -f APP_VERSION ]; then echo $(APP_VERSION) > APP_VERSION; fi 62 | 63 | help: 64 | @echo 65 | @echo 'Usage: make [REBAR_PROFILE=]' 66 | @echo 67 | @echo 'This Makefile uses rebar3 profiles.' 68 | @echo 'Omitting a profile uses the "default" profile.' 69 | @echo 'Run "make profiles" to see which profiles are available.' 70 | 71 | profiles: 72 | @erl -noinput \ 73 | -eval \ 74 | '{ok,C}=file:consult("rebar.config"),Ps=[P||{P,_}<-proplists:get_value(profiles,C)],io:format("Profiles: ~p~n",[Ps]),init:stop().' 75 | 76 | info: 77 | @echo PKG_VERSION=$(PKG_VERSION) 78 | @echo APP_VERSION=$(APP_VERSION) 79 | @echo MARKDOWN_PGM=$(call get_prog,MARKDOWN_PGM) 80 | @echo DCH_COMMENT=$(DCH_COMMENT) 81 | @echo EXTRA_VERSION=$(EXTRA_VERSION) 82 | 83 | compile: $(REBAR) 84 | @$(REBAR) do clean, compile 85 | 86 | # The idea here is to generate a set of fake certs, copy them to the 87 | # simulators, run the simulators in the background, and start SCPF 88 | # to point to the simulators so that it can run without needing real 89 | # certs or an internet connection. 90 | run: $(REBAR) 91 | $(REBAR) as shell do shell --name scpf@127.0.0.1 --setcookie scpf 92 | 93 | dev_rel: $(REBAR) manpage 94 | @echo Building version $(APP_VERSION) 95 | @$(REBAR) as dev do clean, release 96 | 97 | prod_rel: $(REBAR) manpage 98 | @echo Building version $(APP_VERSION) 99 | @$(REBAR) as prod do clean, release 100 | 101 | rel: $(REBAR) manpage 102 | @echo Building version $(APP_VERSION) 103 | @$(REBAR) do clean, release 104 | 105 | tar: $(REBAR) manpage 106 | @$(REBAR) do clean, tar 107 | 108 | ct: $(REBAR) 109 | $(REBAR) do clean, ct --readable 110 | 111 | dialyzer: $(REBAR) 112 | @$(REBAR) dialyzer 113 | 114 | doc: $(REBAR) vsn compile manpage 115 | $(REBAR) edoc # EDOWN_TARGET=github|stash EDOWN_TOP_LEVEL_README_URL=http://example.com/scpf/README.md 116 | 117 | manpage: doc/man/scpf.1 118 | 119 | doc/man/scpf.1: doc/man/README doc/man/scpf.1.template 120 | # Capitalize the headings before passing to pandoc 121 | awk '/^# / { print toupper($$0); next } { print }' $< | \ 122 | pandoc -t man -s \ 123 | --data-dir=$(CURDIR) \ 124 | --template doc/man/scpf.1.template \ 125 | --variable version="scpf $(PKG_VERSION)" \ 126 | --variable date="$(shell date -u)" \ 127 | -o $@ 128 | 129 | clean: $(REBAR) docclean 130 | @$(REBAR) clean 131 | @rm -f *.crashdump 132 | 133 | relclean: clean pkgclean 134 | @rm -rf _build/$(REBAR_PROFILE)/rel 135 | 136 | distclean: clean pkgclean 137 | @rm -rf _build log logs ebin .test .rebar certs 138 | @rm -f test/*.beam 139 | @rm -rf Mnesia.*/ 140 | @rm -rf tools/ 141 | @rm -f *.log 142 | 143 | docclean: 144 | @rm -f APP_VERSION 145 | @rm -rf doc/man/scpf.1 146 | 147 | install: prod_rel 148 | @if [ ! -d $(PROD_REL_DIR) ]; then \ 149 | echo "Production release directory missing, aborting"; \ 150 | exit 1; \ 151 | fi 152 | echo "INSTALL_DIR=$(DESTDIR)" > INSTALL_DIR 153 | mkdir -p $(PKG_LIB_DIR) 154 | mkdir -p $(PKG_ETC_DIR) 155 | mkdir -p $(PKG_ETC_DIR)/certs 156 | chmod 0755 $(PKG_ETC_DIR)/certs 157 | cp -R $(PROD_REL_DIR)/lib $(PKG_LIB_DIR) 158 | cp -R $(PROD_REL_DIR)/bin $(PKG_LIB_DIR) 159 | cp -R $(PROD_REL_DIR)/releases $(PKG_LIB_DIR) 160 | cp -R $(PROD_REL_DIR)/$(ERTS_VSN) $(PKG_LIB_DIR)/ 161 | chmod 0755 $(PKG_LIB_DIR)/$(ERTS_VSN)/bin/* 162 | chmod 0755 $(PKG_LIB_DIR)/bin/nodetool 163 | chmod 0755 $(PKG_LIB_DIR)/bin/$(PACKAGE)* 164 | install -m644 MANIFEST $(PKG_LIB_DIR)/MANIFEST.txt 165 | install -m644 INSTALL_DIR $(PKG_LIB_DIR)/releases/$(REL_VSN)/INSTALL_DIR 166 | install -m644 $(PROD_REL_DIR)/bin/start_clean.boot $(PKG_LIB_DIR)/releases/$(REL_VSN)/start_clean.boot 167 | install -m640 $(PROD_REL_DIR)/releases/$(REL_VSN)/sys.config $(PKG_ETC_DIR)/sys.config 168 | install -m640 $(PROD_REL_DIR)/releases/$(REL_VSN)/vm.args $(PKG_ETC_DIR)/vm.args 169 | 170 | # Build unsigned debian package 171 | dev_package: pkgclean 172 | BUILDDIR=$$(mktemp -d /tmp/$(PACKAGE)_build.XXXXXXXXXX) && \ 173 | (mkdir -p $$BUILDDIR/$(PACKAGE); \ 174 | cp -Rp . $$BUILDDIR/$(PACKAGE)/; \ 175 | export DEBFULLNAME="$$(git config --get user.name)"; \ 176 | export DEBEMAIL="$$(git config --get user.email)"; \ 177 | dch --noquery -c $$BUILDDIR/$(PACKAGE)/debian/changelog \ 178 | --force-distribution \ 179 | --distribution experimental \ 180 | -b -v "$(DCH_VERSION)" "Developer build"; \ 181 | cd $$BUILDDIR/$(PACKAGE) && \ 182 | dpkg-buildpackage -d -us -uc && cd -; \ 183 | mkdir -p $(DISTDIR); \ 184 | cp -Rp $$BUILDDIR/* $(DISTDIR)/) 185 | 186 | pkgclean: 187 | @dh_clean 188 | @rm -rf $(DISTDIR) 189 | 190 | $(REBAR): 191 | curl -s -Lo rebar3 $(REBAR3_URL) || wget $(REBAR3_URL) 192 | chmod a+x $(REBAR) 193 | 194 | # vim: set filetype=make syntax=make noet ts=4 sts=4 sw=4 si: 195 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | doc/README.md -------------------------------------------------------------------------------- /checks.mk: -------------------------------------------------------------------------------- 1 | .PHONY: check_dirty check_tags manifest 2 | 3 | include util.mk 4 | 5 | GIT_PROG=git 6 | GIT=$(call get_prog,GIT_PROG) 7 | GIT_MOD_CMD = diff --quiet 8 | GIT_UC_CMD = diff --cached --quiet 9 | 10 | manifest: 11 | @if [ -d _build/$(REBAR_PROFILE)/lib ]; then \ 12 | { echo $(pwd)/.git; find -L _build/$(REBAR_PROFILE)/lib -type d -name ".git"; } | \ 13 | while read gd; do \ 14 | if [ -d $$gd ]; then \ 15 | wd="$$(dirname $$gd)"; \ 16 | app="$$(basename $$wd)"; \ 17 | echo "$$app $$($(GIT) --git-dir="$$gd" --work-tree="$$wd" describe --long --always)"; \ 18 | fi; \ 19 | done | sort; \ 20 | fi 21 | 22 | check_dirty: 23 | @if [ -d _build/$(REBAR_PROFILE)/lib ]; then \ 24 | { echo $(pwd)/.git; find -L _build/$(REBAR_PROFILE)/lib -type d -name ".git"; } | \ 25 | while read gd; do \ 26 | if [ -d $$gd ]; then \ 27 | unset dirty; \ 28 | wt="$$(dirname $$gd)"; \ 29 | $(GIT) --git-dir="$$gd" --work-tree="$$wt" $(GIT_MOD_CMD) || dirty=y ; \ 30 | $(GIT) --git-dir="$$gd" --work-tree="$$wt" $(GIT_UC_CMD) || dirty=y; \ 31 | [ -z "$$dirty" ] || echo "$$wt"; \ 32 | fi; \ 33 | done; \ 34 | fi 35 | 36 | # Generates a list of git tag commands required to tag 37 | # repos under the current and deps directories with the contents of the 38 | # APP_VERSION file. It skips directories that don't contain 39 | # APP_VERSION. It doesn't actually do the tag or the push, 40 | # but something like this would help: 41 | # 42 | # make check_tags | sh 43 | # 44 | # and then push them up individually. 45 | check_tags: 46 | @{ if [ -d "./.git" ]; then echo "./.git"; fi; \ 47 | if [ -d _build/$(REBAR_PROFILE)/lib ]; then \ 48 | find -L _build/$(REBAR_PROFILE)/lib -maxdepth 2 -type d -name '.git'; \ 49 | fi; \ 50 | } | \ 51 | while read gd; do \ 52 | wt="$$(dirname $$gd)"; \ 53 | if [ -r "$$wt/APP_VERSION" ]; then \ 54 | newtag=v$$(cat "$$wt/APP_VERSION"); \ 55 | tags=$$($(GIT) --git-dir="$$gd" --work-tree="$$wt" tag -l); \ 56 | unset skip_tags; \ 57 | for t in $$tags; do \ 58 | if [ "$$t" = "$$newtag" ]; then \ 59 | skip_tags=1; \ 60 | break; \ 61 | fi; \ 62 | done; \ 63 | if [ -z "$$skip_tags" ]; then \ 64 | echo "$(GIT) --git-dir='$$gd' --work-tree='$$wt' tag -am'$$newtag' '$$newtag'"; \ 65 | else \ 66 | echo "# [$$wt] '$$newtag' is already present - skipping"; \ 67 | fi; \ 68 | fi; \ 69 | done 70 | 71 | 72 | -------------------------------------------------------------------------------- /config/dev.sys.config: -------------------------------------------------------------------------------- 1 | %% See overlay/dev.vars.config 2 | [ 3 | %% SASL config 4 | {sasl, 5 | [ 6 | {sasl_error_logger, {file, "{{sasl_error_log}}"}}, 7 | {errlog_type, error}, 8 | {error_logger_mf_dir, "{{sasl_log_dir}}"}, % Log directory 9 | {error_logger_mf_maxbytes, 10485760}, % 10 MB max file size 10 | {error_logger_mf_maxfiles, 5} % 5 files max 11 | ]}, 12 | 13 | {sc_push, 14 | [ 15 | {services, 16 | [ 17 | [ 18 | {name, 'null'}, 19 | {mod, 'sc_push_svc_null'}, 20 | {description, "Null Push Service"}, 21 | {sessions, [ 22 | [ 23 | {name, 'null-com.silentcircle.NullService'}, 24 | {mod, sc_push_svc_null_srv}, 25 | {config, []} 26 | ] 27 | ]} 28 | ] 29 | ]} 30 | ]}, 31 | 32 | {sc_push_lib, 33 | [ 34 | {db_pools, 35 | [ 36 | {sc_push_reg_pool, % name 37 | [ % sizeargs 38 | {size, 10}, 39 | {max_overflow, 20} 40 | ], 41 | [ % workerargs 42 | {db_mod, sc_push_reg_db_postgres}, 43 | {db_config, #{connection => [ 44 | {host, "localhost"}, 45 | {database, "sc_push_lib_test"}, 46 | {username, "sc_push_lib_test"}, 47 | {password, "test"} 48 | ], 49 | table_config => [ 50 | {table_schema, "scpf"}, 51 | {table_name, "push_tokens"} 52 | ] 53 | }} 54 | ]} 55 | ]} 56 | ]}, 57 | 58 | 59 | {apns_erlv3, [ 60 | {service, [ 61 | {name, apnsv3}, 62 | {mod, sc_push_svc_apnsv3}, 63 | {description, "APNS HTTP/2 Push Service"} 64 | ]}, 65 | 66 | {sessions, []} 67 | ]}, 68 | 69 | {gcm_erl, [ 70 | {service, [ 71 | {name, gcm}, 72 | {mod, sc_push_svc_gcm}, 73 | {description, "Google Cloud Messaging Service"} 74 | ]}, 75 | 76 | {sessions, []} 77 | ]}, 78 | 79 | {lager, [ 80 | %% What handlers to install with what arguments 81 | {handlers, [ 82 | {lager_console_backend, {{ scpf_log_level }} }, 83 | {lager_file_backend, 84 | [{file, "{{ scpf_log_dir }}/error.log"}, 85 | {level, error}, 86 | {size, 10485760}, 87 | {date, "$D0"}, 88 | {count, 5}]}, 89 | {lager_file_backend, 90 | [{file, "{{ scpf_log_dir }}/console.log"}, 91 | {level, {{ scpf_log_level }} }, 92 | {size, 10485760}, 93 | {date, "$D0"}, 94 | {count, 5}]} 95 | ]}, 96 | %% Whether to write a crash log, and where. Undefined means no crash logger. 97 | {crash_log, "{{ scpf_log_dir }}/crash.log"}, 98 | %% Maximum size in bytes of events in the crash log - defaults to 65536 99 | {crash_log_msg_size, 65536}, 100 | %% Maximum size of the crash log in bytes, before its rotated, set 101 | %% to 0 to disable rotation - default is 0 102 | {crash_log_size, 10485760}, 103 | %% What time to rotate the crash log - default is no time 104 | %% rotation. See the README for a description of this format. 105 | {crash_log_date, "$D0"}, 106 | %% Number of rotated crash logs to keep, 0 means keep only the 107 | %% current one - default is 0 108 | {crash_log_count, 5}, 109 | %% Whether to redirect error_logger messages into lager - defaults to true 110 | {error_logger_redirect, true} 111 | ]} 112 | ]. 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /config/dev.vm.args: -------------------------------------------------------------------------------- 1 | ## See overlay/dev.vars.config 2 | 3 | ## Name of the node 4 | -name {{ node }} 5 | 6 | ## Cookie for distributed erlang 7 | -setcookie scpf 8 | 9 | ## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive 10 | ## (Disabled by default..use with caution!) 11 | ##-heart 12 | 13 | ## Enable kernel poll and a few async threads 14 | +K true 15 | +A 5 16 | 17 | ## Treat error_logger warnings as warnings 18 | +W w 19 | 20 | ## Increase number of concurrent ports/sockets 21 | -env ERL_MAX_PORTS 4096 22 | 23 | -env WEBMACHINE_IP :: 24 | -env WEBMACHINE_PORT 8765 25 | 26 | ## Tweak GC to run more often 27 | ##-env ERL_FULLSWEEP_AFTER 10 28 | 29 | ## Set firewall window 30 | -kernel inet_dist_listen_min 40000 inet_dist_listen_max 40999 31 | -mnesia dir '"{{ mnesia_dir }}"' 32 | -------------------------------------------------------------------------------- /config/shell.config: -------------------------------------------------------------------------------- 1 | [ 2 | %% SASL config 3 | {sasl, 4 | [ 5 | {sasl_error_logger, {file, "sasl_error.log"}}, 6 | {errlog_type, error}, 7 | {error_logger_mf_dir, "log/sasl"}, % Log directory 8 | {error_logger_mf_maxbytes, 10485760}, % 10 MB max file size 9 | {error_logger_mf_maxfiles, 5} % 5 files max 10 | ]}, 11 | 12 | {sc_push, 13 | [ 14 | {services, 15 | [ 16 | [ 17 | {name, 'null'}, 18 | {mod, 'sc_push_svc_null'}, 19 | {description, "Null Push Service"}, 20 | {sessions, 21 | [ 22 | [ 23 | {name, 'null-com.silentcircle.NullService'}, 24 | {mod, sc_push_svc_null_srv}, 25 | {config, []} 26 | ] % null-com.silentcircle.NullService 27 | ] 28 | } % sessions 29 | ] % sc_push_svc_null 30 | ] % services props 31 | } % services 32 | ] % sc_push props 33 | }, 34 | 35 | {apns_erlv3, 36 | [ 37 | {service, 38 | [ 39 | {name, apnsv3}, 40 | {mod, sc_push_svc_apns}, 41 | {description, "APNS Push Service"} 42 | ] 43 | }, 44 | 45 | {sessions, []} % sessions 46 | ] 47 | }, % apns_erlv3 48 | 49 | {gcm_erl, 50 | [ 51 | {service, 52 | [ 53 | {name, gcm}, 54 | {mod, sc_push_svc_gcm}, 55 | {description, "Google Cloud Messaging Service"} 56 | ] 57 | }, 58 | 59 | {sessions, []} 60 | ] 61 | }, 62 | 63 | {lager, 64 | [ 65 | %% What handlers to install with what arguments 66 | {handlers, 67 | [ 68 | {lager_console_backend, debug}, 69 | {lager_file_backend, [{file, "log/error.log"}, 70 | {level, error}, 71 | {size, 10485760}, 72 | {date, "$D0"}, 73 | {count, 5}]}, 74 | {lager_file_backend, [{file, "log/console.log"}, 75 | {level, debug }, 76 | {size, 10485760}, 77 | {date, "$D0"}, 78 | {count, 5} 79 | ] 80 | } 81 | ] 82 | }, 83 | %% Whether to write a crash log, and where. Undefined means no crash logger. 84 | {crash_log, "log/crash.log"}, 85 | %% Maximum size in bytes of events in the crash log - defaults to 65536 86 | {crash_log_msg_size, 65536}, 87 | %% Maximum size of the crash log in bytes, before its rotated, set 88 | %% to 0 to disable rotation - default is 0 89 | {crash_log_size, 10485760}, 90 | %% What time to rotate the crash log - default is no time 91 | %% rotation. See the README for a description of this format. 92 | {crash_log_date, "$D0"}, 93 | %% Number of rotated crash logs to keep, 0 means keep only the 94 | %% current one - default is 0 95 | {crash_log_count, 5}, 96 | %% Whether to redirect error_logger messages into lager - defaults to true 97 | {error_logger_redirect, true} 98 | ] 99 | } 100 | ]. 101 | 102 | % vim: set filetype=erlang syntax=erlang et ts=4 sts=4 sw=4 si: 103 | -------------------------------------------------------------------------------- /config/sys.config: -------------------------------------------------------------------------------- 1 | %% See overlay/vars.config 2 | [ 3 | %% SASL config 4 | {sasl, 5 | [ 6 | {sasl_error_logger, {file, "{{sasl_error_log}}"}}, 7 | {errlog_type, error}, 8 | {error_logger_mf_dir, "{{sasl_log_dir}}"}, % Log directory 9 | {error_logger_mf_maxbytes, 10485760}, % 10 MB max file size 10 | {error_logger_mf_maxfiles, 5} % 5 files max 11 | ]}, 12 | 13 | {sc_push, 14 | [ 15 | {services, 16 | [ 17 | [ 18 | {name, 'null'}, 19 | {mod, 'sc_push_svc_null'}, 20 | {description, "Null Push Service"}, 21 | {sessions, [ 22 | [ 23 | {name, 'null-com.silentcircle.NullService'}, 24 | {mod, sc_push_svc_null_srv}, 25 | {config, []} 26 | ] 27 | ]} 28 | ] 29 | ]} 30 | ]}, 31 | 32 | {sc_push_lib, 33 | [ 34 | {db_pools, 35 | [ 36 | {sc_push_reg_pool, % name 37 | [ % sizeargs 38 | {size, 50}, 39 | {max_overflow, 0} 40 | ], 41 | [ % workerargs 42 | {db_mod, sc_push_reg_db_postgres}, 43 | {db_config, #{connection => [ 44 | {host, "localhost"}, 45 | {database, "sc_push_lib_test"}, 46 | {username, "sc_push_lib_test"}, 47 | {password, "test"} 48 | ], 49 | table_config => [ 50 | {table_schema, "scpf"}, 51 | {table_name, "push_tokens"} 52 | ] 53 | }} 54 | ]} 55 | ]} 56 | ]}, 57 | 58 | {apns_erlv3, [ 59 | {service, [ 60 | {name, apnsv3}, 61 | {mod, sc_push_svc_apnsv3}, 62 | {description, "APNS HTTP/2 Push Service"} 63 | ]}, 64 | 65 | {sessions, []} 66 | ]}, 67 | 68 | {gcm_erl, [ 69 | {service, [ 70 | {name, gcm}, 71 | {mod, sc_push_svc_gcm}, 72 | {description, "Google Cloud Messaging Service"} 73 | ]}, 74 | 75 | {sessions, []} 76 | ]}, 77 | 78 | %% Sentry credentials - this must be configured with suitable settings 79 | %% {raven_erlang, [ 80 | %% {dsn, "https://{{ scpf_sentry_public_key }}:{{ scpf_sentry_private_key }}@app.getsentry.com/{{ scpf_sentry_project }}"}, 81 | %% {uri, "https://app.getsentry.com"}, 82 | %% {project, "{{ scpf_sentry_project }}"}, 83 | %% {public_key, "{{ scpf_sentry_public_key }}"}, 84 | %% {private_key, "{{ scpf_sentry_private_key }}"}, 85 | %% {error_logger, '{{ scpf_sentry_error_logger }}' }, 86 | %% {ipfamily, '{{ scpf_sentry_ipfamily }}' } 87 | %% ]}, 88 | 89 | {lager, [ 90 | %% What handlers to install with what arguments 91 | {handlers, 92 | [ 93 | %% Raven/Sentry integration 94 | %% {raven_lager_backend, '{{ scpf_sentry_log_level }}'}, 95 | %% Console 96 | {lager_console_backend, '{{ scpf_log_level }}' }, 97 | %% Error log file 98 | {lager_file_backend, 99 | [{file, "{{ scpf_log_dir }}/error.log"}, 100 | {level, 'error'}, 101 | {size, 10485760}, 102 | {date, "$D0"}, 103 | {count, 5}]}, 104 | %% Console log file 105 | {lager_file_backend, 106 | [{file, "{{ scpf_log_dir }}/console.log"}, 107 | {level, '{{ scpf_log_level }}'}, 108 | {size, 10485760}, 109 | {date, "$D0"}, 110 | {count, 5}]} 111 | ]}, 112 | %% Whether to write a crash log, and where. Undefined means no crash logger. 113 | {crash_log, "{{ scpf_log_dir }}/crash.log"}, 114 | %% Maximum size in bytes of events in the crash log - defaults to 65536 115 | {crash_log_msg_size, 65536}, 116 | %% Maximum size of the crash log in bytes, before its rotated, set 117 | %% to 0 to disable rotation - default is 0 118 | {crash_log_size, 10485760}, 119 | %% What time to rotate the crash log - default is no time 120 | %% rotation. See the README for a description of this format. 121 | {crash_log_date, "$D0"}, 122 | %% Number of rotated crash logs to keep, 0 means keep only the 123 | %% current one - default is 0 124 | {crash_log_count, 5}, 125 | %% Whether to redirect error_logger messages into lager - defaults to true 126 | {error_logger_redirect, true} 127 | ]} 128 | ]. 129 | 130 | 131 | -------------------------------------------------------------------------------- /config/vm.args: -------------------------------------------------------------------------------- 1 | ## See overlay/vars.config 2 | 3 | ## Name of the node 4 | -name {{ node }} 5 | 6 | ## Cookie for distributed erlang 7 | -setcookie {{release_name}} 8 | 9 | ## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive 10 | ## (Disabled by default..use with caution!) 11 | ##-heart 12 | 13 | ## Enable kernel poll and a few async threads 14 | +K true 15 | +A 10 16 | 17 | ## Treat error_logger warnings as warnings 18 | +W w 19 | 20 | ## Increase number of concurrent ports/sockets 21 | -env ERL_MAX_PORTS 4096 22 | 23 | -env WEBMACHINE_IP 0.0.0.0 24 | -env WEBMACHINE_PORT 8765 25 | 26 | ## Tweak GC to run more often 27 | ##-env ERL_FULLSWEEP_AFTER 10 28 | 29 | ## Set firewall window 30 | -kernel inet_dist_listen_min 40000 inet_dist_listen_max 40999 31 | -mnesia dir '"{{ mnesia_dir }}"' 32 | -------------------------------------------------------------------------------- /debian/Makefile: -------------------------------------------------------------------------------- 1 | BUILDPATH = debuild/$(APP)-$(PKG_VERSION) 2 | 3 | build: $(BUILDPATH)/debian \ 4 | debuild/$(APP)_$(PKG_VERSION).orig.tar.gz 5 | export DEBFULLNAME="Me Myself and I"; \ 6 | export DEBEMAIL="support@example.com"; \ 7 | dch --noquery -c $(BUILDPATH)/debian/changelog \ 8 | -b -v "$(PKG_VERSION)-$(RELEASE)+$(DISTRO)" "Put some meaningful comment here" 9 | cd $(BUILDPATH) && debuild --no-lintian \ 10 | --prepend-path=$(ERLANG_BIN) \ 11 | -e REVISION="$(PKG_VERSION)" \ 12 | -e RELEASE="$(RELEASE)" \ 13 | -e VERSIONSTRING="$(VERSIONSTRING)" \ 14 | -uc -us 15 | mkdir -p packages 16 | mv debuild/$(APP)_$(PKG_VERSION)-$(RELEASE)+$(DISTRO)_$(DEBARCH).deb packages/$(APP)_$(PKG_VERSION)-$(RELEASE)_$(DEBARCH).deb 17 | cd packages && \ 18 | for debfile in `ls *.deb`; do \ 19 | sha256sum $${debfile} > $${debfile}.sha \ 20 | ; done 21 | 22 | $(BUILDPATH): $(APP)-$(PKG_VERSION).tar.gz 23 | mkdir -p debuild 24 | tar xz -C debuild -f $^ 25 | 26 | $(BUILDPATH)/debian: $(BUILDPATH) 27 | cp -a $(PKGERDIR) $@ 28 | rm -rf $@/.git $@/Makefile $@/.*.swp 29 | 30 | debuild/$(APP)_$(PKG_VERSION).orig.tar.gz: $(APP)-$(PKG_VERSION).tar.gz 31 | cp $^ $@ 32 | 33 | $(PKGERDIR)/pkgclean: 34 | @echo 35 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | scpf (2.0.4~dev4) unstable; urgency=medium 2 | 3 | * Version 2.0.4 (dev4) 4 | * Fix Postgres-related doc, tests, config: "hostname: should be "host" 5 | 6 | -- Edwin Fine Thu, 13 Jul 2017 11:20:16 -0400 7 | 8 | scpf (2.0.4~dev3) unstable; urgency=medium 9 | 10 | * Version 2.0.4 (dev3) 11 | * Support user-defined Postgres push token table name and schema. 12 | 13 | -- Edwin Fine Wed, 12 Jul 2017 17:46:58 -0400 14 | 15 | scpf (2.0.4~dev2) unstable; urgency=medium 16 | 17 | * Version 2.0.4 (dev2) 18 | 19 | * Remove unnecessary Erlang dependencies from Debian install. 20 | * Fix default config and other files to support 'make run'. 21 | * Support Postgres 22 | - Update rebar.lock to use postgres-supporting commits in sc_push, 23 | sc_push_lib, and apns_erlv3 repos. 24 | - Remove apns_erl (old APNS binary module) from app. 25 | - Add raven_erlang. 26 | - Add runtime_tools. This allows us to run observer on another node and 27 | connect to scpf. 28 | - Upgrade to rebar3 3.4.1. 29 | - Modify test suite to test both Mnesia and Postgres. 30 | - Fix some issues related to the test suite and CT in general. 31 | - Modify config files to reflect Postgres and pool settings. 32 | - Update docs. 33 | 34 | -- Edwin Fine Tue, 11 Jul 2017 23:32:42 -0400 35 | 36 | scpf (2.0.3) stable; urgency=medium 37 | 38 | * Version 2.0.3 39 | 40 | * Improve performance under load 41 | - Use SilentCircle chatterbox ETS table mod that addresses performance 42 | and unbounded memory growth 43 | - Decrease default burst size to 50 44 | - Decrease default burst interval to 50 ms 45 | - Update documentation to reflect changes 46 | - Rewrite parts of apns_erlv3_session.erl to handle load and errors 47 | better 48 | - Modify CT tests to do a better job of flood testing (add 49 | sync_flood_and_disconnect test) 50 | - Modify CT tests to work with JWT auths, together with apns_erl_sim JWT 51 | support and apns_tools JWT token generation 52 | - Fix dialyzer warnings 53 | - Various other small improvements 54 | * Fix recovery when connection is broken. 55 | 56 | -- Edwin Fine Sat, 22 Apr 2017 15:53:18 -0400 57 | 58 | scpf (2.0.2) unstable; urgency=medium 59 | 60 | * Version 2.0.2 61 | 62 | * Fix APNS HTTP/2 500 errors due to idle session or expired token. 63 | 64 | APNS was returning 500 Internal Server Error for no apparent reason. On 65 | investigation it appears to be due to a session that has been idle for 66 | too long, or possibly because the APNS auth token (JWT) has expired. 67 | 68 | This commit adds the following behaviors: 69 | 70 | 1. The session sends an HTTP/2 PING frame periodically (default: 5 71 | minutes). 72 | 2. Whenever it sends a notification, the session first checks that the 73 | APNS authentication JWT is newer - based on its 'Issued At (`iat`) 74 | field - than a configurable value (default: 55 minutes). If it is not 75 | newer, the session generates a new JWT. 76 | 77 | -- Edwin Fine Mon, 19 Dec 2016 23:14:30 -0500 78 | 79 | scpf (2.0.1) unstable; urgency=medium 80 | 81 | * Version 2.0.1 82 | 83 | * Support Provider Authentication Tokens (JWTs) 84 | 85 | -- Edwin Fine Mon, 05 Dec 2016 16:58:00 -0500 86 | 87 | scpf (2.0.0) unstable; urgency=medium 88 | 89 | * Version 2.0.0 90 | 91 | * Add support for APNS HTTP/2. 92 | * Add support for Sentry. 93 | * Add some library support for JWT, but it's not ready for use with 94 | APNS HTTP/2. That will come in a future release. 95 | * Deprecate old APNS session (apns_erl) in favor of apns_erlv3. 96 | apns_erl (binary APNS interface) probably doesn't even work any more. 97 | * Add genuine async push support (not just cast to gen_xxx). 98 | * Do major refactoring. 99 | * Redo most test cases to use new APNS and improved GCM simulators. 100 | * Change APIs (breaking changes for push). 101 | * Add and update significant amount of documentation. 102 | * Make ready for open source by adding headers, copyrights, and licences. 103 | Remove, change, or sanitize SC-specific information and references. 104 | * Move repositories to public github. 105 | 106 | -- Edwin Fine Fri, 02 Dec 2016 18:44:43 -0500 107 | 108 | scpf (1.2.0) unstable; urgency=medium 109 | 110 | * Version 1.2.0 111 | 112 | * Initial open source commit. 113 | 114 | -- Edwin Fine Sun, 13 Nov 2016 13:54:53 -0500 115 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: scpf 2 | Section: net 3 | Priority: extra 4 | Maintainer: Edwin Fine 5 | Build-Depends: 6 | curl, 7 | dh-systemd (>= 1.5), 8 | debhelper (>= 9), 9 | quilt, 10 | erlang (>= 1:18.3~), 11 | erlang-dev (>= 1:18.3~), 12 | git (>= 1.7~), 13 | pandoc (>= 1.12.4~) 14 | Standards-Version: 3.9.5 15 | Homepage: https://silentcircle.com/ 16 | 17 | Package: scpf 18 | Architecture: any 19 | Depends: 20 | adduser, 21 | logrotate, 22 | sudo, 23 | ${erlang-base:Depends}, 24 | ${erlang-asn1:Depends}, 25 | ${erlang-crypto:Depends}, 26 | ${erlang-inets:Depends}, 27 | ${erlang-mnesia:Depends}, 28 | ${erlang-public-key:Depends}, 29 | ${erlang-ssl:Depends}, 30 | ${erlang-syntax-tools:Depends}, 31 | ${erlang-xmerl:Depends}, 32 | ${shlibs:Depends}, 33 | ${misc:Depends} 34 | Description: scpf is the Silent Circle Push Framework application, written in Erlang. 35 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | This package was debianized by Edwin Fine 2 | Tue Dec 18 17:40:05 EDT 2012 3 | 4 | It was downloaded from 5 | 6 | Upstream Author(s): 7 | 8 | 9 | Copyright: 10 | 2012 Silent Circle LLC 11 | 12 | License: 13 | Licensed under the Apache License, Version 2.0 (the "License"); 14 | you may not use this file except in compliance with the License. 15 | You may obtain a copy of the License at 16 | 17 | http://www.apache.org/licenses/LICENSE-2.0 18 | 19 | Unless required by applicable law or agreed to in writing, software 20 | distributed under the License is distributed on an "AS IS" BASIS, 21 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | See the License for the specific language governing permissions and 23 | limitations under the License. 24 | 25 | The Debian packaging is: 26 | 27 | 2012 Silent Circle LLC 28 | 29 | and is licensed under the Apache License, Version 2.0 30 | 31 | # Please also look if there are files or directories which have a 32 | # different copyright/license attached and list them here. 33 | -------------------------------------------------------------------------------- /debian/dirs: -------------------------------------------------------------------------------- 1 | etc/scpf 2 | etc/scpf/certs 3 | etc/logrotate.d 4 | usr/lib/scpf 5 | usr/lib/scpf/bin 6 | usr/sbin 7 | usr/share/man/man1 8 | var/run/scpf 9 | var/lib/scpf 10 | var/log/scpf 11 | var/log/scpf/sasl 12 | -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # postinst script for scpf 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | # create scpf group 9 | if ! getent group scpf >/dev/null; then 10 | addgroup --system scpf 11 | fi 12 | 13 | # create scpf user 14 | if ! getent passwd scpf >/dev/null; then 15 | adduser --ingroup scpf --home /var/lib/scpf --disabled-password \ 16 | --system --shell /bin/bash --no-create-home \ 17 | --gecos "scpf Provider" scpf 18 | fi 19 | 20 | chown -R scpf:scpf /var/lib/scpf 21 | chown -R scpf:scpf /var/run/scpf 22 | chown -R scpf:scpf /var/log/scpf 23 | chown -R scpf:scpf /etc/scpf/certs 24 | chmod 0755 /var/run/scpf /etc/scpf 25 | chmod 0755 /etc/scpf/certs 26 | 27 | # scpf.links and dh_link don't do what we want 28 | #rm -f /usr/sbin/scpf 29 | #ln -sf /usr/lib/scpf/bin/scpf /usr/sbin/scpf 30 | 31 | case "$1" in 32 | configure) 33 | ;; 34 | 35 | abort-upgrade|abort-remove|abort-deconfigure) 36 | ;; 37 | 38 | *) 39 | echo "postinst called with unknown argument \`$1'" >&2 40 | exit 1 41 | ;; 42 | esac 43 | 44 | # dh_installdeb will replace this with shell code automatically 45 | # generated by other debhelper scripts. 46 | 47 | #DEBHELPER# 48 | 49 | exit 0 50 | 51 | -------------------------------------------------------------------------------- /debian/postrm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # postrm script for scpf 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | 7 | # summary of how this script can be called: 8 | # * `remove' 9 | # * `purge' 10 | # * `upgrade' 11 | # * `failed-upgrade' 12 | # * `abort-install' 13 | # * `abort-install' 14 | # * `abort-upgrade' 15 | # * `disappear' 16 | # 17 | # for details, see http://www.debian.org/doc/debian-policy/ or 18 | # the debian-policy package 19 | 20 | 21 | case "$1" in 22 | purge) 23 | rm -f /etc/default/scpf 24 | 25 | if [ -d /var/lib/scpf ]; then 26 | rm -r /var/lib/scpf 27 | fi 28 | if [ -d /var/log/scpf ]; then 29 | rm -r /var/log/scpf 30 | fi 31 | if [ -d /var/run/scpf ]; then 32 | rm -r /var/run/scpf 33 | fi 34 | if [ -d /etc/scpf ]; then 35 | rm -r /etc/scpf 36 | fi 37 | if [ -e /usr/sbin/scpf ]; then 38 | rm /usr/sbin/scpf 39 | fi 40 | if [ -e /usr/sbin/scpf-admin ]; then 41 | rm /usr/sbin/scpf-admin 42 | fi 43 | if [ -e /usr/sbin/search-cmd ]; then 44 | rm /usr/sbin/search-cmd 45 | fi 46 | if getent passwd scpf >/dev/null; then 47 | deluser scpf 48 | fi 49 | if getent group scpf >/dev/null; then 50 | delgroup scpf 51 | fi 52 | ;; 53 | 54 | remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) 55 | ;; 56 | 57 | *) 58 | echo "postrm called with unknown argument \`$1\`" >&2 59 | exit 1 60 | ;; 61 | esac 62 | 63 | # dh_installdeb will replace this with shell code automatically 64 | # generated by other debhelper scripts. 65 | 66 | #DEBHELPER# 67 | 68 | exit 0 69 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | # Sample debian/rules that uses debhelper. 4 | # This file was originally written by Joey Hess and Craig Small. 5 | # As a special exception, when this file is copied by dh-make into a 6 | # dh-make output file, you may use that output file without restriction. 7 | # This special exception was added by Craig Small in version 0.37 of dh-make. 8 | 9 | # Uncomment this to turn on verbose mode. 10 | export DH_VERBOSE=1 11 | export REBAR_PROFILE=prod 12 | export SCPF_OVERRIDE_REL=prod 13 | 14 | package=scpf 15 | erts_vsn=$(shell ./erts-version) 16 | 17 | CFLAGS= 18 | LDFLAGS= 19 | 20 | %: 21 | dh $@ --with=systemd 22 | 23 | # Overriding because previous steps blow away 24 | # erlang entries in substvars. 25 | override_dh_gencontrol: 26 | erlang-depends 27 | dh_gencontrol 28 | 29 | # Commands not to run: 30 | override_dh_auto_build: 31 | dh_testroot 32 | dh_prep 33 | dh_installdirs 34 | 35 | override_dh_auto_test: 36 | 37 | build: 38 | cp debian/scpf.service.src debian/scpf.service 39 | if test -f ./rebar3; then ./rebar3 report tar; true; else true; fi 40 | unset CC CFLAGS CPPFLAGS LDFLAGS CXX CXXFLAGS \ 41 | && $(MAKE) tar DEBUG=1 42 | touch build 43 | 44 | clean: 45 | rm -f build debian/scpf.service 46 | 47 | -------------------------------------------------------------------------------- /debian/scpf.links: -------------------------------------------------------------------------------- 1 | usr/lib/scpf/bin/scpf usr/sbin/scpf 2 | -------------------------------------------------------------------------------- /debian/scpf.manpages: -------------------------------------------------------------------------------- 1 | doc/man/scpf.1 2 | -------------------------------------------------------------------------------- /debian/scpf.service.src: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Erlang Silent Circle Push Framework application 3 | Documentation=man:scpf(1) 4 | Requires=epmd.socket 5 | After=network-online.target 6 | Wants=network-online.target 7 | 8 | [Service] 9 | WorkingDirectory=/var/lib/scpf 10 | User=scpf 11 | Group=scpf 12 | LimitNOFILE=16000 13 | RestartSec=5 14 | ExecStart=/usr/sbin/scpf foreground 15 | ExecStop=/usr/sbin/scpf stop 16 | Type=simple 17 | PIDFile=/var/run/scpf/scpf.pid 18 | Restart=on-failure 19 | 20 | [Install] 21 | WantedBy=multi-user.target 22 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /doc/apns_cert.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module apns_cert # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | APNS certificate utilities. 9 | 10 | Copyright (c) 2015-2016 Silent Circle 11 | 12 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 13 | 14 | 15 | 16 | ## Description ## 17 | This module provides functions to decode and 18 | validate APNS PEM and DER format certificates, given a Team ID 19 | and the AppID Suffix (e.g. com.example.FakeApp). 20 | See [`https://developer.apple.com`](https://developer.apple.com) for more information. 21 | 22 | ## Function Index ## 23 | 24 | 25 |
decode_cert/1Decode binary certificate data into an #'OTPCertificate'{} 26 | record.
der_decode_cert/1Decode DER binary into an #'OTPCertificate'{} record.
get_cert_info_map/1Extract more interesting APNS-related info from cert and 27 | return in a map.
pem_decode_certs/1Decode PEM binary into a list of #'OTPCertificate'{} records.
validate/3Validate that the TeamId and AppIdSuffix correspond to the 28 | certificate data CertData.
29 | 30 | 31 | 32 | 33 | ## Function Details ## 34 | 35 | 36 | 37 | ### decode_cert/1 ### 38 | 39 |

 40 | decode_cert(CertData) -> Result
 41 | 
42 | 43 |
  • CertData = binary()
  • Result = #'OTPCertificate'{} | {error, Reason::term()}
44 | 45 | Decode binary certificate data into an `#'OTPCertificate'{}` 46 | record. 47 | 48 | 49 | 50 | ### der_decode_cert/1 ### 51 | 52 |

 53 | der_decode_cert(DerData::binary()) -> #'OTPCertificate'{} | {error, Reason::term()}
 54 | 
55 |
56 | 57 | Decode DER binary into an #'OTPCertificate'{} record. 58 | 59 | 60 | 61 | ### get_cert_info_map/1 ### 62 | 63 |

 64 | get_cert_info_map(OTPCert) -> CertInfo
 65 | 
66 | 67 |
  • OTPCert = #'OTPCertificate'{}
  • CertInfo = #{}
68 | 69 | Extract more interesting APNS-related info from cert and 70 | return in a map. 71 | 72 | 73 | 74 | ### pem_decode_certs/1 ### 75 | 76 |

 77 | pem_decode_certs(PemData::binary()) -> [#'OTPCertificate'{}] | {error, Reason::term()}
 78 | 
79 |
80 | 81 | Decode PEM binary into a list of #'OTPCertificate'{} records. 82 | 83 | 84 | 85 | ### validate/3 ### 86 | 87 |

 88 | validate(CertData, AppIdSuffix, TeamID) -> Result
 89 | 
90 | 91 |
  • CertData = binary()
  • AppIdSuffix = binary()
  • TeamID = binary()
  • Result = ok | {error, Reason}
  • Reason = term()
92 | 93 | Validate that the `TeamId` and `AppIdSuffix` correspond to the 94 | certificate data `CertData`. `CertData` may be either PEM-encoded or 95 | DER-encoded. If PEM-encoded, only one certificate is permitted in the data. 96 | 97 | 98 | #### Cert Data #### 99 | 100 | Depending on whether or not the certificate is PEM or DER 101 | encoded, you could load it as follows: 102 | 103 | ``` 104 | {ok, PemData} = file:read_file("cert.pem"). 105 | {ok, DerData} = file:read_file("aps_developer.cer"). 106 | ``` 107 | 108 | 109 | #### Team ID #### 110 | 111 | The team ID will be a 10-character binary string, such as 112 | `<<"ABCDE12345">>`. This is obtained from the certificate's Subject OU 113 | field. 114 | 115 | 116 | #### AppID Suffix #### 117 | 118 | The AppID Suffix will be a binary string such as `<<"com.example.MyApp">>`. 119 | This is obtained from the certificate's Subject CN field. 120 | The caller is expected to supply the right AppID Suffix or the 121 | validation will fail. 122 | 123 | 124 | #### Issuer CN #### 125 | 126 | The Issuer CN is expected to be 127 | `Apple Worldwide Developer Relations Certification Authority` 128 | or the validation will fail. 129 | 130 | -------------------------------------------------------------------------------- /doc/apns_erlv3_app.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module apns_erlv3_app # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | __Behaviours:__ [`application`](application.md). 8 | 9 | 10 | 11 | ## Function Index ## 12 | 13 | 14 |
start/2Start the apns_erlv3 application.
stop/1Stop the apns_erlv3 application.
15 | 16 | 17 | 18 | 19 | ## Function Details ## 20 | 21 | 22 | 23 | ### start/2 ### 24 | 25 | `start(StartType, StartArgs) -> any()` 26 | 27 | Start the `apns_erlv3` application. 28 | 29 | 30 | 31 | ### stop/1 ### 32 | 33 | `stop(State) -> any()` 34 | 35 | Stop the `apns_erlv3` application. 36 | 37 | -------------------------------------------------------------------------------- /doc/apns_erlv3_session_sup.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module apns_erlv3_session_sup # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | __Behaviours:__ [`supervisor`](supervisor.md). 8 | 9 | 10 | 11 | ## Function Index ## 12 | 13 | 14 |
get_child_pid/1
init/1
is_child_alive/1
start_child/2Start a child session.
start_link/1Sessions looks like this:.
stop_child/1
15 | 16 | 17 | 18 | 19 | ## Function Details ## 20 | 21 | 22 | 23 | ### get_child_pid/1 ### 24 | 25 | `get_child_pid(Name) -> any()` 26 | 27 | 28 | 29 | ### init/1 ### 30 | 31 | `init(X1) -> any()` 32 | 33 | 34 | 35 | ### is_child_alive/1 ### 36 | 37 | `is_child_alive(Name) -> any()` 38 | 39 | 40 | 41 | ### start_child/2 ### 42 | 43 | `start_child(Name, Opts) -> any()` 44 | 45 | Start a child session. 46 | 47 | 48 | ### Parameters ### 49 | 50 | 51 | * `Name` - Session name (atom) 52 | 53 | * `Opts` - Options, see [`apns_erlv3_session`](apns_erlv3_session.md) for more details 54 | 55 | 56 | 57 | 58 | ### start_link/1 ### 59 | 60 | `start_link(Sessions) -> any()` 61 | 62 | `Sessions` looks like this: 63 | 64 | ``` 65 | [ 66 | [ 67 | {name, 'apns-com.example.MyApp'}, 68 | {config, [ 69 | {host, "api.push.apple.com" | "api.development.push.apple.com"}, 70 | {port, 443 | 2197}, 71 | {app_id_suffix, <<"com.example.MyApp">>}, 72 | {apns_env, prod}, 73 | {apns_topic, <<"com.example.MyApp">>}, 74 | {retry_delay, 1000}, 75 | {disable_apns_cert_validation, false}, 76 | {ssl_opts, [ 77 | {certfile, "/some/path/com.example.MyApp.cert.pem"}, 78 | {keyfile, "/some/path/com.example.MyApp.key.unencrypted.pem"}, 79 | {honor_cipher_order, false}, 80 | {versions, ['tlsv1.2']}, 81 | {alpn_preferred_protocols, [<<"h2">>]} 82 | ] 83 | } 84 | ]} 85 | ] %, ... 86 | ] 87 | ``` 88 | 89 | 90 | 91 | ### stop_child/1 ### 92 | 93 | `stop_child(Name) -> any()` 94 | 95 | -------------------------------------------------------------------------------- /doc/apns_lib.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module apns_lib # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | APNS wire-format encoding and decoding library. 10 | 11 | Copyright (c) 2015 Silent Circle LLC 12 | 13 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 14 | 15 | 16 | 17 | ## Description ## 18 | This supports the simple (0), enhanced (1), and "v2" (2) formats. 19 | 20 | 21 | ## Data Types ## 22 | 23 | 24 | 25 | 26 | ### apns_error() ### 27 | 28 | 29 |

 30 | apns_error() = term()
 31 | 
32 | 33 | 34 | 35 | 36 | ### apns_notification() ### 37 | 38 | 39 |

 40 | apns_notification() = term()
 41 | 
42 | 43 | 44 | 45 | 46 | ### apns_packet() ### 47 | 48 | 49 |

 50 | apns_packet() = binary()
 51 | 
52 | 53 | 54 | 55 | 56 | ### bytes() ### 57 | 58 | 59 |

 60 | bytes() = [byte()]
 61 | 
62 | 63 | 64 | 65 | 66 | ### decode_err_pkt_error() ### 67 | 68 | 69 |

 70 | decode_err_pkt_error() = {error, decode_err_pkt_reason()}
 71 | 
72 | 73 | 74 | 75 | 76 | ### decode_err_pkt_reason() ### 77 | 78 | 79 |

 80 | decode_err_pkt_reason() = bad_packet
 81 | 
82 | 83 | 84 | 85 | 86 | ### decode_error() ### 87 | 88 | 89 |

 90 | decode_error() = {error, decode_reason()}
 91 | 
92 | 93 | 94 | 95 | 96 | ### decode_reason() ### 97 | 98 | 99 |

100 | decode_reason() = bad_packet | buffer_too_short | bad_json
101 | 
102 | 103 | 104 | 105 | 106 | ### decoded_packet() ### 107 | 108 | 109 |

110 | decoded_packet() = {Timestamp::integer(), Token::binary()}
111 | 
112 | 113 | 114 | 115 | 116 | ### encode_error() ### 117 | 118 | 119 |

120 | encode_error() = {error, encode_reason()}
121 | 
122 | 123 | 124 | 125 | 126 | ### encode_reason() ### 127 | 128 | 129 |

130 | encode_reason() = bad_token | bad_json | payload_too_long
131 | 
132 | 133 | 134 | 135 | 136 | ### json() ### 137 | 138 | 139 |

140 | json() = string() | binary()
141 | 
142 | 143 | 144 | 145 | 146 | ### token() ### 147 | 148 | 149 |

150 | token() = string() | bytes() | binary()
151 | 
152 | 153 | 154 | 155 | ## Function Index ## 156 | 157 | 158 |
decode/1Decode an encoded APNS packet.
decode_error_packet/1Decode an error received from APNS.
decode_feedback_packet/1Decode a feedback packet received from APNS feedback service.
encode_enhanced/4Encode the Id, Expiry, Token and Payload into an 159 | "enhanced" (command 1) APNS packet.
encode_simple/2Encode Token and Payload into a "simple" (command 0) APNS 160 | packet.
encode_v2/5Encode into the command 3 APNS packet.
error_description/1Convert APNS error code to textual description (as binary 161 | string).
error_to_atom/1Convert APNS error code to symbolic name (an atom).
maybe_encode_token/1
162 | 163 | 164 | 165 | 166 | ## Function Details ## 167 | 168 | 169 | 170 | ### decode/1 ### 171 | 172 |

173 | decode(Packet) -> Result
174 | 
175 | 176 | 177 | 178 | Decode an encoded APNS packet. 179 | 180 | 181 | 182 | ### decode_error_packet/1 ### 183 | 184 |

185 | decode_error_packet(ErrPkt) -> Result
186 | 
187 | 188 | 189 | 190 | Decode an error received from APNS. 191 | 192 | 193 | 194 | ### decode_feedback_packet/1 ### 195 | 196 |

197 | decode_feedback_packet(Packet) -> Result
198 | 
199 | 200 | 201 | 202 | Decode a feedback packet received from APNS feedback service. 203 | 204 | 205 | 206 | ### encode_enhanced/4 ### 207 | 208 |

209 | encode_enhanced(Id, Expiry, Token, Payload) -> Result
210 | 
211 | 212 | 213 | 214 | Encode the `Id`, `Expiry`, `Token` and `Payload` into an 215 | "enhanced" (command 1) APNS packet. 216 | 217 | 218 | 219 | ### encode_simple/2 ### 220 | 221 |

222 | encode_simple(Token, Payload) -> Result
223 | 
224 | 225 | 226 | 227 | Encode `Token` and `Payload` into a "simple" (command 0) APNS 228 | packet. 229 | 230 | 231 | 232 | ### encode_v2/5 ### 233 | 234 |

235 | encode_v2(Id, Expiry, Token, Payload, Prio) -> Result
236 | 
237 | 238 | 239 | 240 | Encode into the command 3 APNS packet. 241 | 242 | 243 | 244 | ### error_description/1 ### 245 | 246 |

247 | error_description(Err) -> Desc
248 | 
249 | 250 |
  • Err = integer()
  • Desc = binary()
251 | 252 | Convert APNS error code to textual description (as binary 253 | string). 254 | 255 | 256 | 257 | ### error_to_atom/1 ### 258 | 259 |

260 | error_to_atom(Err) -> Atom
261 | 
262 | 263 |
  • Err = 0..255
  • Atom = atom()
264 | 265 | Convert APNS error code to symbolic name (an atom). 266 | 267 | 268 | 269 | ### maybe_encode_token/1 ### 270 | 271 | `maybe_encode_token(L) -> any()` 272 | 273 | -------------------------------------------------------------------------------- /doc/apns_types.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module apns_types # 4 | * [Description](#description) 5 | 6 | `apns_types` provides exported types defined in `apns_types.hrl`. 7 | 8 | Copyright (c) 2015 Silent Circle LLC 9 | 10 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 11 | 12 | -------------------------------------------------------------------------------- /doc/gcm_erl.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module gcm_erl # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Google Cloud Messaging (GCM) API. 9 | 10 | Copyright (c) 2015 Silent Circle 11 | 12 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 13 | 14 | 15 | 16 | ## Description ## 17 | 18 | This is the API to the GCM Service Provider. 19 | 20 | 21 | ### Synopsis ### 22 | 23 | In the example below, all optional values are shown with their 24 | defaults if omitted. 25 | 26 | 27 | #### Starting a session #### 28 | 29 | ``` 30 | Opts = [ 31 | %% Required GCM API key 32 | {api_key, <<"ahsgdfjkjkjfdk">>}, 33 | %% Required, even if empty list. Defaults shown. 34 | {ssl_opts, [ 35 | {verify, verify_peer}, 36 | {reuse_sessions, true} 37 | ]}, 38 | %% Optional, defaults as shown. 39 | {uri, "https://gcm-http.googleapis.com/gcm/send"}, 40 | %% Optional, omitted if missing. 41 | {restricted_package_name, <<"my-android-pkg">>}, 42 | %% Maximum times to try to send and then give up. 43 | {max_attempts, 10}, 44 | %% Starting point in seconds for exponential backoff. 45 | %% Optional. 46 | {retry_interval, 1}, 47 | %% Maximum seconds for a request to live in a retrying state. 48 | {max_req_ttl, 3600}, 49 | %% Reserved for future use 50 | {failure_action, fun(_)} 51 | ], 52 | {ok, Pid} = gcm_erl:start_session('gcm-com.example.MyApp', Opts). 53 | ``` 54 | 55 | 56 | #### Sending an alert via the API #### 57 | 58 | ``` 59 | RegId = <<"e7b300...a67b">>, % From earlier Android registration 60 | Opts = [ 61 | {id, RegId}, 62 | {collapse_key, <<"New Mail">>}, 63 | {data, [{msg, <<"You have new mail">>}]} 64 | ], 65 | {ok, Result} = gcm_erl:send('gcm-com.example.MyApp', Opts), 66 | {UUID, Props} = Result. 67 | ``` 68 | 69 | 70 | #### Sending an alert via a session (for testing only) #### 71 | 72 | ``` 73 | {ok, Result} = gcm_erl_session:send('gcm-com.example.MyApp', 74 | Opts), 75 | {UUID, Props} = Result. 76 | ``` 77 | 78 | 79 | #### Stopping a session #### 80 | 81 | ``` 82 | ok = gcm_erl:stop_session('gcm-com.example.MyApp'). 83 | ``` 84 | 85 | 86 | 87 | ## Function Index ## 88 | 89 | 90 |
async_send/2Asynchronously send a notification specified by proplist 91 | Notification to SvrRef.
async_send/3Asynchronously send a notification specified by proplist 92 | Notification to SvrRef with options Opts.
send/2Send a notification specified by proplist Notification 93 | to SvrRef.
send/3Send a notification specified by proplist Notification 94 | to SvrRef with options Opts.
start_session/2 95 | Start a named session.
stop_session/1Stop named session.
96 | 97 | 98 | 99 | 100 | ## Function Details ## 101 | 102 | 103 | 104 | ### async_send/2 ### 105 | 106 |

107 | async_send(SvrRef, Notification) -> Result
108 | 
109 | 110 |
  • SvrRef = term()
  • Notification = gcm_json:notification()
  • Result = {ok, {submitted, Reply}} | {error, Reason}
  • Reply = term()
  • Reason = term()
111 | 112 | Asynchronously send a notification specified by proplist 113 | `Notification` to `SvrRef`. 114 | 115 | __See also:__ [async_send/3](#async_send-3), [gcm_erl_session:async_send/2](gcm_erl_session.md#async_send-2). 116 | 117 | 118 | 119 | ### async_send/3 ### 120 | 121 |

122 | async_send(SvrRef, Notification, Opts) -> Result
123 | 
124 | 125 | 126 | 127 | Asynchronously send a notification specified by proplist 128 | `Notification` to `SvrRef` with options `Opts`. 129 | 130 | __See also:__ [send/3](#send-3), [gcm_erl_session:async_send/3](gcm_erl_session.md#async_send-3). 131 | 132 | 133 | 134 | ### send/2 ### 135 | 136 |

137 | send(SvrRef, Notification) -> Result
138 | 
139 | 140 |
  • SvrRef = term()
  • Notification = gcm_json:notification()
  • Result = {ok, Reply} | {error, Reason}
  • Reply = term()
  • Reason = term()
141 | 142 | Send a notification specified by proplist `Notification` 143 | to `SvrRef`. 144 | 145 | __See also:__ [send/3](#send-3), [gcm_erl_session:send/2](gcm_erl_session.md#send-2). 146 | 147 | 148 | 149 | ### send/3 ### 150 | 151 |

152 | send(SvrRef, Notification, Opts) -> Result
153 | 
154 | 155 | 156 | 157 | Send a notification specified by proplist `Notification` 158 | to `SvrRef` with options `Opts`. `Opts` currently only supports 159 | `{http_headers, [{string(), string()}]}` to provide extra headers. 160 | 161 | Note that `SvrRef` may be the registered name or `{Name, Node}`, 162 | where `Node` is an Erlang node on which the registered process 163 | called `Name` is running. 164 | 165 | 166 | #### Example #### 167 | 168 | ``` 169 | Name = 'gcm-com.example.MyApp', % Note: atom() ! 170 | Notification = [ 171 | %% Required, all others optional 172 | {id, <<"abc">>}, 173 | {collapse_key, <<"Something">>}, 174 | {priority, <<"high">>}, 175 | {content_available, true}, 176 | {data, []}, 177 | {delay_while_idle, false}, 178 | {time_to_live, 3600}, 179 | {restricted_package_name, <<"foo_pkg>>}, 180 | {dry_run, false} 181 | ], 182 | gcm_erl:send(Name, Notification, []), 183 | gcm_erl:send({Name, node()}, Notification, []). 184 | ``` 185 | 186 | __See also:__ [gcm_erl_session:send/3](gcm_erl_session.md#send-3). 187 | 188 | 189 | 190 | ### start_session/2 ### 191 | 192 |

193 | start_session(Name::atom(), Opts::gcm_erl_session:start_opts()) -> {ok, pid()} | {error, already_started} | {error, Reason::term()}
194 | 
195 |
196 | 197 | Start a named session. 198 | 199 | __See also:__ [gcm_erl_session:start_link/2](gcm_erl_session.md#start_link-2). 200 | 201 | 202 | 203 | ### stop_session/1 ### 204 | 205 |

206 | stop_session(Name::atom()) -> ok | {error, Reason::term()}
207 | 
208 |
209 | 210 | Stop named session. 211 | 212 | -------------------------------------------------------------------------------- /doc/gcm_erl_app.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module gcm_erl_app # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Application callbacks. 9 | 10 | Copyright (c) 2015 Silent Circle 11 | 12 | __Behaviours:__ [`application`](application.md). 13 | 14 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 15 | 16 | 17 | 18 | ## Function Index ## 19 | 20 | 21 |
start/2Start the gcm_erl application.
stop/1Stop the gcm_erl application.
22 | 23 | 24 | 25 | 26 | ## Function Details ## 27 | 28 | 29 | 30 | ### start/2 ### 31 | 32 | `start(StartType, StartArgs) -> any()` 33 | 34 | Start the `gcm_erl` application. 35 | 36 | 37 | 38 | ### stop/1 ### 39 | 40 | `stop(State) -> any()` 41 | 42 | Stop the `gcm_erl` application. 43 | 44 | -------------------------------------------------------------------------------- /doc/gcm_erl_session_sup.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module gcm_erl_session_sup # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | GCM session supervisor behavior callback module. 9 | 10 | Copyright (c) 2015 Silent Circle 11 | 12 | __Behaviours:__ [`supervisor`](supervisor.md). 13 | 14 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 15 | 16 | 17 | 18 | ## Function Index ## 19 | 20 | 21 |
get_child_pid/1
init/1
is_child_alive/1
start_child/2
start_link/1
stop_child/1
22 | 23 | 24 | 25 | 26 | ## Function Details ## 27 | 28 | 29 | 30 | ### get_child_pid/1 ### 31 | 32 | `get_child_pid(Name) -> any()` 33 | 34 | 35 | 36 | ### init/1 ### 37 | 38 | `init(X1) -> any()` 39 | 40 | 41 | 42 | ### is_child_alive/1 ### 43 | 44 | `is_child_alive(Name) -> any()` 45 | 46 | 47 | 48 | ### start_child/2 ### 49 | 50 | `start_child(Name, Opts) -> any()` 51 | 52 | 53 | 54 | ### start_link/1 ### 55 | 56 | `start_link(Sessions) -> any()` 57 | 58 | 59 | 60 | ### stop_child/1 ### 61 | 62 | `stop_child(Name) -> any()` 63 | 64 | -------------------------------------------------------------------------------- /doc/gcm_erl_util.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module gcm_erl_util # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | 8 | 9 | ## Function Index ## 10 | 11 | 12 |
sanitize_opts/1Search Opts`, which may be either a proplist 13 | `[{service, Service::proplist()}, {sessions, Sessions::proplist()}], 14 | or a list of sessions, [[{name, string()}, {config, proplist()}]], 15 | and redact sensitive data, returning the redacted argument.
16 | 17 | 18 | 19 | 20 | ## Function Details ## 21 | 22 | 23 | 24 | ### sanitize_opts/1 ### 25 | 26 |

27 | sanitize_opts(Opts) -> Result
28 | 
29 | 30 |
  • Opts = list()
  • Result = list()
31 | 32 | Search `Opts`, which may be either a proplist 33 | `[{service, Service::proplist()}, {sessions, Sessions::proplist()}]`, 34 | or a list of sessions, `[[{name, string()}, {config, proplist()}]]`, 35 | and redact sensitive data, returning the redacted argument. 36 | Needless to say, the redacted argument is pretty useless for 37 | anything other than logging. 38 | 39 | Currently this only redacts `api_key`. 40 | 41 | -------------------------------------------------------------------------------- /doc/gcm_json.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module gcm_json # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | This module handles JSON conversion for Google Cloud Messaging (GCM). 10 | 11 | Copyright (c) 2015 Silent Circle 12 | 13 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 14 | 15 | 16 | 17 | ## Data Types ## 18 | 19 | 20 | 21 | 22 | ### collapse_key() ### 23 | 24 | 25 |

 26 | collapse_key() = binary()
 27 | 
28 | 29 | 30 | 31 | 32 | ### content_available() ### 33 | 34 | 35 |

 36 | content_available() = boolean()
 37 | 
38 | 39 | 40 | 41 | 42 | ### data() ### 43 | 44 | 45 |

 46 | data() = [{binary(), any()}]
 47 | 
48 | 49 | 50 | 51 | 52 | ### delay_while_idle() ### 53 | 54 | 55 |

 56 | delay_while_idle() = boolean()
 57 | 
58 | 59 | 60 | 61 | 62 | ### dry_run() ### 63 | 64 | 65 |

 66 | dry_run() = boolean()
 67 | 
68 | 69 | 70 | 71 | 72 | ### gcm_opt() ### 73 | 74 | 75 |

 76 | gcm_opt() = recipient_id() | registration_ids() | collapse_key() | priority() | content_available() | delay_while_idle() | time_to_live() | restricted_package_name() | dry_run() | data()
 77 | 
78 | 79 | 80 | 81 | 82 | ### json_term() ### 83 | 84 | 85 |

 86 | json_term() = [{binary() | atom(), json_term()}] | [{}] | [json_term()] | [] | true | false | null | integer() | float() | binary() | atom() | calendar:datetime()
 87 | 
88 | 89 | 90 | 91 | 92 | ### notification() ### 93 | 94 | 95 |

 96 | notification() = [{atom(), gcm_opt()}]
 97 | 
98 | 99 | 100 | 101 | 102 | ### priority() ### 103 | 104 | 105 |

106 | priority() = binary()
107 | 
108 | 109 | 110 | 111 | 112 | ### recipient_id() ### 113 | 114 | 115 |

116 | recipient_id() = binary()
117 | 
118 | 119 | 120 | 121 | 122 | ### registration_ids() ### 123 | 124 | 125 |

126 | registration_ids() = [binary()]
127 | 
128 | 129 | 130 | 131 | 132 | ### restricted_package_name() ### 133 | 134 | 135 |

136 | restricted_package_name() = binary()
137 | 
138 | 139 | 140 | 141 | 142 | ### time_to_live() ### 143 | 144 | 145 |

146 | time_to_live() = integer()
147 | 
148 | 149 | 150 | 151 | ## Function Index ## 152 | 153 | 154 |
make_notification/1Create a notification consisting of a JSON binary suitable for 155 | transmitting to the Google Cloud Messaging Service.
156 | 157 | 158 | 159 | 160 | ## Function Details ## 161 | 162 | 163 | 164 | ### make_notification/1 ### 165 | 166 |

167 | make_notification(Notification::notification()) -> binary()
168 | 
169 |
170 | 171 | Create a notification consisting of a JSON binary suitable for 172 | transmitting to the Google Cloud Messaging Service. 173 | 174 | To understand the various properties below, please see 175 | [ 176 | GCM Architectural Overview](http://developer.android.com/guide/google/gcm/index.md). 177 | The description given is basically to show how to format the 178 | properties in Erlang. 179 | 180 | 181 | ### Notification Properties ### 182 | 183 | 184 | 185 |
id::binary()
186 | 187 | 188 | 189 | 190 |
191 | Recipient ID (binary string). Required unless 192 | registration_ids is provided. This corresponds to the GCM to 193 | parameter, which is one of a registration token, notification key, or 194 | topic.
195 | 196 | 197 | 198 | 199 |
registration_ids::[binary()]
200 | 201 | 202 | 203 | 204 |
List of binary strings. Each binary is a registration id 205 | for an Android device+application. Required unless 206 | id is provided. 207 |
208 | 209 | 210 | 211 | 212 |
data::[{binary(), any()}]
213 | 214 | 215 | 216 | 217 |

Required.
218 | Message payload data, which must be an 219 | object (Erlang proplist) as described in the table below.

220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 |
jsonerlang
number integer() and float()
string binary()
true, false and null true, false and null
array [] and [JSON]
object [{}] and [{binary() OR atom(), JSON}]
244 |
245 | 246 | 247 | 248 | 249 |
gcm::list()
250 | 251 | 252 | 253 | 254 |
An optional list of GCM-specific properties
255 | 256 | 257 | 258 | 259 | 260 |
collapse_key::binary()
261 | 262 | 263 | 264 | 265 |
Binary string. Optional.
266 | 267 | 268 | 269 | 270 |
priority::binary()
271 | 272 | 273 | 274 | 275 |
Binary string. Optional. Either <<"normal">> (default) or 276 | <<"high">>.
277 | 278 | 279 | 280 | 281 |
content_available::boolean()
282 | 283 | 284 | 285 | 286 |
Binary string. Optional.
287 | 288 | 289 | 290 | 291 |
delay_while_idle::boolean()
292 | 293 | 294 | 295 | 296 |
See GCM reference. Optional.
297 | 298 | 299 | 300 | 301 |
time_to_live::integer()
302 | 303 | 304 | 305 | 306 |
Optional.
307 | 308 | 309 | 310 | 311 |
restricted_package_name::binary()
312 | 313 | 314 | 315 | 316 |
Binary string - overrides default on server. Optional.
317 | 318 | 319 | 320 | 321 |
dry_run::boolean()
322 | 323 | 324 | 325 | 326 |
Optional (defaults to false)
327 | 328 | 329 | 330 | 331 | 332 | 333 | ### Examples of Notification proplist ### 334 | 335 | 336 | #### Simplest possible single registration id notification #### 337 | 338 | ``` 339 | Notification = [ 340 | {'id', <<"registration-id">>}, 341 | {'data', [{msg, <<"Would you like to play a game?">>}]} 342 | ]. 343 | ``` 344 | 345 | 346 | #### Simplest possible multiple registration id notification #### 347 | 348 | 349 | ``` 350 | Notification = [ 351 | {'registration_ids', [<<"registration-id-1">>, <<"registration-id-2">>]}, 352 | {'data', [{msg, <<"Would you like to play a game?">>}]} 353 | ]. 354 | ``` 355 | 356 | -------------------------------------------------------------------------------- /doc/gcm_req_sched.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module gcm_req_sched # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | This module is the request scheduler. 10 | 11 | Copyright (c) 2015 Silent Circle 12 | 13 | __Behaviours:__ [`gen_server`](gen_server.md). 14 | 15 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 16 | 17 | 18 | 19 | ## Description ## 20 | 21 | Its purpose is to store and 22 | resubmit delayed GCM requests at some future time. For performance 23 | reasons, it does not store notifications on disk, so a shutdown 24 | or crash will lose notifications that were to have been resubmitted. 25 | 26 | GCM notifications may be delayed because the GCM server was busy 27 | or temporarily unavailable. 28 | 29 | 30 | 31 | ## Data Types ## 32 | 33 | 34 | 35 | 36 | ### time_posix_milli_seconds() ### 37 | 38 | 39 |

 40 | time_posix_milli_seconds() = pos_integer()
 41 | 
42 | 43 | 44 | 45 | 46 | ### time_posix_secs() ### 47 | 48 | 49 |

 50 | time_posix_secs() = pos_integer()
 51 | 
52 | 53 | 54 | 55 | 56 | ### trigger_time() ### 57 | 58 | 59 |

 60 | trigger_time() = time_posix_secs() | {time_posix_milli_seconds(), milli_seconds}
 61 | 
62 | 63 | 64 | 65 | ## Function Index ## 66 | 67 | 68 |
add/4Add data with unique id to be triggered at a POSIX time 69 | to send the data to pid.
clear_all/0Clear all entries.
del/1Delete data with given id.
get/1Retrieve data with given id, and return {TriggerTime, Data}, or 70 | notfound.
run_sweep/0Force a sweep of all data to trigger matching messages.
start/0
start_link/0 71 | Starts the server.
stop/0Stops the server - for testing only.
72 | 73 | 74 | 75 | 76 | ## Function Details ## 77 | 78 | 79 | 80 | ### add/4 ### 81 | 82 |

 83 | add(Id::any(), TriggerTime::trigger_time(), Data::any(), Pid::pid()) -> ok
 84 | 
85 |
86 | 87 | Add data with unique id to be triggered at a POSIX time 88 | to send the data to pid. 89 | `Data` is sent to `Pid` as `{triggered, {ReqId, Data}}`. 90 | -------------------------------------------------------------------- 91 | 92 | 93 | 94 | ### clear_all/0 ### 95 | 96 |

 97 | clear_all() -> ok
 98 | 
99 |
100 | 101 | Clear all entries. 102 | -------------------------------------------------------------------- 103 | 104 | 105 | 106 | ### del/1 ### 107 | 108 |

109 | del(Id::any()) -> ok
110 | 
111 |
112 | 113 | Delete data with given id. 114 | -------------------------------------------------------------------- 115 | 116 | 117 | 118 | ### get/1 ### 119 | 120 |

121 | get(Id::any()) -> {ok, {TriggerTime::pos_integer(), Data::any}} | notfound
122 | 
123 |
124 | 125 | Retrieve data with given id, and return `{TriggerTime, Data}`, or 126 | `notfound`. `TriggerTime` is Erlang monotonic time in milliseconds. 127 | -------------------------------------------------------------------- 128 | 129 | 130 | 131 | ### run_sweep/0 ### 132 | 133 |

134 | run_sweep() -> ok
135 | 
136 |
137 | 138 | Force a sweep of all data to trigger matching messages. 139 | -------------------------------------------------------------------- 140 | 141 | 142 | 143 | ### start/0 ### 144 | 145 |

146 | start() -> {ok, pid()} | ignore | {error, term()}
147 | 
148 |
149 | 150 | 151 | 152 | ### start_link/0 ### 153 | 154 |

155 | start_link() -> {ok, pid()} | ignore | {error, term()}
156 | 
157 |
158 | 159 | Starts the server 160 | 161 | 162 | 163 | ### stop/0 ### 164 | 165 |

166 | stop() -> ok
167 | 
168 |
169 | 170 | Stops the server - for testing only. 171 | -------------------------------------------------------------------- 172 | 173 | -------------------------------------------------------------------------------- /doc/man/README: -------------------------------------------------------------------------------- 1 | % SCPF User's Guide 2 | % Edwin Fine 3 | % Mon May 8 15:03:55 UTC 2017 4 | 5 | Synopsis 6 | ======== 7 | 8 | `scpf` [*command*] 9 | 10 | Description 11 | =========== 12 | 13 | scpf is a general push service provider written in Erlang. 14 | 15 | Examples 16 | ======== 17 | 18 | `scpf start` 19 | 20 | : Starts `scpf` in the background as a daemon. 21 | 22 | `scpf status` 23 | 24 | : Attempts to ping `scpf`'s Erlang node. 25 | 26 | `scpf foreground` 27 | 28 | : Starts **scpf** in the foreground, suitable for working with 29 | supervisors like systemd. 30 | 31 | `scpf version` 32 | 33 | : Shows the release version string for `scpf`. This is not a fully 34 | reliable way of getting the release version, because it uses the 35 | contents of an installed file, 36 | _/usr/lib/scpf/releases/start_erl.data_. 37 | 38 | `scpf rpcterms application which_applications` 39 | 40 | : Displays, in Erlang term format, which applications are running 41 | in the node. 42 | 43 | `scpf rpcterms io format '"This is a string~n"'` 44 | 45 | : Prints "This is a string" on the console, followed by "ok". 46 | 47 | `scpf rpcterms io format '"Int: ~B, Atom: ~p, Str: ~s~n"' '[10,foo,"Hi"]'` 48 | 49 | : This is an example of giving multiple arguments to `rpcterms`. 50 | It is equivalent to `io:format("Int: ~B, Atom: ~p, Str: ~s~n", [10,foo,"Hi"]).` 51 | 52 | Commands 53 | ======== 54 | 55 | The `scpf` command controls the main server process. Note that 56 | interactions with `systemd` may cause unexpected behavior, such as 57 | automatically restarting `scpf` immediately after `scpf stop` has 58 | been called. 59 | 60 | `attach` 61 | 62 | : Attaches to the `scpf` process via a pipe. This will only work 63 | if `scpf` was started in a background mode. 64 | 65 | `backup` _file_ 66 | 67 | : Backs up `scpf` Mnesia databases in running node to _file_. 68 | 69 | `console` 70 | 71 | : Starts scpf as a foreground process with access to the Erlang console 72 | messages and shell; outputs "Node is already running!" when node is 73 | already running as a background process. 74 | 75 | This command is equivalent to `console_boot scpf.boot`. 76 | 77 | `console_boot` _boot-file_ 78 | 79 | : Starts `scpf` in console mode, using the Erlang boot file 80 | specified in _file_. This command drops the caller into an Erlang 81 | shell on the system booted with _boot-file_. Exiting the shell 82 | shuts down the system. 83 | 84 | `console_clean` 85 | 86 | : Starts a clean Erlang VM in console mode, using the Erlang boot 87 | file `start_clean.boot`. 88 | 89 | `erts_vsn` 90 | 91 | : Displays the version of the Erlang run-time system (erts) that scpf 92 | will be booted into. scpf need not be running for this command to 93 | work. 94 | 95 | `downgrade` _package-base-name_ 96 | 97 | : Synonym for **install**. 98 | 99 | `escript` _file_ 100 | 101 | : Runs escript _file_ in the node's environment. 102 | 103 | `eval` _expression_ 104 | 105 | : Evaluates Erlang _expression_ in the running node. 106 | 107 | `foreground` 108 | 109 | : Starts up the release in the foreground suitable for runit, 110 | systemctl, and other supervision tools. 111 | 112 | `install` _package-base-name_ 113 | 114 | : Runs `install_upgrade.escript install` on `_package-base-name_` in 115 | the running node. 116 | 117 | Installs, upgrades, or downgrades the scpf release depending on 118 | whether or not the release in the package tarball is absent, newer, 119 | or older, respectively. The tarball is `_package-base-name_.tar.gz` 120 | in the current directory. 121 | 122 | `pid` 123 | 124 | : Displays the pid of the running scpf node. 125 | 126 | `ping` 127 | 128 | : Pings scpf node as a basic test for node liveness; outputs "pong" on 129 | success or warnings about the node not responding to pings. 130 | 131 | `reboot` 132 | 133 | : Stops and starts scpf node while also exiting the current Erlang 134 | virtual machine; outputs "ok" on success or warnings about node not 135 | responding to pings. 136 | 137 | `remote_console` 138 | 139 | : Starts a remote console on a background scpf node; outputs "Node is 140 | not running!" if unable to reach the node. 141 | 142 | `restart` 143 | 144 | : Stops and starts scpf node while maintaining current Erlang virtual 145 | machine; outputs "ok" on success or warnings about node not 146 | responding to pings. 147 | 148 | `rpc` 149 | 150 | : Executes RPC using _module_, _function_, and _args_ on running 151 | node. This currently only works when args are omitted. 152 | 153 | `rpcterms` _module_ _function_ [_args_] 154 | 155 | : Executes RPC using _module_, _function_, and _args_ on running 156 | node, and displays the result in Erlang term format. The optional 157 | _args_ parameter is a sequence of one or more arguments. Each 158 | argument must be a separate command-line parameter. The arguments 159 | are parsed into Erlang, so eahc argument must be valid Erlang 160 | syntax. This will probably require escaping _args_ for the shell. 161 | 162 | `start` 163 | 164 | : Starts scpf node as a background process; outputs message "Node is 165 | already running!" when attempting to start an already running node. 166 | 167 | `status` 168 | 169 | : Synonym for **ping**. 170 | 171 | `start_boot` _boot-file_ 172 | 173 | : Starts scpf node as a background process, using _boot-file_. 174 | Outputs message "Node is already running!" when attempting to start 175 | an already running node. 176 | 177 | `stop` 178 | 179 | : Stops scpf node; outputs "ok" on success or warnings about the node 180 | not responding to pings. If attached to scpf on the Erlang shell, 181 | this example will shut down scpf in that node: 182 | 183 | `(scpf@example.com)1> q().` 184 | 185 | If in a remote Erlang shell attached to scpf, this example will 186 | disconnect from the shell without shutting down scpf: 187 | 188 | `(scpf@example.com)1> _CTRL-g_q_ENTER_` 189 | 190 | `upgrade` _package-base-name_ 191 | 192 | : Synonym for **install**. 193 | 194 | `version` 195 | 196 | : Displays the scpf version string. scpf need not be running. 197 | 198 | Authors 199 | ======= 200 | 201 | © 2012-2016 Silent Circle LLC (https://silentcircle.com) 202 | 203 | Edwin Fine (efine@silentcircle.com) 204 | 205 | 208 | -------------------------------------------------------------------------------- /doc/man/scpf.1: -------------------------------------------------------------------------------- 1 | .TH SCPF 1 "Thu Jul 13 15:21:14 UTC 2017" "scpf 2.0.4~dev4" 2 | .SH NAME 3 | scpf - general push service provider written in Erlang. 4 | .SH Synopsis 5 | .PP 6 | \f[C]scpf\f[] [\f[I]command\f[]] 7 | .SH Description 8 | .PP 9 | scpf is a general push service provider written in Erlang. 10 | .SH Examples 11 | .TP 12 | .B \f[C]scpf\ start\f[] 13 | Starts \f[C]scpf\f[] in the background as a daemon. 14 | .RS 15 | .RE 16 | .TP 17 | .B \f[C]scpf\ status\f[] 18 | Attempts to ping \f[C]scpf\f[]\[aq]s Erlang node. 19 | .RS 20 | .RE 21 | .TP 22 | .B \f[C]scpf\ foreground\f[] 23 | Starts \f[B]scpf\f[] in the foreground, suitable for working with 24 | supervisors like systemd. 25 | .RS 26 | .RE 27 | .TP 28 | .B \f[C]scpf\ version\f[] 29 | Shows the release version string for \f[C]scpf\f[]. 30 | This is not a fully reliable way of getting the release version, because 31 | it uses the contents of an installed file, 32 | \f[I]/usr/lib/scpf/releases/start\f[]erl.data_. 33 | .RS 34 | .RE 35 | .TP 36 | .B \f[C]scpf\ rpcterms\ application\ which_applications\f[] 37 | Displays, in Erlang term format, which applications are running in the 38 | node. 39 | .RS 40 | .RE 41 | .TP 42 | .B \f[C]scpf\ rpcterms\ io\ format\ \[aq]"This\ is\ a\ string~n"\[aq]\f[] 43 | Prints "This is a string" on the console, followed by "ok". 44 | .RS 45 | .RE 46 | .TP 47 | .B \f[C]scpf\ rpcterms\ io\ format\ \[aq]"Int:\ ~B,\ Atom:\ ~p,\ Str:\ ~s~n"\[aq]\ \[aq][10,foo,"Hi"]\[aq]\f[] 48 | This is an example of giving multiple arguments to \f[C]rpcterms\f[]. 49 | It is equivalent to 50 | \f[C]io:format("Int:\ ~B,\ Atom:\ ~p,\ Str:\ ~s~n",\ [10,foo,"Hi"]).\f[] 51 | .RS 52 | .RE 53 | .SH Commands 54 | .PP 55 | The \f[C]scpf\f[] command controls the main server process. 56 | Note that interactions with \f[C]systemd\f[] may cause unexpected 57 | behavior, such as automatically restarting \f[C]scpf\f[] immediately 58 | after \f[C]scpf\ stop\f[] has been called. 59 | .TP 60 | .B \f[C]attach\f[] 61 | Attaches to the \f[C]scpf\f[] process via a pipe. 62 | This will only work if \f[C]scpf\f[] was started in a background mode. 63 | .RS 64 | .RE 65 | .TP 66 | .B \f[C]backup\f[] \f[I]file\f[] 67 | Backs up \f[C]scpf\f[] Mnesia databases in running node to 68 | \f[I]file\f[]. 69 | .RS 70 | .RE 71 | .TP 72 | .B \f[C]console\f[] 73 | Starts scpf as a foreground process with access to the Erlang console 74 | messages and shell; outputs "Node is already running!" when node is 75 | already running as a background process. 76 | .RS 77 | .PP 78 | This command is equivalent to \f[C]console_boot\ scpf.boot\f[]. 79 | .RE 80 | .TP 81 | .B \f[C]console_boot\f[] \f[I]boot\-file\f[] 82 | Starts \f[C]scpf\f[] in console mode, using the Erlang boot file 83 | specified in \f[I]file\f[]. 84 | This command drops the caller into an Erlang shell on the system booted 85 | with \f[I]boot\-file\f[]. 86 | Exiting the shell shuts down the system. 87 | .RS 88 | .RE 89 | .TP 90 | .B \f[C]console_clean\f[] 91 | Starts a clean Erlang VM in console mode, using the Erlang boot file 92 | \f[C]start_clean.boot\f[]. 93 | .RS 94 | .RE 95 | .TP 96 | .B \f[C]erts_vsn\f[] 97 | Displays the version of the Erlang run\-time system (erts) that scpf 98 | will be booted into. 99 | scpf need not be running for this command to work. 100 | .RS 101 | .RE 102 | .TP 103 | .B \f[C]downgrade\f[] \f[I]package\-base\-name\f[] 104 | Synonym for \f[B]install\f[]. 105 | .RS 106 | .RE 107 | .TP 108 | .B \f[C]escript\f[] \f[I]file\f[] 109 | Runs escript \f[I]file\f[] in the node\[aq]s environment. 110 | .RS 111 | .RE 112 | .TP 113 | .B \f[C]eval\f[] \f[I]expression\f[] 114 | Evaluates Erlang \f[I]expression\f[] in the running node. 115 | .RS 116 | .RE 117 | .TP 118 | .B \f[C]foreground\f[] 119 | Starts up the release in the foreground suitable for runit, systemctl, 120 | and other supervision tools. 121 | .RS 122 | .RE 123 | .TP 124 | .B \f[C]install\f[] \f[I]package\-base\-name\f[] 125 | Runs \f[C]install_upgrade.escript\ install\f[] on 126 | \f[C]_package\-base\-name_\f[] in the running node. 127 | .RS 128 | .PP 129 | Installs, upgrades, or downgrades the scpf release depending on whether 130 | or not the release in the package tarball is absent, newer, or older, 131 | respectively. 132 | The tarball is \f[C]_package\-base\-name_.tar.gz\f[] in the current 133 | directory. 134 | .RE 135 | .TP 136 | .B \f[C]pid\f[] 137 | Displays the pid of the running scpf node. 138 | .RS 139 | .RE 140 | .TP 141 | .B \f[C]ping\f[] 142 | Pings scpf node as a basic test for node liveness; outputs "pong" on 143 | success or warnings about the node not responding to pings. 144 | .RS 145 | .RE 146 | .TP 147 | .B \f[C]reboot\f[] 148 | Stops and starts scpf node while also exiting the current Erlang virtual 149 | machine; outputs "ok" on success or warnings about node not responding 150 | to pings. 151 | .RS 152 | .RE 153 | .TP 154 | .B \f[C]remote_console\f[] 155 | Starts a remote console on a background scpf node; outputs "Node is not 156 | running!" if unable to reach the node. 157 | .RS 158 | .RE 159 | .TP 160 | .B \f[C]restart\f[] 161 | Stops and starts scpf node while maintaining current Erlang virtual 162 | machine; outputs "ok" on success or warnings about node not responding 163 | to pings. 164 | .RS 165 | .RE 166 | .TP 167 | .B \f[C]rpc\f[] 168 | Executes RPC using \f[I]module\f[], \f[I]function\f[], and \f[I]args\f[] 169 | on running node. 170 | This currently only works when args are omitted. 171 | .RS 172 | .RE 173 | .TP 174 | .B \f[C]rpcterms\f[] \f[I]module\f[] \f[I]function\f[] [\f[I]args\f[]] 175 | Executes RPC using \f[I]module\f[], \f[I]function\f[], and \f[I]args\f[] 176 | on running node, and displays the result in Erlang term format. 177 | The optional \f[I]args\f[] parameter is a sequence of one or more 178 | arguments. 179 | Each argument must be a separate command\-line parameter. 180 | The arguments are parsed into Erlang, so eahc argument must be valid 181 | Erlang syntax. 182 | This will probably require escaping \f[I]args\f[] for the shell. 183 | .RS 184 | .RE 185 | .TP 186 | .B \f[C]start\f[] 187 | Starts scpf node as a background process; outputs message "Node is 188 | already running!" when attempting to start an already running node. 189 | .RS 190 | .RE 191 | .TP 192 | .B \f[C]status\f[] 193 | Synonym for \f[B]ping\f[]. 194 | .RS 195 | .RE 196 | .TP 197 | .B \f[C]start_boot\f[] \f[I]boot\-file\f[] 198 | Starts scpf node as a background process, using \f[I]boot\-file\f[]. 199 | Outputs message "Node is already running!" when attempting to start an 200 | already running node. 201 | .RS 202 | .RE 203 | .TP 204 | .B \f[C]stop\f[] 205 | Stops scpf node; outputs "ok" on success or warnings about the node not 206 | responding to pings. 207 | If attached to scpf on the Erlang shell, this example will shut down 208 | scpf in that node: 209 | .RS 210 | .PP 211 | \f[C](scpf\@example.com)1>\ q().\f[] 212 | .PP 213 | If in a remote Erlang shell attached to scpf, this example will 214 | disconnect from the shell without shutting down scpf: 215 | .PP 216 | \f[C](scpf\@example.com)1>\ _CTRL\-g_q_ENTER_\f[] 217 | .RE 218 | .TP 219 | .B \f[C]upgrade\f[] \f[I]package\-base\-name\f[] 220 | Synonym for \f[B]install\f[]. 221 | .RS 222 | .RE 223 | .TP 224 | .B \f[C]version\f[] 225 | Displays the scpf version string. 226 | scpf need not be running. 227 | .RS 228 | .RE 229 | .SH Authors 230 | .PP 231 | © 2012\-2016 Silent Circle LLC (https://silentcircle.com) 232 | .PP 233 | Edwin Fine (efine\@silentcircle.com) 234 | -------------------------------------------------------------------------------- /doc/man/scpf.1.template: -------------------------------------------------------------------------------- 1 | $if(has-tables)$ 2 | .\"t 3 | $endif$ 4 | .TH SCPF 1 "$date$" "$version$" 5 | .SH NAME 6 | scpf - general push service provider written in Erlang. 7 | $body$ 8 | -------------------------------------------------------------------------------- /doc/sc_config.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_config # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Configuration server for sc_push. 9 | 10 | __Behaviours:__ [`gen_server`](gen_server.md). 11 | 12 | __Authors:__ Edwin Fine. 13 | 14 | 15 | 16 | ## Function Index ## 17 | 18 | 19 |
delete/1Delete key.
delete_all/0
delete_keys/1Delete multiple values matching a key.
get/1Get value for key, or undefined is not found.
get/2Get value for key, or default value if key not found.
get_all_keys/0Get all keys.
get_all_values/0Get all values.
select/1Select multiple values matching a key.
set/2Set key/value pair.
start_link/0 20 | Starts the server.
21 | 22 | 23 | 24 | 25 | ## Function Details ## 26 | 27 | 28 | 29 | ### delete/1 ### 30 | 31 |

 32 | delete(K::term()) -> ok
 33 | 
34 |
35 | 36 | Delete key. 37 | 38 | 39 | 40 | ### delete_all/0 ### 41 | 42 |

 43 | delete_all() -> ok
 44 | 
45 |
46 | 47 | 48 | 49 | ### delete_keys/1 ### 50 | 51 |

 52 | delete_keys(K) -> ok
 53 | 
54 | 55 |
  • K = any()
56 | 57 | Delete multiple values matching a key. The key may contain wildcards, 58 | defined as the atom `'_'`. There is no prefix matching such as `'foo_'`. To match part of a key that is a tuple, put wildcards in the "don't 59 | care" positions of the tuple. The arity of the tuple must be correct. 60 | 61 | The deletion will be performed atomically. 62 | -------------------------------------------------------------------- 63 | 64 | 65 | 66 | ### get/1 ### 67 | 68 |

 69 | get(K::term()) -> term() | undefined
 70 | 
71 |
72 | 73 | Get value for key, or undefined is not found. 74 | 75 | 76 | 77 | ### get/2 ### 78 | 79 |

 80 | get(K::term(), Def::term()) -> term()
 81 | 
82 |
83 | 84 | Get value for key, or default value if key not found. 85 | 86 | 87 | 88 | ### get_all_keys/0 ### 89 | 90 |

 91 | get_all_keys() -> list()
 92 | 
93 |
94 | 95 | Get all keys 96 | 97 | 98 | 99 | ### get_all_values/0 ### 100 | 101 |

102 | get_all_values() -> list()
103 | 
104 |
105 | 106 | Get all values 107 | 108 | 109 | 110 | ### select/1 ### 111 | 112 |

113 | select(K) -> Values
114 | 
115 | 116 |
  • K = any()
  • Values = [any()]
117 | 118 | Select multiple values matching a key. The key may contain wildcards, 119 | defined as the atom `'_'`. There is no prefix matching such as `'foo_'`. To match part of a key that is a tuple, put wildcards in the "don't 120 | care" positions of the tuple. The arity of the tuple must be correct. 121 | 122 | 123 | #### Examples #### 124 | 125 | * `select('_')` is the same as `get_all_values()`. 126 | 127 | * `select({foo,'_'})` will select all keys that are 2-element 128 | tuples whose first element is `'foo'` 129 | 130 | 131 | 132 | 133 | ### set/2 ### 134 | 135 |

136 | set(K::term(), V::term()) -> ok
137 | 
138 |
139 | 140 | Set key/value pair. 141 | 142 | 143 | 144 | ### start_link/0 ### 145 | 146 |

147 | start_link() -> {ok, pid()} | ignore | {error, term()}
148 | 
149 |
150 | 151 | Starts the server 152 | 153 | -------------------------------------------------------------------------------- /doc/sc_priority_queue.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_priority_queue # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | Priority queues have essentially the same interface as ordinary 10 | queues, except that a) there is an `in/3` that takes a priority, and 11 | b) we have only implemented the core API we need. 12 | 13 | 14 | 15 | ## Description ## 16 | 17 | Priorities should be integers - the higher the value the higher the 18 | priority - but we don't actually check that. 19 | 20 | `in/2` inserts items with priority 0. 21 | 22 | We optimise the case where a priority queue is being used just like 23 | an ordinary queue. When that is the case we represent the priority 24 | queue as an ordinary queue. We could just call into the `queue` 25 | module for that, but for efficiency we implement the relevant 26 | functions directly in here, thus saving on inter-module calls and 27 | eliminating a level of boxing. 28 | 29 | When the queue contains items with non-zero priorities, it is 30 | represented as a sorted kv list with the inverted priority as the 31 | key and an ordinary queue as the value. Here again we use our own 32 | ordinary queue implemention for efficiency, often making recursive 33 | calls into the same function knowing that ordinary queues represent 34 | a base case. 35 | 36 | 37 | ## Data Types ## 38 | 39 | 40 | 41 | 42 | ### pqueue() ### 43 | 44 | 45 |

 46 | pqueue() = squeue() | {pqueue, [{priority(), squeue()}]}
 47 | 
48 | 49 | 50 | 51 | 52 | ### priority() ### 53 | 54 | 55 |

 56 | priority() = integer() | infinity
 57 | 
58 | 59 | 60 | 61 | 62 | ### q() ### 63 | 64 | 65 |

 66 | q() = pqueue()
 67 | 
68 | 69 | 70 | 71 | 72 | ### squeue() ### 73 | 74 | 75 |

 76 | squeue() = {queue, [any()], [any()], non_neg_integer()}
 77 | 
78 | 79 | 80 | 81 | ## Function Index ## 82 | 83 | 84 |
filter/2
fold/3
from_list/1
highest/1
in/2
in/3
is_empty/1
is_queue/1
join/2
len/1
new/0
out/1
out_p/1
to_list/1
85 | 86 | 87 | 88 | 89 | ## Function Details ## 90 | 91 | 92 | 93 | ### filter/2 ### 94 | 95 |

 96 | filter(Pred::fun((any()) -> boolean()), Q::pqueue()) -> pqueue()
 97 | 
98 |
99 | 100 | 101 | 102 | ### fold/3 ### 103 | 104 |

105 | fold(Fun::fun((any(), priority(), A) -> A), A, Q::pqueue()) -> A
106 | 
107 |
108 | 109 | 110 | 111 | ### from_list/1 ### 112 | 113 |

114 | from_list(L::[{priority(), any()}]) -> pqueue()
115 | 
116 |
117 | 118 | 119 | 120 | ### highest/1 ### 121 | 122 |

123 | highest(X1::pqueue()) -> priority() | empty
124 | 
125 |
126 | 127 | 128 | 129 | ### in/2 ### 130 | 131 |

132 | in(Item::any(), Q::pqueue()) -> pqueue()
133 | 
134 |
135 | 136 | 137 | 138 | ### in/3 ### 139 | 140 |

141 | in(X::any(), Priority::priority(), Q::pqueue()) -> pqueue()
142 | 
143 |
144 | 145 | 146 | 147 | ### is_empty/1 ### 148 | 149 |

150 | is_empty(X1::pqueue()) -> boolean()
151 | 
152 |
153 | 154 | 155 | 156 | ### is_queue/1 ### 157 | 158 |

159 | is_queue(X1::any()) -> boolean()
160 | 
161 |
162 | 163 | 164 | 165 | ### join/2 ### 166 | 167 |

168 | join(A::pqueue(), B::pqueue()) -> pqueue()
169 | 
170 |
171 | 172 | 173 | 174 | ### len/1 ### 175 | 176 |

177 | len(X1::pqueue()) -> non_neg_integer()
178 | 
179 |
180 | 181 | 182 | 183 | ### new/0 ### 184 | 185 |

186 | new() -> pqueue()
187 | 
188 |
189 | 190 | 191 | 192 | ### out/1 ### 193 | 194 |

195 | out(Q::pqueue()) -> {empty | {value, any()}, pqueue()}
196 | 
197 |
198 | 199 | 200 | 201 | ### out_p/1 ### 202 | 203 |

204 | out_p(Q::pqueue()) -> {empty | {value, any(), priority()}, pqueue()}
205 | 
206 |
207 | 208 | 209 | 210 | ### to_list/1 ### 211 | 212 |

213 | to_list(X1::pqueue()) -> [{priority(), any()}]
214 | 
215 |
216 | 217 | -------------------------------------------------------------------------------- /doc/sc_push_app.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_app # 4 | * [Data Types](#types) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | __Behaviours:__ [`application`](application.md). 9 | 10 | 11 | 12 | ## Data Types ## 13 | 14 | 15 | 16 | 17 | ### pv_list() ### 18 | 19 | 20 |

 21 | pv_list() = [pv_tuple()]
 22 | 
23 | 24 | 25 | 26 | 27 | ### pv_tuple() ### 28 | 29 | 30 |

 31 | pv_tuple() = {term(), term()}
 32 | 
33 | 34 | 35 | 36 | 37 | ### start_type() ### 38 | 39 | 40 |

 41 | start_type() = normal | {takeover, Node::node()} | {failover, Node::node()}
 42 | 
43 | 44 | 45 | 46 | ## Function Index ## 47 | 48 | 49 |
config_change/3 50 | This function is called by an application after a code replacement, if there 51 | are any changes to the configuration parameters.
prep_stop/1 52 | This function is called when an application is about to be stopped, before 53 | shutting down the processes of the application.
start/2Start the sc_push application.
stop/1Stop the sc_push application.
54 | 55 | 56 | 57 | 58 | ## Function Details ## 59 | 60 | 61 | 62 | ### config_change/3 ### 63 | 64 |

 65 | config_change(Changed::pv_list(), New::pv_list(), Removed::list()) -> ok
 66 | 
67 |
68 | 69 | This function is called by an application after a code replacement, if there 70 | are any changes to the configuration parameters. Changed is a list of 71 | parameter-value tuples with all configuration parameters with changed 72 | values, New is a list of parameter-value tuples with all configuration 73 | parameters that have been added, and Removed is a list of all parameters 74 | that have been removed. 75 | 76 | 77 | 78 | ### prep_stop/1 ### 79 | 80 |

 81 | prep_stop(State::term()) -> NewState::term()
 82 | 
83 |
84 | 85 | This function is called when an application is about to be stopped, before 86 | shutting down the processes of the application. State is the state returned 87 | from Module:start/2, or [] if no state was returned. NewState is any term 88 | and will be passed to Module:stop/1. The function is optional. If it is not 89 | defined, the processes will be terminated and then Module:stop(State) is 90 | called. 91 | 92 | 93 | 94 | ### start/2 ### 95 | 96 |

 97 | start(StartType::start_type(), StartArgs::term()) -> {ok, pid(), list()} | {error, term()}
 98 | 
99 |
100 | 101 | Start the `sc_push` application. 102 | 103 | 104 | 105 | ### stop/1 ### 106 | 107 |

108 | stop(State::term()) -> any()
109 | 
110 |
111 | 112 | Stop the `sc_push` application. 113 | 114 | -------------------------------------------------------------------------------- /doc/sc_push_lib.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_lib # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | Push service common library functions. 10 | 11 | Copyright (c) 2015, 2016 Silent Circle 12 | 13 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 14 | 15 | 16 | 17 | ## Data Types ## 18 | 19 | 20 | 21 | 22 | ### std_proplist() ### 23 | 24 | 25 |

26 | std_proplist() = sc_types:proplist(atom(), term())
27 | 
28 | 29 | 30 | 31 | ## Function Index ## 32 | 33 | 34 |
get_all_service_configs/0Get all service configs.
get_service_config/1Get service configuration.
register_service/1Register a service in the service configuration registry.
unregister_service/1Unregister a service in the service configuration registry.
35 | 36 | 37 | 38 | 39 | ## Function Details ## 40 | 41 | 42 | 43 | ### get_all_service_configs/0 ### 44 | 45 |

46 | get_all_service_configs() -> [std_proplist()]
47 | 
48 |
49 | 50 | Get all service configs. 51 | 52 | 53 | 54 | ### get_service_config/1 ### 55 | 56 |

57 | get_service_config(Service::term()) -> {ok, std_proplist()} | {error, term()}
58 | 
59 |
60 | 61 | Get service configuration 62 | 63 | __See also:__ [start_service/1](#start_service-1). 64 | 65 | 66 | 67 | ### register_service/1 ### 68 | 69 |

70 | register_service(Svc) -> ok
71 | 
72 | 73 | 74 | 75 | Register a service in the service configuration registry. 76 | Requires a property `{name, ServiceName :: atom()}` to be present 77 | in `Svc`. 78 | 79 | __See also:__ [start_service/1](#start_service-1). 80 | 81 | 82 | 83 | ### unregister_service/1 ### 84 | 85 |

86 | unregister_service(ServiceName::atom()) -> ok
87 | 
88 |
89 | 90 | Unregister a service in the service configuration registry. 91 | 92 | __See also:__ [start_service/1](#start_service-1). 93 | 94 | -------------------------------------------------------------------------------- /doc/sc_push_lib_app.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_lib_app # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Application callbacks. 9 | 10 | Copyright (c) 2015, 2016 Silent Circle 11 | 12 | __Behaviours:__ [`application`](application.md). 13 | 14 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 15 | 16 | 17 | 18 | ## Function Index ## 19 | 20 | 21 |
start/2 22 | This function is called whenever an application is started using 23 | application:start/[1,2], and should start the processes of the 24 | application.
stop/1 25 | This function is called whenever an application has stopped.
26 | 27 | 28 | 29 | 30 | ## Function Details ## 31 | 32 | 33 | 34 | ### start/2 ### 35 | 36 |

37 | start(StartType, StartArgs) -> Result
38 | 
39 | 40 |
  • StartType = normal | {takeover, Node} | {failover, Node}
  • StartArgs = term()
  • Result = {ok, Pid} | {ok, Pid, State} | {error, Reason}
  • Node = node()
  • Pid = pid()
  • State = term()
  • Reason = term()
41 | 42 | This function is called whenever an application is started using 43 | application:start/[1,2], and should start the processes of the 44 | application. If the application is structured according to the OTP 45 | design principles as a supervision tree, this means starting the 46 | top supervisor of the tree. 47 | 48 | 49 | 50 | ### stop/1 ### 51 | 52 |

53 | stop(State) -> ok
54 | 
55 | 56 |
  • State = term()
57 | 58 | This function is called whenever an application has stopped. It 59 | is intended to be the opposite of Module:start/2 and should do 60 | any necessary cleaning up. The return value is ignored. 61 | 62 | -------------------------------------------------------------------------------- /doc/sc_push_lib_sup.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_lib_sup # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | __Behaviours:__ [`supervisor`](supervisor.md). 8 | 9 | 10 | 11 | ## Function Index ## 12 | 13 | 14 |
init/1
start_link/0
15 | 16 | 17 | 18 | 19 | ## Function Details ## 20 | 21 | 22 | 23 | ### init/1 ### 24 | 25 | `init(X1) -> any()` 26 | 27 | 28 | 29 | ### start_link/0 ### 30 | 31 | `start_link() -> any()` 32 | 33 | -------------------------------------------------------------------------------- /doc/sc_push_req_mgr.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_req_mgr # 4 | * [Data Types](#types) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Copyright (c) (C) 2012,2013 Silent Circle LLC 9 | 10 | __Behaviours:__ [`gen_server`](gen_server.md). 11 | 12 | __Authors:__ Edwin Fine. 13 | 14 | 15 | 16 | ## Data Types ## 17 | 18 | 19 | 20 | 21 | ### req_prop() ### 22 | 23 | 24 |

 25 | req_prop() = sc_types:prop(atom(), term())
 26 | 
27 | 28 | 29 | 30 | 31 | ### req_props() ### 32 | 33 | 34 |

 35 | req_props() = [req_prop()]
 36 | 
37 | 38 | 39 | 40 | ## Function Index ## 41 | 42 | 43 |
add/2Add a request.
all_req/0Return all requests as a list of proplists.
default_callback/1Default callback.
lookup/1 44 | Lookup a request.
remove/1Remove a request and return it if it was there, or undefined.
remove_all/0Remove all requests.
start_link/0 45 | Start the server.
sweep/0Manually kick off an asynchronous sweep for aged-out requests.
sweep/1Manually kick off a sync sweep for requests, specifying max age in 46 | seconds.
sync_sweep/0Manually kick off a synchronous sweep for aged-out requests.
sync_sweep/1Manually kick off a sync sweep for requests, specifying max age in 47 | seconds.
48 | 49 | 50 | 51 | 52 | ## Function Details ## 53 | 54 | 55 | 56 | ### add/2 ### 57 | 58 |

 59 | add(Id::term(), Req::term()) -> ok
 60 | 
61 |
62 | 63 | Add a request. 64 | 65 | 66 | 67 | ### all_req/0 ### 68 | 69 |

 70 | all_req() -> [req_props()]
 71 | 
72 |
73 | 74 | Return all requests as a list of proplists. 75 | 76 | 77 | 78 | ### default_callback/1 ### 79 | 80 | `default_callback(Props) -> any()` 81 | 82 | Default callback. Does nothing, returns ok. 83 | 84 | 85 | 86 | ### lookup/1 ### 87 | 88 |

 89 | lookup(Id::term()) -> Req::req_props() | undefined
 90 | 
91 |
92 | 93 | Lookup a request. 94 | 95 | 96 | 97 | ### remove/1 ### 98 | 99 |

100 | remove(Id::term()) -> Req::req_props() | undefined
101 | 
102 |
103 | 104 | Remove a request and return it if it was there, or undefined. 105 | 106 | 107 | 108 | ### remove_all/0 ### 109 | 110 |

111 | remove_all() -> ok
112 | 
113 |
114 | 115 | Remove all requests. 116 | 117 | 118 | 119 | ### start_link/0 ### 120 | 121 |

122 | start_link() -> {ok, pid()} | ignore | {error, term()}
123 | 
124 |
125 | 126 | Start the server. 127 | 128 | 129 | 130 | ### sweep/0 ### 131 | 132 |

133 | sweep() -> ok
134 | 
135 |
136 | 137 | Manually kick off an asynchronous sweep for aged-out requests. 138 | 139 | 140 | 141 | ### sweep/1 ### 142 | 143 |

144 | sweep(MaxAge::non_neg_integer()) -> ok
145 | 
146 |
147 | 148 | Manually kick off a sync sweep for requests, specifying max age in 149 | seconds. If max age is negative, all requests will be removed. 150 | 151 | 152 | 153 | ### sync_sweep/0 ### 154 | 155 |

156 | sync_sweep() -> {ok, NumDeleted::non_neg_integer()}
157 | 
158 |
159 | 160 | Manually kick off a synchronous sweep for aged-out requests. 161 | 162 | 163 | 164 | ### sync_sweep/1 ### 165 | 166 |

167 | sync_sweep(MaxAge::non_neg_integer()) -> {ok, NumDeleted::non_neg_integer()}
168 | 
169 |
170 | 171 | Manually kick off a sync sweep for requests, specifying max age in 172 | seconds. If max age is negative, all requests will be removed. 173 | 174 | -------------------------------------------------------------------------------- /doc/sc_push_sup.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_sup # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | __Behaviours:__ [`supervisor`](supervisor.md). 8 | 9 | 10 | 11 | ## Function Index ## 12 | 13 | 14 |
init/1
start/1
start_link/1
stop/1
15 | 16 | 17 | 18 | 19 | ## Function Details ## 20 | 21 | 22 | 23 | ### init/1 ### 24 | 25 | `init(Opts) -> any()` 26 | 27 | 28 | 29 | ### start/1 ### 30 | 31 | `start(Opts) -> any()` 32 | 33 | 34 | 35 | ### start_link/1 ### 36 | 37 | `start_link(Opts) -> any()` 38 | 39 | 40 | 41 | ### stop/1 ### 42 | 43 | `stop(SupRef) -> any()` 44 | 45 | -------------------------------------------------------------------------------- /doc/sc_push_svc_null.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_svc_null # 4 | * [Data Types](#types) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | __Behaviours:__ [`supervisor`](supervisor.md). 9 | 10 | 11 | 12 | ## Data Types ## 13 | 14 | 15 | 16 | 17 | ### session_config() ### 18 | 19 | 20 |

 21 | session_config() = [session_opt()]
 22 | 
23 | 24 | 25 | 26 | 27 | ### session_configs() ### 28 | 29 | 30 |

 31 | session_configs() = [session_config()]
 32 | 
33 | 34 | 35 | 36 | 37 | ### session_opt() ### 38 | 39 | 40 |

 41 | session_opt() = {mod, atom()} | {name, atom()} | {config, proplists:proplist()}
 42 | 
43 | 44 | 45 | 46 | ## Function Index ## 47 | 48 | 49 |
async_send/2Asynchronously send notification to named session.
async_send/3Asynchronously send notification to named session with options Opts.
get_session_pid/1Get pid of named session.
init/1Opts is a list of proplists.
quiesce_session/1Quiesce named session.
send/2Send notification to named session.
send/3Send notification to named session with options Opts.
start/1
start_link/1Opts is a list of proplists.
start_session/1
stop/1
stop_session/1Stop named session.
50 | 51 | 52 | 53 | 54 | ## Function Details ## 55 | 56 | 57 | 58 | ### async_send/2 ### 59 | 60 |

 61 | async_send(Name::term(), Notification::sc_types:proplist(atom(), term())) -> ok | {error, Reason::term()}
 62 | 
63 |
64 | 65 | Asynchronously send notification to named session. 66 | 67 | 68 | 69 | ### async_send/3 ### 70 | 71 |

 72 | async_send(Name::term(), Notification::sc_types:proplist(atom(), term()), Opts::list()) -> ok | {error, Reason::term()}
 73 | 
74 |
75 | 76 | Asynchronously send notification to named session with options Opts. 77 | 78 | 79 | 80 | ### get_session_pid/1 ### 81 | 82 |

 83 | get_session_pid(Name::atom()) -> pid() | undefined
 84 | 
85 |
86 | 87 | Get pid of named session. 88 | 89 | 90 | 91 | ### init/1 ### 92 | 93 |

 94 | init(Opts) -> Result
 95 | 
96 | 97 |
  • Opts = session_configs()
  • Result = {ok, {SupFlags, Children}} | ignore
  • SupFlags = {one_for_one, non_neg_integer(), pos_integer()}
  • Children = [{term(), {Mod::atom(), start_link, Args::[any()]}, permanent, non_neg_integer(), worker, [atom()]}]
98 | 99 | `Opts` is a list of proplists. 100 | Each proplist is a session definition containing 101 | name, mod, and config keys. 102 | 103 | 104 | 105 | ### quiesce_session/1 ### 106 | 107 |

108 | quiesce_session(Name) -> Result
109 | 
110 | 111 |
  • Name = atom()
  • Result = ok | {error, Reason}
  • Reason = term()
112 | 113 | Quiesce named session. 114 | This signals the session to prepare for shutdown by refusing to 115 | accept any more notifications, but still servicing in-flight 116 | requests. 117 | 118 | 119 | 120 | ### send/2 ### 121 | 122 |

123 | send(Name::term(), Notification::sc_types:proplist(atom(), term())) -> {ok, Ref::term()} | {error, Reason::term()}
124 | 
125 |
126 | 127 | Send notification to named session. 128 | 129 | 130 | 131 | ### send/3 ### 132 | 133 |

134 | send(Name::term(), Notification::sc_types:proplist(atom(), term()), Opts::list()) -> {ok, Ref::term()} | {error, Reason::term()}
135 | 
136 |
137 | 138 | Send notification to named session with options Opts. 139 | 140 | 141 | 142 | ### start/1 ### 143 | 144 |

145 | start(Opts) -> Result
146 | 
147 | 148 | 149 | 150 | 151 | 152 | ### start_link/1 ### 153 | 154 |

155 | start_link(Opts) -> Result
156 | 
157 | 158 | 159 | 160 | `Opts` is a list of proplists. 161 | Each proplist is a session definition containing 162 | name, mod, and config keys. 163 | 164 | 165 | 166 | ### start_session/1 ### 167 | 168 |

169 | start_session(Opts) -> Result
170 | 
171 | 172 |
  • Opts = proplists:proplist()
  • Result = {ok, pid()} | {error, already_started} | {error, Reason}
  • Reason = term()
173 | 174 | 175 | 176 | ### stop/1 ### 177 | 178 | `stop(SupRef) -> any()` 179 | 180 | 181 | 182 | ### stop_session/1 ### 183 | 184 |

185 | stop_session(Name::atom()) -> ok | {error, Reason::term()}
186 | 
187 |
188 | 189 | Stop named session. 190 | 191 | -------------------------------------------------------------------------------- /doc/sc_push_svc_null_srv.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_svc_null_srv # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | Null push service. 10 | 11 | __Behaviours:__ [`gen_server`](gen_server.md). 12 | 13 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 14 | 15 | 16 | 17 | ## Description ## 18 | 19 | This is a do-nothing module that provides the 20 | required interfaces. Push notifications will always succeed (and, 21 | of course, not go anywhere) unless otherwise configured in the 22 | notification. This supports standalone testing. 23 | 24 | Sessions must have unique names within the node because they are 25 | registered. If more concurrency is desired, a session may elect to 26 | become a supervisor of other session, or spawn a pool of processes, 27 | or whatever it takes. 28 | 29 | 30 | 31 | ## Data Types ## 32 | 33 | 34 | 35 | 36 | ### opt() ### 37 | 38 | 39 |

 40 | opt() = {log_dest, {file, string()}} | {log_level, off | info}
 41 | 
42 | 43 | default = info 44 | 45 | 46 | 47 | ### uuid() ### 48 | 49 | 50 |

 51 | uuid() = binary()
 52 | 
53 | 54 | 55 | 56 | ## Function Index ## 57 | 58 | 59 |
async_send/2Equivalent to async_send(SvrRef, Notification, []).
async_send/3Asynchronously send a notification specified by Notification via 60 | SvrRef with options Opts.
send/2Equivalent to send(SvrRef, Notification, []).
send/3Send a notification specified by Notification via SvrRef 61 | with options Opts.
start/2Start a named session as described by the options Opts.
start_link/2Start a named session as described by the options Opts.
stop/1Stop session.
62 | 63 | 64 | 65 | 66 | ## Function Details ## 67 | 68 | 69 | 70 | ### async_send/2 ### 71 | 72 |

 73 | async_send(SvrRef::term(), Notification::list()) -> {ok, {submitted, uuid()}} | {error, {uuid(), term()}}
 74 | 
75 |
76 | 77 | Equivalent to [`async_send(SvrRef, Notification, [])`](#async_send-3). 78 | 79 | 80 | 81 | ### async_send/3 ### 82 | 83 |

 84 | async_send(SvrRef::term(), Notification::list(), Opts::list()) -> {ok, {submitted, uuid()}} | {error, {uuid(), term()}}
 85 | 
86 |
87 | 88 | Asynchronously send a notification specified by `Notification` via 89 | `SvrRef` with options `Opts`. 90 | 91 | 92 | #### Parameters #### 93 | 94 | For parameter descriptions, see [`send/3`](#send-3). 95 | 96 | 97 | 98 | ### send/2 ### 99 | 100 |

101 | send(SvrRef::term(), Notification::list()) -> {ok, {uuid(), list()}} | {error, {uuid(), {Reason::term()}}}
102 | 
103 |
104 | 105 | Equivalent to [`send(SvrRef, Notification, [])`](#send-3). 106 | 107 | 108 | 109 | ### send/3 ### 110 | 111 |

112 | send(SvrRef::term(), Notification::list(), Opts::list()) -> {ok, {uuid(), list()}} | {error, {uuid(), Reason::term()}}
113 | 
114 |
115 | 116 | Send a notification specified by `Notification` via `SvrRef` 117 | with options `Opts`. 118 | 119 | 120 | #### Notification format #### 121 | 122 | ``` 123 | [ 124 | {return, success | {error, term()}}, % default = success 125 | ] 126 | ``` 127 | 128 | 129 | #### Options #### 130 | 131 | Not currently supported, will accept any list. 132 | 133 | 134 | 135 | ### start/2 ### 136 | 137 |

138 | start(Name::atom(), Opts::[opt()]) -> term()
139 | 
140 |
141 | 142 | Start a named session as described by the options `Opts`. The name 143 | `Name` is registered so that the session can be referenced using the name to 144 | call functions like send/2. Note that this function is only used 145 | for testing; see start_link/2. 146 | 147 | 148 | 149 | ### start_link/2 ### 150 | 151 |

152 | start_link(Name::atom(), Opts::[opt()]) -> term()
153 | 
154 |
155 | 156 | Start a named session as described by the options `Opts`. The name 157 | `Name` is registered so that the session can be referenced using the name to 158 | call functions like send/2. 159 | 160 | 161 | 162 | ### stop/1 ### 163 | 164 |

165 | stop(SvrRef::term()) -> term()
166 | 
167 |
168 | 169 | Stop session. 170 | 171 | -------------------------------------------------------------------------------- /doc/sc_push_top.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_top # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | 8 | 9 | ## Function Index ## 10 | 11 | 12 |
info/0
13 | 14 | 15 | 16 | 17 | ## Function Details ## 18 | 19 | 20 | 21 | ### info/0 ### 22 | 23 | `info() -> any()` 24 | 25 | -------------------------------------------------------------------------------- /doc/sc_push_wm_common.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_wm_common # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | 8 | 9 | ## Function Index ## 10 | 11 | 12 |
add_result/2
bad_request/3
encode_ref/1
ensure_path_item/2
ensure_path_items/2
ensure_path_items/3
error_to_json/1
get_media_type/1
parse_json/1
result_to_json/1
results_to_json/1
send_push/1
store_prop/2
store_props/2
13 | 14 | 15 | 16 | 17 | ## Function Details ## 18 | 19 | 20 | 21 | ### add_result/2 ### 22 | 23 |

 24 | add_result(ReqData::sc_push_wm_helper:wrq(), Results::list()) -> sc_push_wm_helper:wrq()
 25 | 
26 |
27 | 28 | 29 | 30 | ### bad_request/3 ### 31 | 32 | `bad_request(ReqData, Ctx, Msg) -> any()` 33 | 34 | 35 | 36 | ### encode_ref/1 ### 37 | 38 |

 39 | encode_ref(Ref::term()) -> binary()
 40 | 
41 |
42 | 43 | 44 | 45 | ### ensure_path_item/2 ### 46 | 47 | `ensure_path_item(Key, ReqData) -> any()` 48 | 49 | 50 | 51 | ### ensure_path_items/2 ### 52 | 53 | `ensure_path_items(Keys, ReqData) -> any()` 54 | 55 | 56 | 57 | ### ensure_path_items/3 ### 58 | 59 | `ensure_path_items(Rest, ReqData, Acc) -> any()` 60 | 61 | 62 | 63 | ### error_to_json/1 ### 64 | 65 | `error_to_json(L) -> any()` 66 | 67 | 68 | 69 | ### get_media_type/1 ### 70 | 71 |

 72 | get_media_type(CT::string()) -> string()
 73 | 
74 |
75 | 76 | 77 | 78 | ### parse_json/1 ### 79 | 80 |

 81 | parse_json(ReqBody::binary()) -> {ok, sc_types:proplist(atom(), string() | binary())} | {error, binary()}
 82 | 
83 |
84 | 85 | 86 | 87 | ### result_to_json/1 ### 88 | 89 | `result_to_json(X1) -> any()` 90 | 91 | 92 | 93 | ### results_to_json/1 ### 94 | 95 | `results_to_json(Results) -> any()` 96 | 97 | 98 | 99 | ### send_push/1 ### 100 | 101 |

102 | send_push(Notification) -> Result
103 | 
104 | 105 | 106 | 107 | 108 | 109 | ### store_prop/2 ### 110 | 111 | `store_prop(NewProp, PL) -> any()` 112 | 113 | 114 | 115 | ### store_props/2 ### 116 | 117 | `store_props(Props, PL) -> any()` 118 | 119 | -------------------------------------------------------------------------------- /doc/sc_push_wm_send_device.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_wm_send_device # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | webmachine resource that handles the push notification component 10 | of the REST API. 11 | 12 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 13 | 14 | 15 | 16 | ## Description ## 17 | See [`https://github.com/basho/webmachine/wiki/Resource-Functions`](https://github.com/basho/webmachine/wiki/Resource-Functions) for a 18 | description of the resource functions used in this module. 19 | 20 | 21 | ## Data Types ## 22 | 23 | 24 | 25 | 26 | ### ctx() ### 27 | 28 | 29 |

 30 | ctx() = #ctx{}
 31 | 
32 | 33 | Context record. 34 | 35 | 36 | 37 | ## Function Index ## 38 | 39 | 40 |
allow_missing_post/2
allowed_methods/2
finish_request/2
init/1
malformed_request/2
post_is_create/2
process_post/2
resource_exists/2
valid_content_headers/2
41 | 42 | 43 | 44 | 45 | ## Function Details ## 46 | 47 | 48 | 49 | ### allow_missing_post/2 ### 50 | 51 |

 52 | allow_missing_post(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 53 | 
54 |
55 | 56 | 57 | 58 | ### allowed_methods/2 ### 59 | 60 |

 61 | allowed_methods(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> {list(), sc_push_wm_helper:wrq(), ctx()}
 62 | 
63 |
64 | 65 | 66 | 67 | ### finish_request/2 ### 68 | 69 |

 70 | finish_request(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 71 | 
72 |
73 | 74 | 75 | 76 | ### init/1 ### 77 | 78 |

 79 | init(Config::sc_push_wm_helper:config()) -> {sc_push_wm_helper:debug_info(), ctx()}
 80 | 
81 |
82 | 83 | 84 | 85 | ### malformed_request/2 ### 86 | 87 |

 88 | malformed_request(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 89 | 
90 |
91 | 92 | 93 | 94 | ### post_is_create/2 ### 95 | 96 |

 97 | post_is_create(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 98 | 
99 |
100 | 101 | 102 | 103 | ### process_post/2 ### 104 | 105 |

106 | process_post(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
107 | 
108 |
109 | 110 | 111 | 112 | ### resource_exists/2 ### 113 | 114 |

115 | resource_exists(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
116 | 
117 |
118 | 119 | 120 | 121 | ### valid_content_headers/2 ### 122 | 123 |

124 | valid_content_headers(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
125 | 
126 |
127 | 128 | -------------------------------------------------------------------------------- /doc/sc_push_wm_send_svc_appid_tok.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_wm_send_svc_appid_tok # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | webmachine resource that handles the push notification component 10 | of the REST API. 11 | 12 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 13 | 14 | 15 | 16 | ## Description ## 17 | See [`https://github.com/basho/webmachine/wiki/Resource-Functions`](https://github.com/basho/webmachine/wiki/Resource-Functions) for a 18 | description of the resource functions used in this module. 19 | 20 | 21 | ## Data Types ## 22 | 23 | 24 | 25 | 26 | ### ctx() ### 27 | 28 | 29 |

 30 | ctx() = #ctx{}
 31 | 
32 | 33 | Context record. 34 | 35 | 36 | 37 | ## Function Index ## 38 | 39 | 40 |
allow_missing_post/2
allowed_methods/2
finish_request/2
init/1
malformed_request/2
post_is_create/2
process_post/2
resource_exists/2
valid_content_headers/2
41 | 42 | 43 | 44 | 45 | ## Function Details ## 46 | 47 | 48 | 49 | ### allow_missing_post/2 ### 50 | 51 |

 52 | allow_missing_post(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 53 | 
54 |
55 | 56 | 57 | 58 | ### allowed_methods/2 ### 59 | 60 |

 61 | allowed_methods(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> {list(), sc_push_wm_helper:wrq(), ctx()}
 62 | 
63 |
64 | 65 | 66 | 67 | ### finish_request/2 ### 68 | 69 |

 70 | finish_request(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 71 | 
72 |
73 | 74 | 75 | 76 | ### init/1 ### 77 | 78 |

 79 | init(Config::sc_push_wm_helper:config()) -> {sc_push_wm_helper:debug_info(), ctx()}
 80 | 
81 |
82 | 83 | 84 | 85 | ### malformed_request/2 ### 86 | 87 |

 88 | malformed_request(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 89 | 
90 |
91 | 92 | 93 | 94 | ### post_is_create/2 ### 95 | 96 |

 97 | post_is_create(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 98 | 
99 |
100 | 101 | 102 | 103 | ### process_post/2 ### 104 | 105 |

106 | process_post(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
107 | 
108 |
109 | 110 | 111 | 112 | ### resource_exists/2 ### 113 | 114 |

115 | resource_exists(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
116 | 
117 |
118 | 119 | 120 | 121 | ### valid_content_headers/2 ### 122 | 123 |

124 | valid_content_headers(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
125 | 
126 |
127 | 128 | -------------------------------------------------------------------------------- /doc/sc_push_wm_send_svc_tok.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_wm_send_svc_tok # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | webmachine resource that handles the push notification component 10 | of the REST API. 11 | 12 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 13 | 14 | 15 | 16 | ## Description ## 17 | See [`https://github.com/basho/webmachine/wiki/Resource-Functions`](https://github.com/basho/webmachine/wiki/Resource-Functions) for a 18 | description of the resource functions used in this module. 19 | 20 | 21 | ## Data Types ## 22 | 23 | 24 | 25 | 26 | ### ctx() ### 27 | 28 | 29 |

 30 | ctx() = #ctx{}
 31 | 
32 | 33 | Context record. 34 | 35 | 36 | 37 | ## Function Index ## 38 | 39 | 40 |
allow_missing_post/2
allowed_methods/2
finish_request/2
init/1
malformed_request/2
post_is_create/2
process_post/2
resource_exists/2
valid_content_headers/2
41 | 42 | 43 | 44 | 45 | ## Function Details ## 46 | 47 | 48 | 49 | ### allow_missing_post/2 ### 50 | 51 |

 52 | allow_missing_post(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 53 | 
54 |
55 | 56 | 57 | 58 | ### allowed_methods/2 ### 59 | 60 |

 61 | allowed_methods(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> {list(), sc_push_wm_helper:wrq(), ctx()}
 62 | 
63 |
64 | 65 | 66 | 67 | ### finish_request/2 ### 68 | 69 |

 70 | finish_request(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 71 | 
72 |
73 | 74 | 75 | 76 | ### init/1 ### 77 | 78 |

 79 | init(Config::sc_push_wm_helper:config()) -> {sc_push_wm_helper:debug_info(), ctx()}
 80 | 
81 |
82 | 83 | 84 | 85 | ### malformed_request/2 ### 86 | 87 |

 88 | malformed_request(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 89 | 
90 |
91 | 92 | 93 | 94 | ### post_is_create/2 ### 95 | 96 |

 97 | post_is_create(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 98 | 
99 |
100 | 101 | 102 | 103 | ### process_post/2 ### 104 | 105 |

106 | process_post(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
107 | 
108 |
109 | 110 | 111 | 112 | ### resource_exists/2 ### 113 | 114 |

115 | resource_exists(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
116 | 
117 |
118 | 119 | 120 | 121 | ### valid_content_headers/2 ### 122 | 123 |

124 | valid_content_headers(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
125 | 
126 |
127 | 128 | -------------------------------------------------------------------------------- /doc/sc_push_wm_send_tag.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_wm_send_tag # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | webmachine resource that handles the push notification component 10 | of the REST API. 11 | 12 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 13 | 14 | 15 | 16 | ## Description ## 17 | See [`https://github.com/basho/webmachine/wiki/Resource-Functions`](https://github.com/basho/webmachine/wiki/Resource-Functions) for a 18 | description of the resource functions used in this module. 19 | 20 | 21 | ## Data Types ## 22 | 23 | 24 | 25 | 26 | ### ctx() ### 27 | 28 | 29 |

 30 | ctx() = #ctx{}
 31 | 
32 | 33 | Context record. 34 | 35 | 36 | 37 | ## Function Index ## 38 | 39 | 40 |
allow_missing_post/2
allowed_methods/2
finish_request/2
init/1
malformed_request/2
post_is_create/2
process_post/2
resource_exists/2
valid_content_headers/2
41 | 42 | 43 | 44 | 45 | ## Function Details ## 46 | 47 | 48 | 49 | ### allow_missing_post/2 ### 50 | 51 |

 52 | allow_missing_post(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 53 | 
54 |
55 | 56 | 57 | 58 | ### allowed_methods/2 ### 59 | 60 |

 61 | allowed_methods(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> {list(), sc_push_wm_helper:wrq(), ctx()}
 62 | 
63 |
64 | 65 | 66 | 67 | ### finish_request/2 ### 68 | 69 |

 70 | finish_request(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 71 | 
72 |
73 | 74 | 75 | 76 | ### init/1 ### 77 | 78 |

 79 | init(Config::sc_push_wm_helper:config()) -> {sc_push_wm_helper:debug_info(), ctx()}
 80 | 
81 |
82 | 83 | 84 | 85 | ### malformed_request/2 ### 86 | 87 |

 88 | malformed_request(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 89 | 
90 |
91 | 92 | 93 | 94 | ### post_is_create/2 ### 95 | 96 |

 97 | post_is_create(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
 98 | 
99 |
100 | 101 | 102 | 103 | ### process_post/2 ### 104 | 105 |

106 | process_post(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
107 | 
108 |
109 | 110 | 111 | 112 | ### resource_exists/2 ### 113 | 114 |

115 | resource_exists(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
116 | 
117 |
118 | 119 | 120 | 121 | ### valid_content_headers/2 ### 122 | 123 |

124 | valid_content_headers(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
125 | 
126 |
127 | 128 | -------------------------------------------------------------------------------- /doc/sc_push_wm_sup.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_wm_sup # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Supervisor for the webmachine part of the sc_push application. 9 | 10 | __Behaviours:__ [`supervisor`](supervisor.md). 11 | 12 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 13 | 14 | 15 | 16 | ## Function Index ## 17 | 18 | 19 |
init/1supervisor callback.
start_link/0API for starting the supervisor.
start_link/1API for starting the supervisor.
upgrade/0Add processes if necessary.
20 | 21 | 22 | 23 | 24 | ## Function Details ## 25 | 26 | 27 | 28 | ### init/1 ### 29 | 30 |

31 | init(Env::[]) -> SupervisorTree
32 | 
33 |
34 | 35 | supervisor callback. 36 | 37 | 38 | 39 | ### start_link/0 ### 40 | 41 |

42 | start_link() -> ServerRet
43 | 
44 |
45 | 46 | API for starting the supervisor. 47 | 48 | 49 | 50 | ### start_link/1 ### 51 | 52 |

53 | start_link(Env) -> ServerRet
54 | 
55 |
56 | 57 | API for starting the supervisor. 58 | 59 | 60 | 61 | ### upgrade/0 ### 62 | 63 |

64 | upgrade() -> ok
65 | 
66 |
67 | 68 | Add processes if necessary. 69 | 70 | -------------------------------------------------------------------------------- /doc/sc_push_wm_version.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_push_wm_version # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | webmachine resource that handles the version endpoint. 10 | 11 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 12 | 13 | 14 | 15 | ## Description ## 16 | See [`https://github.com/basho/webmachine/wiki/Resource-Functions`](https://github.com/basho/webmachine/wiki/Resource-Functions) for a 17 | description of the resource functions used in this module. 18 | 19 | 20 | ## Data Types ## 21 | 22 | 23 | 24 | 25 | ### ctx() ### 26 | 27 | 28 |

29 | ctx() = #ctx{}
30 | 
31 | 32 | Context record. 33 | 34 | 35 | 36 | ## Function Index ## 37 | 38 | 39 |
allowed_methods/2
content_types_provided/2
init/1
resource_exists/2
to_json/2
40 | 41 | 42 | 43 | 44 | ## Function Details ## 45 | 46 | 47 | 48 | ### allowed_methods/2 ### 49 | 50 |

51 | allowed_methods(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> {list(), sc_push_wm_helper:wrq(), ctx()}
52 | 
53 |
54 | 55 | 56 | 57 | ### content_types_provided/2 ### 58 | 59 |

60 | content_types_provided(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> {list(), sc_push_wm_helper:wrq(), ctx()}
61 | 
62 |
63 | 64 | 65 | 66 | ### init/1 ### 67 | 68 |

69 | init(Config::sc_push_wm_helper:config()) -> {sc_push_wm_helper:debug_info(), ctx()}
70 | 
71 |
72 | 73 | 74 | 75 | ### resource_exists/2 ### 76 | 77 |

78 | resource_exists(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> sc_push_wm_helper:wbool_ret()
79 | 
80 |
81 | 82 | 83 | 84 | ### to_json/2 ### 85 | 86 |

87 | to_json(ReqData::sc_push_wm_helper:wrq(), Ctx::ctx()) -> {sc_push_wm_helper:json(), sc_push_wm_helper:wrq(), ctx()}
88 | 
89 |
90 | 91 | -------------------------------------------------------------------------------- /doc/sc_types.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_types # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | 7 | Common type specifications. 8 | 9 | Copyright (c) 2015 Silent Circle 10 | 11 | __Authors:__ Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 12 | 13 | 14 | 15 | ## Data Types ## 16 | 17 | 18 | 19 | 20 | ### atom_or_binary() ### 21 | 22 | 23 |

 24 | atom_or_binary() = atom() | binary()
 25 | 
26 | 27 | 28 | 29 | 30 | ### atom_or_string() ### 31 | 32 | 33 |

 34 | atom_or_string() = atom() | string()
 35 | 
36 | 37 | 38 | 39 | 40 | ### binary_or_string() ### 41 | 42 | 43 |

 44 | binary_or_string() = binary() | string()
 45 | 
46 | 47 | 48 | 49 | 50 | ### exml() ### 51 | 52 | 53 |

 54 | exml() = #xmlelement{}
 55 | 
56 | 57 | 58 | 59 | 60 | ### gen_result() ### 61 | 62 | 63 |

 64 | gen_result() = {result, exml()} | {error, exml()}
 65 | 
66 | 67 | 68 | 69 | 70 | ### posix_time() ### 71 | 72 | 73 |

 74 | posix_time() = integer()
 75 | 
76 | 77 | 78 | 79 | 80 | ### prop() ### 81 | 82 | 83 |

 84 | prop(KT, VT) = {KT, VT}
 85 | 
86 | 87 | 88 | 89 | 90 | ### proplist() ### 91 | 92 | 93 |

 94 | proplist(KT, VT) = [prop(KT, VT)]
 95 | 
96 | 97 | 98 | 99 | 100 | ### reg_prop() ### 101 | 102 | 103 |

104 | reg_prop() = prop(atom(), atom_or_binary())
105 | 
106 | 107 | 108 | 109 | 110 | ### reg_proplist() ### 111 | 112 | 113 |

114 | reg_proplist() = [reg_prop(), ...]
115 | 
116 | 117 | 118 | 119 | 120 | ### reg_result() ### 121 | 122 | 123 |

124 | reg_result() = ok | {error, term()}
125 | 
126 | 127 | -------------------------------------------------------------------------------- /doc/sc_util_app.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module sc_util_app # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | Utility functions to manage applications and their configurations. 10 | 11 | Copyright (c) 2015 Silent Circle 12 | 13 | __Authors:__ Sebastien Merle, Edwin Fine ([`efine@silentcircle.com`](mailto:efine@silentcircle.com)). 14 | 15 | 16 | 17 | ## Data Types ## 18 | 19 | 20 | 21 | 22 | ### app_info() ### 23 | 24 | 25 |

 26 | app_info() = {Application::atom(), Description::string(), Version::string()}
 27 | 
28 | 29 | 30 | 31 | 32 | ### app_info_list() ### 33 | 34 | 35 |

 36 | app_info_list() = [app_info()]
 37 | 
38 | 39 | 40 | 41 | 42 | ### app_state() ### 43 | 44 | 45 |

 46 | app_state() = loading | loaded | starting | started | start_p_false | running
 47 | 
48 | 49 | 50 | 51 | 52 | ### app_state_info() ### 53 | 54 | 55 |

 56 | app_state_info() = {app_state(), app_info_list()}
 57 | 
58 | 59 | 60 | 61 | 62 | ### app_states() ### 63 | 64 | 65 |

 66 | app_states() = [app_state_info()]
 67 | 
68 | 69 | 70 | 71 | ## Function Index ## 72 | 73 | 74 |
get_app_info/0Return states of all applications.
merge_config/2Merge a list of application configurations and a list of overrides.
start_applications/1Starts a list of application.
start_applications/2Start a list of applications, using specified options.
75 | 76 | 77 | 78 | 79 | ## Function Details ## 80 | 81 | 82 | 83 | ### get_app_info/0 ### 84 | 85 |

 86 | get_app_info() -> app_states()
 87 | 
88 |
89 | 90 | Return states of all applications. 91 | 92 | 93 | 94 | ### merge_config/2 ### 95 | 96 |

 97 | merge_config(Config, Overrides) -> Config
 98 | 
99 | 100 | 101 | 102 | Merge a list of application configurations and a list of overrides. 103 | Guarantee the order of the keys; the keys from the configuration will be 104 | first and in the same order they were specified, then the keys from 105 | the override in the same order they were specified. 106 | 107 | Note that this function is not very efficient and should not be used 108 | intensively. 109 | 110 | Configuration and override are proplists, e.g: 111 | 112 | ``` 113 | Config = [{app1, [{foo, 1}, {bar, [{spam, 2}]}, {buz, 3}]}], 114 | Override = [{app1, [{foo, a}, {biz, b}, {bar, [{spam, c}, {eggs, d}]}]}], 115 | sc_util_app:merge_config(Config, Override). 116 | -> [{app1, [{foo, a}, {bar, [{spam, c}, {eggs, d}]}, {buz, 3}, {biz, b}]}] 117 | ``` 118 | 119 | 120 | 121 | ### start_applications/1 ### 122 | 123 |

124 | start_applications(Apps) -> {ok, StartedApps} | {error, Reason}
125 | 
126 | 127 | 128 | 129 | Starts a list of application. Same as start_applications(Apps, []). 130 | 131 | 132 | 133 | ### start_applications/2 ### 134 | 135 |

136 | start_applications(Apps, Options) -> Result
137 | 
138 | 139 |
  • Apps = proplists:proplist()
  • Options = [Option]
  • Option = {logger, Logger}
  • Logger = fun((Level, Format, Args) -> ok)
  • Level = info | warning
  • Format = nonempty_string()
  • Args = [term()]
  • Result = {ok, StartedApps} | {error, Reason}
  • StartedApps = [atom()]
  • Reason = term()
140 | 141 | Start a list of applications, using specified options. 142 | The applications are specified as a proplist like 143 | 144 | ``` 145 | [{app1_name, app1_config}, {app2_name, app2_config}] 146 | ``` 147 | 148 | like in sys.config. 149 | 150 | * All the dependencies will be started in the proper order. 151 | 152 | * If a dependency is not specified it will be started without setting 153 | any environment for it. 154 | 155 | * If a specified application is already started, it will not be started 156 | again and its environment will not be changed; it will only log a warning. 157 | 158 | 159 | -------------------------------------------------------------------------------- /erts-version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %% 3 | main(_) -> 4 | io:format("erts-~s\n", [erlang:system_info(version)]). 5 | -------------------------------------------------------------------------------- /examples/push_apns.escript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/escript 2 | 3 | %%==================================================================== 4 | %% Push a message to a token/appid combination. 5 | %%==================================================================== 6 | 7 | %% Internal exports 8 | main([SNode, SCookie, SAppId, SToken, SMsg]) -> 9 | RC = try 10 | run(SNode, SCookie, SAppId, SToken, SMsg) 11 | catch X:Y -> 12 | err_msg("***** ~p:~n~p~n", [X, Y]), 13 | err_msg("~p~n", [erlang:get_stacktrace()]), 14 | 1 15 | end, 16 | halt(RC); 17 | 18 | main(_) -> 19 | usage(). 20 | 21 | run(SNode, SCookie, SAppId, SToken, SMsg) -> 22 | Node = list_to_atom(SNode), 23 | connect_to_node(Node, list_to_atom(SCookie)), 24 | remote_push(Node, SMsg, SToken, SAppId). 25 | 26 | connect_to_node(Node, Cookie) -> 27 | start_distributed(derive_node_name(Node)), 28 | erlang:set_cookie(node(), Cookie), 29 | net_kernel:hidden_connect_node(Node). 30 | 31 | remote_push(Node, Msg, Tok, AppId) when is_list(Msg), 32 | is_list(Tok), 33 | is_list(AppId) -> 34 | Notification = [ 35 | {alert, list_to_binary(Msg)}, 36 | {sound,<<"something.wav">>} 37 | ], 38 | Result = do_rpc(Node, apns_erl_session_sup, 39 | get_child_pid, [list_to_atom(AppId)]), 40 | case Result of 41 | undefined -> 42 | err_msg("Unknown app id ~p~n", [AppId]), 43 | 1; 44 | Fsm when is_pid(Fsm) -> 45 | Token = do_rpc(Node, sc_util, hex_to_bitstring, [Tok]), 46 | JSON = do_rpc(Node, apns_json, make_notification, [Notification]), 47 | SendRes = do_rpc(Node, apns_erl_session, send, [Fsm, Token, JSON]), 48 | case SendRes of 49 | {ok, RefNo} -> 50 | err_msg("Sent msg, refno = ~B~n", [RefNo]), 51 | 0; 52 | Error -> 53 | err_msg("Failed to send msg: ~p~n", [Error]), 54 | 1 55 | end 56 | end. 57 | 58 | %%-------------------------------------------------------------------- 59 | %% usage 60 | %%-------------------------------------------------------------------- 61 | usage() -> 62 | err_msg("usage: ~s scpf-node cookie app-id apns-token message~n", 63 | [escript:script_name()]), 64 | halt(1). 65 | 66 | %%-------------------------------------------------------------------- 67 | %% err_msg 68 | %%-------------------------------------------------------------------- 69 | err_msg(Fmt, Args) -> 70 | io:format(standard_error, Fmt, Args). 71 | 72 | %%-------------------------------------------------------------------- 73 | %%% Perform an RPC call and throw on error 74 | %%%-------------------------------------------------------------------- 75 | do_rpc(Node, M, F, A) -> 76 | try rpc:call(Node, M, F, A) of 77 | {badrpc, Reason} -> 78 | err_msg("RPC Error: ~p~n", [Reason]), 79 | throw({rpcerror, {Reason, {Node, M, F, A}}}); 80 | Result -> 81 | Result 82 | catch _:Why -> 83 | throw(Why) 84 | end. 85 | 86 | start_distributed(ThisNode) -> 87 | case net_kernel:start([ThisNode, longnames]) of 88 | {ok, _Pid} -> 89 | ok; 90 | {error, {already_started, _Pid}} -> 91 | ok; 92 | {error, Reason} -> 93 | err_msg("Cannot start this node (~p) as a distributed node," 94 | " reason:~n~p~n", [ThisNode, Reason]), 95 | throw(Reason) 96 | end. 97 | 98 | nodehost(Node) when is_atom(Node) -> 99 | nodehost(atom_to_list(Node)); 100 | nodehost(SNode) when is_list(SNode) -> 101 | [_Name, Host] = string:tokens(SNode, "@"), 102 | Host. 103 | 104 | derive_node_name(Node) -> 105 | list_to_atom(atom_to_list(?MODULE) ++ "@" ++ nodehost(Node)). 106 | 107 | -------------------------------------------------------------------------------- /examples/recreate_node_db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | die() { 3 | echo $* >&2 4 | exit 1 5 | } 6 | 7 | dire_warning() { 8 | local app="$1"; shift 9 | local extra_db_nodes="$1"; shift 10 | 11 | echo "Mnesia directory: ${MNESIA_DIR}" 12 | echo -ne "Extra db nodes : ${extra_db_nodes}\n\n" 13 | 14 | cat < /dev/null 2>&1 && die "Please stop ${app} first" 44 | } 45 | 46 | recreate_node() { 47 | local this_node="$1"; shift 48 | local extra_db_nodes="$1"; shift 49 | local dir="\"${MNESIA_DIR}\"" 50 | rm -f ${MNESIA_DIR}/* 51 | erl -name ${this_node} \ 52 | -setcookie $(<${MNESIA_DIR}/.erlang.cookie) \ 53 | -noinput -noshell \ 54 | -mnesia dir "${dir}" \ 55 | -mnesia extra_db_nodes "${extra_db_nodes}" \ 56 | -s mnesia \ 57 | -s init stop 58 | } 59 | 60 | get_nodes() { 61 | local node=$1; shift 62 | local extra_db_nodes='[' 63 | 64 | while [[ -n "$node" ]]; do 65 | extra_db_nodes="${extra_db_nodes}'${node}'" 66 | node=$1; shift 67 | [[ -n "${node}" ]] && extra_db_nodes="${extra_db_nodes}," 68 | done 69 | 70 | extra_db_nodes="${extra_db_nodes}]" 71 | echo "${extra_db_nodes}" 72 | } 73 | 74 | usage() { 75 | die "usage: $(basename $0) appname this_erlang_node extra_db_node1 [extra_db_node2...]" 76 | } 77 | 78 | 79 | # 80 | # Main 81 | # 82 | [[ $# -ge 3 ]] || usage 83 | 84 | APP=$1; shift 85 | THIS_NODE="$1"; shift 86 | EXTRA_DB_NODES=$(get_nodes $*) 87 | 88 | MNESIA_DIR="/var/lib/${APP}" 89 | 90 | check_user "${APP}" 91 | check_app_stopped "${APP}" 92 | dire_warning "${APP}" "${EXTRA_DB_NODES}" 93 | recreate_node "${THIS_NODE}" "${EXTRA_DB_NODES}" 94 | -------------------------------------------------------------------------------- /examples/recreate_nodes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MNESIA_DIR="/var/lib/scpf" 3 | SCPF=/usr/sbin/scpf 4 | 5 | die() { 6 | echo $* >&2 7 | exit 1 8 | } 9 | 10 | dire_warning() { 11 | local extra_db_nodes="$1"; shift 12 | 13 | echo "Mnesia directory: ${MNESIA_DIR}" 14 | echo -ne "Extra db nodes : ${extra_db_nodes}\n\n" 15 | 16 | cat <<'END' 17 | **************************************************************************** 18 | ***** WARNING!!! ***** 19 | **************************************************************************** 20 | ***** 21 | ***** This will COMPLETELY DELETE ALL SCPF DATABASE TABLES on this node. 22 | ***** Are you REALLY sure you want to do this? 23 | ***** 24 | ***** Press CTRL-C to abort or ENTER to continue: 25 | **************************************************************************** 26 | END 27 | read 28 | } 29 | 30 | check_user() { 31 | [[ $(id -un) == 'scpf' ]] || die "This must be run as scpf user" 32 | } 33 | 34 | check_scpf_stopped() { 35 | ${SCPF} status > /dev/null 2>&1 && die "Please stop scpf first" 36 | } 37 | 38 | ping_node() { 39 | local this_node="$1"; shift 40 | local node=$1; shift 41 | 42 | erl -name ${this_node} \ 43 | -setcookie $(<${MNESIA_DIR}/.erlang.cookie) \ 44 | -noinput -noshell \ 45 | -kernel inet_dist_listen_min 40000 inet_dist_listen_max 40999 \ 46 | -eval "Res = net_adm:ping('$node'), io:format(\"~p~n\", [Res]), init:stop()." 47 | } 48 | 49 | check_connectivity() { 50 | local this_node="$1"; shift 51 | local node=$1; shift 52 | 53 | while [[ -n "$node" ]]; do 54 | echo -n "Pinging $node... " 55 | [[ $(ping_node $this_node $node) == "pong" ]] || die "Cannot ping $node" 56 | echo "OK" 57 | node=$1; shift 58 | done 59 | } 60 | 61 | recreate_node() { 62 | local this_node="$1"; shift 63 | local extra_db_nodes="$1"; shift 64 | local dir="\"${MNESIA_DIR}\"" 65 | local node1=$(echo $extra_db_nodes | cut -f1 -d' ') 66 | rm -f ${MNESIA_DIR}/* 67 | 68 | erl -name ${this_node} \ 69 | -setcookie $(<${MNESIA_DIR}/.erlang.cookie) \ 70 | -noinput -noshell \ 71 | -kernel inet_dist_listen_min 40000 inet_dist_listen_max 40999 \ 72 | -mnesia dir "${dir}" \ 73 | -mnesia extra_db_nodes "${extra_db_nodes}" \ 74 | -s mnesia \ 75 | -s init stop 76 | echo "RC=$?" 77 | } 78 | 79 | get_nodes() { 80 | local node=$1; shift 81 | local extra_db_nodes='[' 82 | 83 | while [[ -n "$node" ]]; do 84 | extra_db_nodes="${extra_db_nodes}'${node}'" 85 | node=$1; shift 86 | [[ -n "${node}" ]] && extra_db_nodes="${extra_db_nodes}," 87 | done 88 | 89 | extra_db_nodes="${extra_db_nodes}]" 90 | echo "${extra_db_nodes}" 91 | } 92 | 93 | usage() { 94 | die "usage: $(basename $0) this_erlang_node extra_db_node1 [extra_db_node2...]" 95 | } 96 | 97 | 98 | # 99 | # Main 100 | # 101 | [[ $# -ge 2 ]] || usage 102 | 103 | THIS_NODE="$1"; shift 104 | NODE_ARGS=$* 105 | EXTRA_DB_NODES=$(get_nodes $*) 106 | check_user 107 | check_scpf_stopped 108 | check_connectivity "${THIS_NODE}" ${NODE_ARGS} 109 | dire_warning "${EXTRA_DB_NODES}" 110 | recreate_node "${THIS_NODE}" "${EXTRA_DB_NODES}" 111 | -------------------------------------------------------------------------------- /files/backup_database.escript: -------------------------------------------------------------------------------- 1 | %% 2 | %%! -noshell -noinput 3 | %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- 4 | %% ex: ft=erlang ts=4 sw=4 et 5 | 6 | -module(backup_database). 7 | -compile(export_all). 8 | -mode(compile). 9 | 10 | -define(TIMEOUT, 60000). 11 | -define(INFO(Fmt, Args), io:format(Fmt,Args)). 12 | 13 | main([NodeName, Cookie, BackupFile]) -> 14 | main(["-name", NodeName, Cookie, BackupFile]); 15 | main([NameTypeArg, NodeName, Cookie, BackupFile]) -> 16 | TargetNode = start_distribution(NodeName, NameTypeArg, Cookie), 17 | ok = rpc:call(TargetNode, mnesia, backup, [BackupFile], ?TIMEOUT), 18 | ?INFO("Backed up ~p tables to ~p~n", [NodeName, BackupFile]), 19 | halt(0); 20 | main([BackupFile, "list"]) -> 21 | Acc = list_backup_tables(BackupFile), 22 | show_table_backup_info(Acc), 23 | halt(0); 24 | main(_) -> 25 | usage(). 26 | 27 | usage() -> 28 | ScriptName = filename:basename(escript:script_name()), 29 | ?INFO("usage: ~s [-sname|-name] node-name cookie backup-file-name~n" 30 | " ~s backup-file-name 'list'~n", 31 | [ScriptName, ScriptName]), 32 | halt(1). 33 | 34 | start_distribution(NodeName, NameTypeArg, Cookie) -> 35 | MyNode = make_script_node(NodeName), 36 | {ok, _Pid} = net_kernel:start([MyNode, get_name_type(NameTypeArg)]), 37 | erlang:set_cookie(node(), list_to_atom(Cookie)), 38 | TargetNode = list_to_atom(NodeName), 39 | case {net_kernel:connect_node(TargetNode), 40 | net_adm:ping(TargetNode)} of 41 | {true, pong} -> 42 | ok; 43 | {_, pang} -> 44 | ?INFO("Node ~p not responding to pings.\n", [TargetNode]), 45 | erlang:halt(1) 46 | end, 47 | {ok, Cwd} = file:get_cwd(), 48 | ok = rpc:call(TargetNode, file, set_cwd, [Cwd], ?TIMEOUT), 49 | TargetNode. 50 | 51 | %% get name type from arg 52 | get_name_type(NameTypeArg) -> 53 | case NameTypeArg of 54 | "-sname" -> 55 | shortnames; 56 | _ -> 57 | longnames 58 | end. 59 | 60 | make_script_node(Node0) -> 61 | [Node, Host] = string:tokens(Node0, "@"), 62 | list_to_atom(lists:concat([Node, "_backuptask_", os:getpid(), "@", Host])). 63 | 64 | traverser({schema, Tab, _L}, {Cs, Ds, Rs}) -> 65 | {[], {[Tab|Cs], Ds, Rs}}; 66 | traverser({schema, Tab}, {Cs, Ds, Rs}) -> 67 | {[], {Cs, [Tab|Ds], Rs}}; 68 | traverser(Rec, {Cs, Ds, Rs}) when is_tuple(Rec) -> 69 | Tab = element(1, Rec), 70 | {[], {Cs, Ds, dict:update_counter(Tab, 1, Rs)}}; 71 | traverser(_, Acc) -> 72 | {[], Acc}. 73 | 74 | list_backup_tables(BackupFile) -> 75 | Acc0 = {[], [], dict:new()}, 76 | {ok, LastAcc} = mnesia:traverse_backup(BackupFile, mnesia_backup, dummy, 77 | read_only, fun traverser/2, Acc0), 78 | LastAcc. 79 | 80 | show_table_backup_info({Cs, Ds, Rs}) -> 81 | show_table_list("Tables created", Cs), 82 | show_table_list("Tables deleted", Ds), 83 | show_record_stats(Rs). 84 | 85 | show_table_list(Msg, L) -> 86 | ?INFO("~s:~n~s~n", 87 | [Msg, string:join([atom_to_list(C) || C <- L], "\n")]). 88 | 89 | show_record_stats(Rs) -> 90 | ?INFO("Record info:~n~s~n", [rs_to_str(Rs)]). 91 | 92 | rs_to_str(Rs) -> 93 | [[atom_to_list(K), " ", integer_to_list(V), "\n"] 94 | || {K, V} <- dict:to_list(Rs)]. 95 | -------------------------------------------------------------------------------- /files/mnesia_init.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% 3 | 4 | -module(mnesia_init). 5 | -mode(compile). 6 | -compile(export_all). 7 | 8 | -define(TIMEOUT, 300000). 9 | 10 | main([]) -> 11 | main(default_node_and_dirname()); 12 | main([S]) when S =:= "-h"; S =:= "--help" -> 13 | usage(); 14 | main([SMnesiaNode, SMnesiaDir]) -> 15 | main(["-name", SMnesiaNode, SMnesiaDir]); 16 | main([NameTypeArg, SMnesiaNode, SMnesiaDir]) -> 17 | RC = try 18 | run(NameTypeArg, SMnesiaNode, SMnesiaDir), 19 | 0 20 | catch X:Y -> 21 | err_msg("***** ~p:~n~p~n", [X, Y]), 22 | err_msg("~p~n", [erlang:get_stacktrace()]), 23 | 1 24 | end, 25 | RC; 26 | main(_) -> 27 | usage(). 28 | 29 | 30 | run(NameTypeArg, SMnesiaNode, SMnesiaDir) -> 31 | Node = list_to_atom(SMnesiaNode), 32 | start_distribution(Node, NameTypeArg), 33 | application:set_env(mnesia, dir, SMnesiaDir), 34 | ok = filelib:ensure_dir(SMnesiaDir), 35 | create_schema(Node, SMnesiaDir). 36 | 37 | %%-------------------------------------------------------------------- 38 | %% usage 39 | %%-------------------------------------------------------------------- 40 | create_schema(Node, Dir) -> 41 | case mnesia:create_schema([Node]) of 42 | ok -> 43 | err_msg("Created disk schema for ~p in directory ~s~n", 44 | [Node, Dir]), 45 | show_info(), 46 | 0; 47 | {error, {Node, {already_exists, Node}}} -> 48 | err_msg("Schema for node ~p already exists in directory ~s~n", 49 | [Node, Dir]), 50 | show_info(), 51 | 0; 52 | Error -> 53 | err_msg("Error creating schema for node ~p: ~p~n", [Node, Error]), 54 | 1 55 | end. 56 | 57 | %%-------------------------------------------------------------------- 58 | %% usage 59 | %%-------------------------------------------------------------------- 60 | usage() -> 61 | ScriptName = filename:basename(escript:script_name()), 62 | [SNode, SDir] = default_node_and_dirname(), 63 | err_msg("~s [equivalent to ~s -name ~s ~s]\n", 64 | [ScriptName, ScriptName, SNode, SDir]), 65 | err_msg("~s -sname|-name mnesia-node-name mnesia-dir\n", [ScriptName]), 66 | halt(1). 67 | 68 | %%-------------------------------------------------------------------- 69 | %% err_msg 70 | %%-------------------------------------------------------------------- 71 | err_msg(Fmt, Args) -> 72 | io:format(standard_error, Fmt, Args). 73 | 74 | start_distribution(ThisNode, NameTypeArg) -> 75 | case net_kernel:start([ThisNode, get_name_type(NameTypeArg)]) of 76 | {ok, _Pid} -> 77 | err_msg("Started node ~p as a distributed node~n", [ThisNode]); 78 | {error, {already_started, Pid}} -> 79 | err_msg("Node ~p (~p) was already started as a distributed node~n", 80 | [ThisNode, Pid]); 81 | {error, Reason} -> 82 | err_msg("Cannot start this node (~p) as a distributed node," 83 | " reason:~n~p~n", [ThisNode, Reason]), 84 | throw(Reason) 85 | end. 86 | 87 | %% get name type from arg 88 | get_name_type(NameTypeArg) -> 89 | case NameTypeArg of 90 | "-sname" -> 91 | shortnames; 92 | _ -> 93 | longnames 94 | end. 95 | 96 | show_info() -> 97 | ok = mnesia:start(), 98 | show_tables(mnesia:system_info(tables)), 99 | mnesia:stop(). 100 | 101 | show_tables(TabList) -> 102 | err_msg("Tables:~n", []), 103 | [err_msg(" ~p~n", [Tab]) || Tab <- TabList], 104 | ok. 105 | 106 | default_node_and_dirname() -> 107 | SMnesiaNode = atom_to_list(node()), 108 | SMnesiaDir = "Mnesia." ++ SMnesiaNode, 109 | [SMnesiaNode, SMnesiaDir]. 110 | 111 | %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- 112 | %% ex: ft=erlang ts=4 sw=4 et 113 | -------------------------------------------------------------------------------- /files/nodetool: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- 3 | %% ex: ft=erlang ts=4 sw=4 et 4 | %% ------------------------------------------------------------------- 5 | %% 6 | %% nodetool: Helper Script for interacting with live nodes 7 | %% 8 | %% ------------------------------------------------------------------- 9 | 10 | main(Args) -> 11 | ok = start_epmd(), 12 | %% Extract the args 13 | {RestArgs, TargetNode} = process_args(Args, [], undefined), 14 | 15 | %% See if the node is currently running -- if it's not, we'll bail 16 | case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of 17 | {true, pong} -> 18 | ok; 19 | {_, pang} -> 20 | io:format("Node ~p not responding to pings.\n", [TargetNode]), 21 | halt(1) 22 | end, 23 | 24 | case RestArgs of 25 | ["ping"] -> 26 | %% If we got this far, the node already responsed to a ping, so just dump 27 | %% a "pong" 28 | io:format("pong\n"); 29 | ["stop"] -> 30 | io:format("~p\n", [rpc:call(TargetNode, init, stop, [], 60000)]); 31 | ["restart"] -> 32 | io:format("~p\n", [rpc:call(TargetNode, init, restart, [], 60000)]); 33 | ["reboot"] -> 34 | io:format("~p\n", [rpc:call(TargetNode, init, reboot, [], 60000)]); 35 | ["rpc", Module, Function | RpcArgs] -> 36 | case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), 37 | RpcArgs, 60000) of 38 | ok -> 39 | ok; 40 | {badrpc, Reason} -> 41 | io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), 42 | halt(1); 43 | _ -> 44 | halt(1) 45 | end; 46 | ["rpcterms", Module, Function | ArgsAsString] -> 47 | FlatIArgs = flat_intersperse(ArgsAsString), 48 | case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), 49 | consult(FlatIArgs), 60000) of 50 | {badrpc, Reason} -> 51 | io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), 52 | halt(1); 53 | Other -> 54 | io:format("~p\n", [Other]) 55 | end; 56 | ["eval" | ListOfArgs] -> 57 | % shells may process args into more than one, and end up stripping 58 | % spaces, so this converts all of that to a single string to parse; 59 | % then just as a convenience to users, if they forgot a trailing 60 | % '.' add it for them. 61 | Normalized = flat_terminate(string:join(ListOfArgs, " ")), 62 | 63 | % then scan and parse the string 64 | {ok, Scanned, _} = erl_scan:string(Normalized), 65 | {ok, Parsed } = erl_parse:parse_exprs(Scanned), 66 | 67 | % and evaluate it on the remote node 68 | case rpc:call(TargetNode, erl_eval, exprs, [Parsed, [] ]) of 69 | {value, Value, _} -> 70 | io:format ("~p\n",[Value]); 71 | {badrpc, Reason} -> 72 | io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), 73 | halt(1) 74 | end; 75 | Other -> 76 | io:format("Other: ~p\n", [Other]), 77 | io:format("Usage: nodetool {ping|stop|restart|reboot|rpc|rpcterms|eval [Terms]} [RPC]\n") 78 | end, 79 | net_kernel:stop(). 80 | 81 | process_args([], Acc, TargetNode) -> 82 | {lists:reverse(Acc), TargetNode}; 83 | process_args(["-setcookie", Cookie | Rest], Acc, TargetNode) -> 84 | erlang:set_cookie(node(), list_to_atom(Cookie)), 85 | process_args(Rest, Acc, TargetNode); 86 | process_args(["-name", TargetName | Rest], Acc, _) -> 87 | ThisNode = append_node_suffix(TargetName, "_maint_"), 88 | {ok, _} = net_kernel:start([ThisNode, longnames]), 89 | process_args(Rest, Acc, nodename(TargetName)); 90 | process_args(["-sname", TargetName | Rest], Acc, _) -> 91 | ThisNode = append_node_suffix(TargetName, "_maint_"), 92 | {ok, _} = net_kernel:start([ThisNode, shortnames]), 93 | process_args(Rest, Acc, nodename(TargetName)); 94 | process_args([Arg | Rest], Acc, Opts) -> 95 | process_args(Rest, [Arg | Acc], Opts). 96 | 97 | 98 | start_epmd() -> 99 | [] = os:cmd("\"" ++ epmd_path() ++ "\" -daemon"), 100 | ok. 101 | 102 | epmd_path() -> 103 | ErtsBinDir = filename:dirname(escript:script_name()), 104 | Name = "epmd", 105 | case os:find_executable(Name, ErtsBinDir) of 106 | false -> 107 | case os:find_executable(Name) of 108 | false -> 109 | io:format("Could not find epmd.~n"), 110 | halt(1); 111 | GlobalEpmd -> 112 | GlobalEpmd 113 | end; 114 | Epmd -> 115 | Epmd 116 | end. 117 | 118 | 119 | nodename(Name) -> 120 | case string:tokens(Name, "@") of 121 | [_Node, _Host] -> 122 | list_to_atom(Name); 123 | [Node] -> 124 | [_, Host] = string:tokens(atom_to_list(node()), "@"), 125 | list_to_atom(lists:concat([Node, "@", Host])) 126 | end. 127 | 128 | append_node_suffix(Name, Suffix) -> 129 | case string:tokens(Name, "@") of 130 | [Node, Host] -> 131 | list_to_atom(lists:concat([Node, Suffix, os:getpid(), "@", Host])); 132 | [Node] -> 133 | list_to_atom(lists:concat([Node, Suffix, os:getpid()])) 134 | end. 135 | 136 | %% 137 | %% Given a string or binary, parse it into a list of terms, ala file:consult/0 138 | %% 139 | consult(Str) when is_list(Str) -> 140 | consult([], Str, []); 141 | consult(Bin) when is_binary(Bin)-> 142 | consult([], binary_to_list(Bin), []). 143 | 144 | consult(Cont, Str, Acc) -> 145 | case erl_scan:tokens(Cont, Str, 0) of 146 | {done, Result, Remaining} -> 147 | case Result of 148 | {ok, Tokens, _} -> 149 | {ok, Term} = erl_parse:parse_term(Tokens), 150 | consult([], Remaining, [Term | Acc]); 151 | {eof, _Other} -> 152 | lists:reverse(Acc); 153 | {error, Info, _} -> 154 | {error, Info} 155 | end; 156 | {more, Cont1} -> 157 | consult(Cont1, eof, Acc) 158 | end. 159 | 160 | consult_terminate(S) -> 161 | case lists:reverse(S) of 162 | [] -> []; 163 | "." ++ _ -> S; 164 | L -> lists:reverse("." ++ L) 165 | end. 166 | 167 | flat_intersperse(ArgsAsString) -> 168 | lists:flatten( 169 | string:join([consult_terminate(X) || X <- ArgsAsString], " ") 170 | ). 171 | 172 | flat_terminate(ArgsAsString) -> 173 | consult_terminate(lists:flatten(ArgsAsString)). 174 | -------------------------------------------------------------------------------- /get_apns_tools.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | APNS_TOOLS_REPO=https://github.com/SilentCircle/apns_tools.git 6 | 7 | die() { 8 | echo $* >&2 9 | exit 1 10 | } 11 | 12 | upstream_changed() { 13 | remote_commit=$(git rev-parse 'FETCH_HEAD^{commit}') 14 | echo "apns_tools remote commit: $remote_commit" 15 | local_commit=$(git rev-parse 'refs/heads/master^{commit}') 16 | echo "apns_tools local commit: $local_commit" 17 | test $local_commit != $remote_commit 18 | } 19 | 20 | get_tools() { 21 | mkdir -p tools 22 | pushd tools > /dev/null 2>&1 23 | 24 | if [[ -d apns_tools ]]; then 25 | cd apns_tools 26 | git checkout -q master 27 | git fetch -q origin master 28 | if upstream_changed; then 29 | git merge --ff FETCH_HEAD 30 | upstream_did_change=true 31 | else 32 | upstream_did_change=false 33 | fi 34 | else 35 | upstream_did_change=true 36 | git clone ${APNS_TOOLS_REPO} 37 | cd apns_tools 38 | git checkout -q master 39 | fi 40 | 41 | popd > /dev/null 2>&1 42 | echo "Upstream changed: $upstream_did_change" 43 | $upstream_did_change 44 | } 45 | 46 | generate_new_certs() { 47 | local rc=0 48 | 49 | pushd tools/apns_tools > /dev/null 2>&1 50 | ./fake_apple_certs.sh 51 | rc=$? 52 | popd > /dev/null 2>&1 53 | return $rc 54 | } 55 | 56 | if get_tools; then 57 | generate_new_certs || die "Error generating new certs" 58 | fi 59 | 60 | -------------------------------------------------------------------------------- /manifest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PROFILE=$1; shift 4 | 5 | find _build/${PROFILE:-default}/lib -type d -name ".git" | 6 | while read gd; do 7 | wd="$(dirname $gd)" 8 | app="$(basename $wd)" 9 | printf '%s\t%s\n' $app "$(git --git-dir="$gd" --work-tree="$wd" describe --long --always)" 10 | done | sort 11 | 12 | -------------------------------------------------------------------------------- /markedoc.sed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentCircle/scpf/68d46626a056cfd8234d3b6f4661d7d037758619/markedoc.sed -------------------------------------------------------------------------------- /overlay/dev.vars.config: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- 2 | %% ex: ft=erlang ts=4 sw=4 et 3 | 4 | % Platform-specific installation paths 5 | {scpf_bin_dir, "usr/lib/scpf/bin"}. 6 | {scpf_lib_dir, "usr/lib/scpf/lib"}. 7 | {scpf_data_dir, "var/lib/scpf"}. 8 | {scpf_log_dir, "var/log/scpf"}. 9 | {scpf_run_dir, "var/run/scpf"}. 10 | {scpf_etc_dir, "etc/scpf"}. 11 | 12 | % Sentry configuration 13 | {scpf_sentry_public_key, "PUBLIC_KEY"}. 14 | {scpf_sentry_private_key, "SECRET_KEY"}. 15 | {scpf_sentry_project, "PROJECT"}. 16 | {scpf_sentry_error_logger, "false"}. 17 | {scpf_sentry_ipfamily, "inet"}. 18 | {scpf_sentry_log_level, "error"}. 19 | 20 | %% 21 | %% sys.config 22 | %% 23 | {sasl_error_log, "{{scpf_log_dir}}/sasl-error.log"}. 24 | {sasl_log_dir, "{{scpf_log_dir}}/sasl"}. 25 | {scpf_log_level, "debug"}. 26 | 27 | %% 28 | %% etc/vm.args 29 | %% 30 | {node, "scpf"}. 31 | {crash_dump, "{{scpf_log_dir}}/erl_crash.dump"}. 32 | 33 | %% 34 | %% bin/scpf 35 | %% 36 | {runner_etc_dir, "{{scpf_etc_dir}}"}. 37 | {runner_log_dir, "{{scpf_log_dir}}"}. 38 | {pipe_dir, "/tmp/scpf/"}. 39 | {runner_user, ""}. 40 | {mnesia_dir, "{{scpf_data_dir}}/mnesia"}. 41 | {pidfile_dir, "{{scpf_run_dir}}"}. 42 | {code_loading_mode, "interactive"}. 43 | -------------------------------------------------------------------------------- /overlay/vars.config: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- 2 | %% ex: ft=erlang ts=4 sw=4 et 3 | 4 | % Platform-specific installation paths 5 | {scpf_base_dir, "/usr/lib/scpf"}. 6 | {scpf_bin_dir, "{{scpf_base_dir}}/bin"}. 7 | {scpf_lib_dir, "{{scpf_base_dir}}/lib"}. 8 | {scpf_data_dir, "/var/lib/scpf"}. 9 | {scpf_log_dir, "/var/log/scpf"}. 10 | {scpf_run_dir, "/var/run/scpf"}. 11 | {scpf_etc_dir, "/etc/scpf"}. 12 | 13 | % Sentry configuration 14 | {scpf_sentry_public_key, "PUBLIC_KEY"}. 15 | {scpf_sentry_private_key, "SECRET_KEY"}. 16 | {scpf_sentry_project, "PROJECT"}. 17 | {scpf_sentry_error_logger, "false"}. 18 | {scpf_sentry_ipfamily, "inet"}. 19 | {scpf_sentry_log_level, "error"}. 20 | 21 | %% 22 | %% sys.config 23 | %% 24 | {sasl_error_log, "{{scpf_log_dir}}/sasl-error.log"}. 25 | {sasl_log_dir, "{{scpf_log_dir}}/sasl"}. 26 | {scpf_log_level, "info"}. 27 | 28 | %% 29 | %% etc/vm.args 30 | %% 31 | {node, "scpf"}. 32 | {crash_dump, "{{scpf_log_dir}}/erl_crash.dump"}. 33 | 34 | %% 35 | %% bin/scpf 36 | %% 37 | {runner_etc_dir, "{{scpf_etc_dir}}"}. 38 | {runner_log_dir, "{{scpf_log_dir}}"}. 39 | {pipe_dir, "/tmp/scpf/"}. 40 | {runner_user, "scpf"}. 41 | {mnesia_dir, "{{scpf_data_dir}}"}. 42 | {pidfile_dir, "{{scpf_run_dir}}"}. 43 | {code_loading_mode, "embedded"}. 44 | 45 | -------------------------------------------------------------------------------- /pkg.mk: -------------------------------------------------------------------------------- 1 | include checks.mk 2 | 3 | ifeq ($(GIT),) 4 | $(error git is required for this build but is not on the path) 5 | endif 6 | 7 | ifneq ($(wildcard ./.git),) 8 | DCH_COMMENT := $(shell $(GIT) log --oneline -1) 9 | EXTRA_VERSION := $(shell $(GIT) describe --long | sed -re 's/^.*-([0-9]+)-([^-]+)$$/\1.\2/') 10 | else 11 | DCH_COMMENT := No .git dir, probably automated build 12 | EXTRA_VERSION := 0 13 | endif 14 | 15 | DATE := $(shell date +'%Y-%m-%d') 16 | DATETIME := $(shell date -u +'%Y%m%d%H%M%S') 17 | OSNAME := $(shell lsb_release --short --id) 18 | ARCH := $(shell dpkg-architecture -qDEB_BUILD_ARCH) 19 | VERSIONSTRING = $(PACKAGE) ($(PKG_VERSION) $(DATE)) $(OSNAME) $(ARCH) 20 | PKG_VERSION := $(shell dpkg-parsechangelog --count 0 | awk '/^Version:/ { print $$2 }') 21 | DCH_VERSION := $(PKG_VERSION)+0~$(DATETIME).$(EXTRA_VERSION) 22 | 23 | DISTDIR = $(CURDIR)/_debian_build 24 | 25 | -------------------------------------------------------------------------------- /pre_common_test_hook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "Environment:" 6 | env 7 | echo "Current dir: $(pwd)" 8 | 9 | TEST_SUITE_DIR=test/scpf_SUITE_data 10 | 11 | copy_cert_data() { 12 | CA_DIR=tools/apns_tools/CA 13 | 14 | [[ -d ${CA_DIR} ]] || die "Expected ${CA_DIR} to exist" 15 | 16 | if [[ ! -d ${TEST_SUITE_DIR} ]]; then 17 | mkdir -p ${TEST_SUITE_DIR} 18 | elif ls ${TEST_SUITE_DIR}/*.pem > /dev/null 2>&1; then 19 | chmod a+w ${TEST_SUITE_DIR}/*.pem 20 | rm -f ${TEST_SUITE_DIR}/*.pem 21 | fi 22 | 23 | cp ${CA_DIR}/*.pem ${TEST_SUITE_DIR}/ 24 | 25 | for dir in $CA_DIR ${CA_DIR}/WWDRCA ${CA_DIR}/ISTCA2G1; do 26 | cp $dir/{certs,private}/*.pem ${TEST_SUITE_DIR}/ 27 | done 28 | 29 | chmod -R a+w ${TEST_SUITE_DIR}/*.pem 30 | } 31 | 32 | # Get the cert generation tools 33 | echo $0: Get fake cert tools and generate certs in $(pwd)/tools/apns_tools/CA 34 | ./get_apns_tools.sh 35 | 36 | echo $0: Copy fake cert data to ${TEST_SUITE_DIR} 37 | copy_cert_data 38 | 39 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- 2 | %% ex: ts=4 sw=4 ft=erlang et 3 | 4 | %% SCPF now relies on Erlang 18.0 or later 5 | {minimum_otp_vsn, "18.3"}. 6 | 7 | {erl_opts, [ 8 | %% Erlang releases after 17 don't put R in front of their name, and 9 | %% also require dict() to be written like dict:dict() 10 | {platform_define, "^[0-9]+", namespaced_dicts}, 11 | debug_info, 12 | export_all, 13 | report, 14 | warnings_as_errors, 15 | {src_dirs, ["src"]}, 16 | {parse_transform, lager_transform} 17 | ]}. 18 | 19 | {lager_extra_sinks, [sentry]}. 20 | 21 | {edoc_opts, [{doclet, edown_doclet}, 22 | {source_path, [ 23 | "_build/default/lib/sc_push/src", 24 | "_build/default/lib/sc_push_lib/src", 25 | "_build/default/lib/apns_erlv3/src", 26 | "_build/default/lib/apns_erl_util/src", 27 | "_build/default/lib/gcm_erl/src", 28 | "_build/default/lib/sc_util/src" 29 | ]}, 30 | {stylesheet, ""}, 31 | {image, ""}, 32 | {app_default, "http://www.erlang.org/doc/man"}, 33 | {edown_target, github}, 34 | {top_level_readme, 35 | {"./README.md", "http://github.com/SilentCircle/scpf"}} 36 | ]}. 37 | 38 | {ct_opts, [{spec, "scpf.test.spec"}]}. 39 | 40 | {pre_hooks, [ 41 | {ct, "./pre_common_test_hook.sh"}, 42 | {edoc, "sed -r -f markedoc.sed doc/README-src.md > doc/overview.edoc"} 43 | ]}. 44 | 45 | {post_hooks, [ 46 | {compile, "sh -c './manifest.sh > MANIFEST'"} 47 | ]}. 48 | 49 | {shell, [{config, "config/shell.config"}, 50 | {script_file, "files/mnesia_init.erl"} 51 | ] 52 | }. 53 | 54 | {cover_opts, [verbose, {level, details}]}. 55 | {cover_enabled, true}. 56 | 57 | {dialyzer, [ 58 | %{warnings, [unknown]}, 59 | {get_warnings, true}, 60 | {plt_apps, top_level_deps} % top_level_deps | all_deps 61 | ]}. 62 | 63 | {deps, [ 64 | {webmachine, ".*", 65 | {git,"https://github.com/SilentCircle/webmachine.git", {branch, "master"}}}, 66 | {sc_push_lib, ".*", 67 | {git, "https://github.com/SilentCircle/sc_push_lib.git", {branch, "master"}}}, 68 | {sc_push, ".*", 69 | {git, "https://github.com/SilentCircle/sc_push.git", {branch, "master"}}}, 70 | {apns_erlv3, ".*", 71 | {git, "https://github.com/SilentCircle/apns_erlv3.git", {branch, "master"}}}, 72 | {gcm_erl, ".*", 73 | {git, "https://github.com/SilentCircle/gcm_erl.git", {branch, "master"}}}, 74 | {raven_erlang, ".*", 75 | {git, "https://github.com/SilentCircle/raven-erlang.git", {branch, "silentcircle/master"}}} 76 | ] 77 | }. 78 | 79 | %% The default release is the same as a dev release. 80 | %% To generate a production release: 81 | %% 82 | %% ./rebar3 as prod release 83 | %% 84 | {relx, [{release, {scpf, {cmd, "./scpf_version.sh"}}, 85 | [ 86 | scpf, 87 | runtime_tools 88 | ]}, 89 | {dev_mode, true}, 90 | {include_erts, true}, 91 | {include_src, false}, 92 | 93 | {extended_start_script, true}, 94 | 95 | {overlay_vars, "overlay/dev.vars.config"}, 96 | {overlay, [ 97 | {mkdir, "log/sasl"}, 98 | {mkdir, "db"}, 99 | {template, "config/dev.sys.config", "sys.config"}, 100 | {template, "config/dev.vm.args", "vm.args"} 101 | ]} 102 | ] 103 | }. 104 | 105 | {profiles, 106 | [ 107 | {prod, [{relx, [{dev_mode, false}, 108 | {include_erts, true}, 109 | {overlay_vars, "overlay/vars.config"}, 110 | {overlay, [ 111 | {mkdir, "log/sasl"}, 112 | {mkdir, "db"}, 113 | {copy, "files/backup_database.escript", "bin/backup_database.escript"}, 114 | {copy, "files/nodetool", "bin/nodetool"}, 115 | {template, "files/scpf.src", "bin/scpf"}, 116 | {template, "config/sys.config", "releases/{{release_version}}/sys.config"}, 117 | {template, "config/vm.args", "releases/{{release_version}}/vm.args"} 118 | ] 119 | } 120 | ] 121 | }] 122 | }, 123 | {dev, [{relx, []}]}, 124 | {test, [ 125 | {erl_opts, [ 126 | debug_info, 127 | export_all, 128 | {i, ["include"]}, 129 | {parse_transform, lager_transform} 130 | ]}, 131 | {deps, 132 | [ 133 | {mustache, ".*", 134 | {git, "https://github.com/mojombo/mustache.erl.git", {branch, "master"}}}, 135 | {apns_erl_sim, ".*", 136 | {git, "https://github.com/SilentCircle/apns_erl_sim.git", {branch, "master"}}}, 137 | {gcm_sim, ".*", 138 | {git, "https://github.com/SilentCircle/gcm_sim.git", {branch, "master"}}} 139 | ]} 140 | ] 141 | }, 142 | {shell, [ 143 | {erl_opts, [{i, ["include"]}]}, 144 | {deps, 145 | [ 146 | {apns_erl_sim, ".*", 147 | {git, "https://github.com/SilentCircle/apns_erl_sim.git", {branch, "master"}}}, 148 | {gcm_sim, ".*", 149 | {git, "https://github.com/SilentCircle/gcm_sim.git", {branch, "master"}}} 150 | ]} 151 | ] 152 | } 153 | ] 154 | }. 155 | 156 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | {"1.1.0", 2 | [{<<"apns_erl_util">>, 3 | {git,"https://github.com/SilentCircle/apns_erl_util.git", 4 | {ref,"76668788f2e568461fd67a1bfda0dc0f7d5e9f97"}}, 5 | 1}, 6 | {<<"apns_erlv3">>, 7 | {git,"https://github.com/SilentCircle/apns_erlv3.git", 8 | {ref,"b383d44e54bf0b06dc809cc972328cff4603abc7"}}, 9 | 0}, 10 | {<<"chatterbox">>, 11 | {git,"https://github.com/SilentCircle/chatterbox.git", 12 | {ref,"747baae55b5d7a9958779d0ebf9ae85f83e57130"}}, 13 | 1}, 14 | {<<"edown">>, 15 | {git,"https://github.com/uwiger/edown.git", 16 | {ref,"754be25f71a04099c83f3ffdff268e70beeb0021"}}, 17 | 1}, 18 | {<<"epgsql">>, 19 | {git,"https://github.com/epgsql/epgsql.git", 20 | {ref,"7447cd51af73b4ff7947237a4be0a58ece34feb2"}}, 21 | 1}, 22 | {<<"gcm_erl">>, 23 | {git,"https://github.com/SilentCircle/gcm_erl.git", 24 | {ref,"082d3c27b3c953030a0de2d94d168979aa745efd"}}, 25 | 0}, 26 | {<<"goldrush">>, 27 | {git,"git://github.com/DeadZen/goldrush.git", 28 | {ref,"212299233c7e7eb63a97be2777e1c05ebaa58dbe"}}, 29 | 2}, 30 | {<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.2.3">>},2}, 31 | {<<"jsone">>,{pkg,<<"jsone">>,<<"1.2.3">>},1}, 32 | {<<"jsx">>, 33 | {git,"https://github.com/talentdeficit/jsx.git", 34 | {ref,"3074d4865b3385a050badf7828ad31490d860df5"}}, 35 | 1}, 36 | {<<"lager">>, 37 | {git,"https://github.com/basho/lager.git", 38 | {ref,"8187757388c9adc915379caaab36a2f2ca26e944"}}, 39 | 1}, 40 | {<<"mochiweb">>, 41 | {git,"git://github.com/basho/mochiweb.git", 42 | {ref,"ade2a9b29a11034eb550c1d79b4f991bf5ca05ba"}}, 43 | 1}, 44 | {<<"parse_trans">>, 45 | {git,"https://github.com/uwiger/parse_trans.git", 46 | {ref,"6f3645afb43c7c57d61b54ef59aecab288ce1013"}}, 47 | 2}, 48 | {<<"poolboy">>, 49 | {git,"https://github.com/devinus/poolboy.git", 50 | {ref,"d378f996182daa6251ad5438cee4d3f6eb7ea50f"}}, 51 | 1}, 52 | {<<"quickrand">>, 53 | {git,"https://github.com/okeuday/quickrand.git", 54 | {ref,"559e5e3f28bb2bf51acbc38f1c47b12c10094117"}}, 55 | 2}, 56 | {<<"raven_erlang">>, 57 | {git,"https://github.com/SilentCircle/raven-erlang.git", 58 | {ref,"c92edca92b7bfe337be1bf10b64354119b0d0372"}}, 59 | 0}, 60 | {<<"sc_push">>, 61 | {git,"https://github.com/SilentCircle/sc_push.git", 62 | {ref,"fbf455524a5a2b67e45ed988116c06983125fa71"}}, 63 | 0}, 64 | {<<"sc_push_lib">>, 65 | {git,"https://github.com/SilentCircle/sc_push_lib.git", 66 | {ref,"50099687fe16e4916a6083f3c01baf59b3d71010"}}, 67 | 0}, 68 | {<<"sc_util">>, 69 | {git,"https://github.com/SilentCircle/sc_util.git", 70 | {ref,"7a0a7579e8b9ceed387364d5376123d95f252ec0"}}, 71 | 1}, 72 | {<<"uuid">>, 73 | {git,"https://github.com/okeuday/uuid.git", 74 | {ref,"06eca0bd373e97f5d7eda4f8df0621d3903754ef"}}, 75 | 1}, 76 | {<<"webmachine">>, 77 | {git,"https://github.com/SilentCircle/webmachine.git", 78 | {ref,"06a880f576692d2a07dfd73b6db189b2cb93fbdb"}}, 79 | 0}]}. 80 | [ 81 | {pkg_hash,[ 82 | {<<"hpack">>, <<"17670F83FF984AE6CD74B1C456EDDE906D27FF013740EE4D9EFAA4F1BF999633">>}, 83 | {<<"jsone">>, <<"D2E9979326BDAACF50AB0E52C80F53D74CDED93D1AE21FABCF66346238CC0322">>}]} 84 | ]. 85 | -------------------------------------------------------------------------------- /rebar3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentCircle/scpf/68d46626a056cfd8234d3b6f4661d7d037758619/rebar3 -------------------------------------------------------------------------------- /scpf.test.spec.src: -------------------------------------------------------------------------------- 1 | %%==================================================================== 2 | %% Common Test Test Spec 3 | %%==================================================================== 4 | 5 | {node, ct1, '{{NEEDFULLNODENAME}}'}. 6 | {config, ["test/test.config"]}. 7 | {suites, "test", [scpf_SUITE]}. 8 | 9 | %% ex: ft=erlang ts=4 sts=4 sw=4 et: 10 | -------------------------------------------------------------------------------- /scpf_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Try to figure out SCPF version 4 | 5 | GIT=$(which git) 6 | DPKG_PARSE=$(which dpkg-parsechangelog) 7 | 8 | file_version() { 9 | local dsc=$(find .. -maxdepth 1 -type f -name 'scpf_*.dsc' | sort | tail -1) 10 | 11 | if [[ -n $dsc ]]; then 12 | echo -n $(basename $dsc .dsc | sed 's/^scpf_//') 13 | fi 14 | } 15 | 16 | find_version() { 17 | # Default to current UTC date/time 18 | local vsn=$(date -u +"%Y%m%d%H%M%S") 19 | 20 | if [[ -r ./APP_VERSION ]]; then 21 | vsn=$(< ./APP_VERSION) 22 | elif [[ -n $GIT ]] && [[ -d ./.git ]]; then 23 | vsn=$($GIT describe --always --long) 24 | elif [[ -n $DPKG_PARSE ]] && [[ -r ./debian/changelog ]]; then 25 | vsn=$($DPKG_PARSE --count 0 | awk '/^Version:/ { print $$2 }') 26 | else 27 | local fvsn=$(file_version) 28 | if [[ -n $fvsn ]]; then 29 | vsn=$fvsn 30 | else 31 | # Last-ditch effort - write current datetime to APP_VERSION file 32 | echo $vsn > ./APP_VERSION 33 | fi 34 | fi 35 | 36 | echo -n $vsn 37 | } 38 | 39 | find_version 40 | -------------------------------------------------------------------------------- /src/sc_push_top.erl: -------------------------------------------------------------------------------- 1 | -module(sc_push_top). 2 | -export([info/0]). 3 | 4 | info() -> 5 | "This is just a placeholder to keep the release system happy.". 6 | -------------------------------------------------------------------------------- /src/scpf.app.src: -------------------------------------------------------------------------------- 1 | {application, scpf, 2 | [ 3 | {description, "Silent Circle Push Framework Application"}, 4 | {vsn, {cmd, "./scpf_version.sh"}}, 5 | {registered, []}, 6 | {applications, [ 7 | sc_push, 8 | apns_erlv3, 9 | gcm_erl, 10 | raven_erlang 11 | ]}, 12 | {env, []} 13 | ] 14 | }. 15 | 16 | -------------------------------------------------------------------------------- /template_nodename.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | (( $# == 3 )) || exit 1 6 | 7 | NODE_NAME=$1; shift 8 | INPUT_FILENAME=$1; shift 9 | OUTPUT_FILENAME=$1; shift 10 | 11 | sed -e 's/{{NEEDFULLNODENAME}}/'${NODE_NAME}'/' ${INPUT_FILENAME} > ${OUTPUT_FILENAME} 12 | 13 | # ex: ft=sh ts=4 sts=4 sw=4 et: 14 | -------------------------------------------------------------------------------- /test/scpf_SUITE.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(SCPF_SUITE_HRL__). 2 | -define(SCPF_SUITE_HRL__, true). 3 | 4 | -define(assertMsg(Cond, Fmt, Args), 5 | case (Cond) of 6 | true -> 7 | ok; 8 | false -> 9 | ct:fail("Assertion failed: ~p~n" ++ Fmt, [??Cond] ++ Args) 10 | end 11 | ). 12 | 13 | -define(assert(Cond), ?assertMsg((Cond), "", [])). 14 | -define(assertThrow(Expr, Class, Reason), 15 | begin 16 | ok = (fun() -> 17 | try (Expr) of 18 | Res -> 19 | {unexpected_return, Res} 20 | catch 21 | C:R -> 22 | case {C, R} of 23 | {Class, Reason} -> 24 | ok; 25 | _ -> 26 | {unexpected_exception, {C, R}} 27 | end 28 | end 29 | end)() 30 | end 31 | ). 32 | 33 | -define(ALERT_MSG, <<"scpf_SUITE test alert">>). 34 | 35 | -endif. 36 | -------------------------------------------------------------------------------- /test/scpf_SUITE_data/dispatch.conf: -------------------------------------------------------------------------------- 1 | %%-*- mode: erlang -*- 2 | %% ex: ft=erlang ts=4 sw=4 3 | 4 | %% Registration processing 5 | {["registrations"], sc_push_reg_resource, []}. 6 | {["registration"], sc_push_reg_resource, []}. 7 | {["registration", "tag", tag], sc_push_reg_resource, []}. 8 | {["registration", "service-id", service_id], sc_push_reg_resource, []}. 9 | 10 | %% Push notifications 11 | %{["send", "service", service, "id", id], sc_push_resource, []}. 12 | %{["send", "tag", tag], sc_push_resource, []}. 13 | -------------------------------------------------------------------------------- /util.mk: -------------------------------------------------------------------------------- 1 | # $1 = Makefile variable name 2 | # $2 = anything - suppress return value, suggest using $(call assert_nonempty_var,X,q) 3 | assert_nonempty_var = $(if $(value $(1)),$(if $(2),,$(value $(1))),$(error Expected nonempty variable `$(1)`)) 4 | 5 | # $1 = executable name $2 = variable name to display on error 6 | assert_on_path = $(if $(shell which $(1)),$(shell which $(1)),$(error Not on PATH: `$(1)` [$(2)])) 7 | 8 | assert_dir_exists = $(if $(shell if test -d $(1); then echo yes; fi),,$(error Directory $(1) does not exist)) 9 | 10 | get_prog = $(call assert_on_path,$(call assert_nonempty_var,$(1)),$(1)) 11 | --------------------------------------------------------------------------------