├── .circleci └── config.yml ├── .credo.exs ├── .formatter.exs ├── .gitignore ├── .nvmrc ├── .tool-versions ├── .tool-versions-e ├── Dockerfile ├── README.md ├── ansible ├── .gitignore ├── ansible.cfg ├── install_roles.yml ├── inventory │ ├── group_vars │ │ ├── all │ │ │ ├── app.yml │ │ │ ├── elixir-release.yml │ │ │ ├── secrets.yml │ │ │ ├── users.yml │ │ │ └── vars.yml │ │ ├── build-servers │ │ │ ├── postgresql.yml │ │ │ └── vars.yml │ │ └── web-servers │ │ │ ├── secrets.yml │ │ │ └── vars.yml │ └── hosts ├── library │ └── iptables_raw.py ├── playbooks │ ├── config-build.yml │ ├── config-web.yml │ ├── deploy-app.yml │ ├── manage-users.yml │ ├── setup-ansible.yml │ ├── setup-build.yml │ ├── setup-python.yml │ └── setup-web.yml ├── roles.galaxy │ ├── ANXS.postgresql │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── Vagrantfile │ │ ├── ansible.cfg │ │ ├── defaults │ │ │ └── main.yml │ │ ├── handlers │ │ │ └── main.yml │ │ ├── meta │ │ │ ├── .galaxy_install_info │ │ │ └── main.yml │ │ ├── tasks │ │ │ ├── configure.yml │ │ │ ├── databases.yml │ │ │ ├── extensions.yml │ │ │ ├── extensions │ │ │ │ ├── contrib.yml │ │ │ │ ├── dev_headers.yml │ │ │ │ └── postgis.yml │ │ │ ├── install.yml │ │ │ ├── install_yum.yml │ │ │ ├── main.yml │ │ │ ├── monit.yml │ │ │ ├── users.yml │ │ │ └── users_privileges.yml │ │ ├── templates │ │ │ ├── HOWTO.postgresql.conf │ │ │ ├── etc_apt_preferences.d_apt_postgresql_org_pub_repos_apt.pref.j2 │ │ │ ├── etc_monit_conf.d_postgresql.j2 │ │ │ ├── etc_systemd_system_postgresql.service.d_custom.conf.j2 │ │ │ ├── pg_hba.conf.j2 │ │ │ ├── postgresql.conf-10.j2 │ │ │ ├── postgresql.conf-10.orig │ │ │ ├── postgresql.conf-9.1.j2 │ │ │ ├── postgresql.conf-9.1.orig │ │ │ ├── postgresql.conf-9.2.j2 │ │ │ ├── postgresql.conf-9.2.orig │ │ │ ├── postgresql.conf-9.3.j2 │ │ │ ├── postgresql.conf-9.3.orig │ │ │ ├── postgresql.conf-9.4.j2 │ │ │ ├── postgresql.conf-9.4.orig │ │ │ ├── postgresql.conf-9.5.j2 │ │ │ ├── postgresql.conf-9.5.orig │ │ │ ├── postgresql.conf-9.6.j2 │ │ │ └── postgresql.conf-9.6.orig │ │ ├── tests │ │ │ ├── Dockerfile-centos6 │ │ │ ├── Dockerfile-ubuntu14.04 │ │ │ ├── docker │ │ │ │ ├── group_vars │ │ │ │ │ ├── all.yml │ │ │ │ │ └── postgresql.yml │ │ │ │ ├── hosts │ │ │ │ ├── images │ │ │ │ │ ├── Dockerfile.centos.6-builded │ │ │ │ │ ├── Dockerfile.centos.7-builded │ │ │ │ │ ├── Dockerfile.debian.8-builded │ │ │ │ │ ├── Dockerfile.debian.9-builded │ │ │ │ │ └── Dockerfile.ubuntu.16.04-builded │ │ │ │ └── site.yml │ │ │ ├── idempotence_check.sh │ │ │ ├── playbook.yml │ │ │ └── vars.yml │ │ ├── vagrant-inventory │ │ └── vars │ │ │ ├── Debian.yml │ │ │ ├── RedHat.yml │ │ │ ├── empty.yml │ │ │ └── xenial.yml │ ├── cogini.elixir-release │ │ ├── LICENSE │ │ ├── README.md │ │ ├── defaults │ │ │ └── main.yml │ │ ├── handlers │ │ │ └── main.yml │ │ ├── meta │ │ │ ├── .galaxy_install_info │ │ │ └── main.yml │ │ ├── tasks │ │ │ ├── config.yml │ │ │ ├── deploy.yml │ │ │ ├── main.yml │ │ │ └── setup.yml │ │ └── templates │ │ │ ├── etc │ │ │ ├── init │ │ │ │ └── upstart_job.conf.j2 │ │ │ └── sudoers.d │ │ │ │ └── app.j2 │ │ │ ├── lib │ │ │ └── systemd │ │ │ │ └── system │ │ │ │ └── app.service.j2 │ │ │ └── scripts │ │ │ └── remote_console.sh.j2 │ └── cogini.users │ │ ├── LICENSE │ │ ├── README.md │ │ ├── defaults │ │ └── main.yml │ │ ├── meta │ │ ├── .galaxy_install_info │ │ └── main.yml │ │ ├── tasks │ │ ├── CentOS.yml │ │ ├── Debian.yml │ │ ├── RedHat.yml │ │ ├── Ubuntu.yml │ │ ├── main.yml │ │ ├── setup-Debian.yml │ │ └── setup-RedHat.yml │ │ ├── templates │ │ └── etc │ │ │ └── sudoers.d │ │ │ └── 00-admin │ │ ├── tests │ │ ├── inventory │ │ └── test.yml │ │ └── vars │ │ └── main.yml ├── roles │ ├── app │ │ ├── README.md │ │ ├── defaults │ │ │ └── main.yml │ │ ├── handlers │ │ │ └── main.yml │ │ ├── tasks │ │ │ ├── deploy.yml │ │ │ ├── iptables.yml │ │ │ ├── main.yml │ │ │ └── setup.yml │ │ └── templates │ │ │ ├── etc │ │ │ ├── init │ │ │ │ └── upstart_job.conf.j2 │ │ │ └── sudoers.d │ │ │ │ └── app.j2 │ │ │ └── lib │ │ │ └── systemd │ │ │ └── system │ │ │ └── app.service.j2 │ ├── asdf │ │ ├── .gitignore │ │ ├── README.md │ │ ├── Vagrantfile │ │ ├── ansible.cfg │ │ ├── defaults │ │ │ └── main.yml │ │ ├── meta │ │ │ └── main.yml │ │ ├── tasks │ │ │ ├── install.yml │ │ │ ├── main.yml │ │ │ └── plugins.yml │ │ ├── templates │ │ │ ├── asdf.sh.j2 │ │ │ └── asdfrc.j2 │ │ ├── tests │ │ │ ├── Dockerfile-centos7 │ │ │ ├── Dockerfile-ubuntu16.04 │ │ │ ├── inventory │ │ │ └── playbook.yml │ │ └── vars │ │ │ ├── Debian.yml │ │ │ ├── RedHat.yml │ │ │ ├── main.yml │ │ │ └── os_defaults.yml │ ├── common-minimal │ │ ├── README.md │ │ ├── defaults │ │ │ └── main.yml │ │ ├── files │ │ │ └── opt │ │ │ │ └── bin │ │ │ │ └── cronic │ │ ├── meta │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── cronic │ │ ├── README.md │ │ ├── defaults │ │ │ └── main.yml │ │ ├── files │ │ │ └── opt │ │ │ │ └── bin │ │ │ │ └── cronic │ │ └── tasks │ │ │ └── main.yml │ ├── iptables-http │ │ ├── README.md │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── iptables │ │ ├── README.md │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ ├── iptables.yml │ │ │ ├── main.yml │ │ │ ├── setup-Debian.yml │ │ │ └── setup-RedHat.yml │ └── tools-other │ │ ├── defaults │ │ └── main.yml │ │ └── tasks │ │ └── main.yml ├── templates │ ├── config │ │ └── prod.secret.exs.j2 │ └── etc │ │ └── app.conf.j2 └── vars │ ├── build-CentOS-7.yml │ ├── build-Debian-9.yml │ ├── build-Debian.yml │ ├── build-RedHat.yml │ ├── build-Ubuntu-16.yml │ └── build-Ubuntu-18.yml ├── assets ├── js │ ├── app.js │ └── socket.js ├── package.json ├── postcss.config.js ├── scss │ ├── app.scss │ ├── bulmaswatch │ │ ├── _overrides.scss │ │ └── _variables.scss │ ├── forum.css │ ├── landing.scss │ └── layout.scss ├── static │ ├── favicon.ico │ ├── images │ │ ├── elixir_taiwan_small.png │ │ ├── landing-bg.jpg │ │ └── phoenix.png │ └── robots.txt ├── webpack.common.js ├── webpack.dev.js ├── webpack.prod.js └── yarn.lock ├── config ├── config.exs ├── dev.exs ├── dev.secret.exs.example ├── prod.exs ├── prod.secret.exs.example └── test.exs ├── docker-compose.yml ├── lib ├── elixir_tw.ex ├── elixir_tw │ ├── account │ │ ├── account.ex │ │ └── schemas │ │ │ ├── oauth_info.ex │ │ │ └── user.ex │ ├── auth │ │ ├── guardian.ex │ │ ├── user_error_handler.ex │ │ └── user_pipeline.ex │ ├── board │ │ ├── board.ex │ │ ├── comment.ex │ │ └── post.ex │ ├── release_tasks.ex │ └── repo.ex ├── elixir_tw_web.ex ├── elixir_tw_web │ ├── channels │ │ └── user_socket.ex │ ├── controllers │ │ ├── page_controller.ex │ │ ├── post_controller.ex │ │ ├── session_controller.ex │ │ └── user │ │ │ ├── config_controller.ex │ │ │ └── post_controller.ex │ ├── endpoint.ex │ ├── gettext.ex │ ├── router.ex │ ├── templates │ │ ├── layout │ │ │ ├── _head.html.eex │ │ │ ├── _main_nav.html.eex │ │ │ ├── _side_panel.html.eex │ │ │ ├── _user_sidebar.html.eex │ │ │ └── app.html.eex │ │ ├── page │ │ │ └── landing.html.eex │ │ ├── post │ │ │ ├── _post_item.html.eex │ │ │ ├── index.html.eex │ │ │ └── show.html.eex │ │ ├── session │ │ │ ├── new.html.eex │ │ │ └── request.html.eex │ │ └── user │ │ │ ├── config │ │ │ └── dashboard.html.eex │ │ │ └── post │ │ │ └── new.html.eex │ └── views │ │ ├── error_helpers.ex │ │ ├── error_view.ex │ │ ├── layout_view.ex │ │ ├── page_view.ex │ │ ├── post_view.ex │ │ ├── session_view.ex │ │ └── user │ │ ├── config_view.ex │ │ └── post_view.ex └── mix │ └── tasks │ └── deploy.ex ├── mix.exs ├── mix.lock ├── priv ├── gettext │ ├── en │ │ └── LC_MESSAGES │ │ │ └── errors.po │ └── errors.pot └── repo │ ├── migrations │ ├── 20160620042914_create_user.exs │ ├── 20160708122133_create_oauth_credential.exs │ ├── 20160713034714_change_oauth_terms.exs │ ├── 20160714065857_adding_guardian_tokens.exs │ ├── 20160822163850_create_post.exs │ ├── 20160927150252_remove_excerpt_from_posts.exs │ ├── 20160928120350_adding_pined_to_posts.exs │ ├── 20161001150215_making_posts_user_id_not_null.exs │ ├── 20161226142126_create_comment.exs │ └── 20161229015228_add_markdown_body_column_to_posts.exs │ └── seeds.exs ├── rel ├── commands │ └── migrate.sh ├── config.exs └── vm.args.eex ├── scripts ├── build-release.sh ├── db-migrate.sh ├── db-setup.sh ├── deploy-local.sh ├── deploy-remote.sh └── sync-assets.sh └── test ├── elixir_tw ├── account │ └── account_test.exs └── board │ └── board_test.exs ├── elixir_tw_web ├── controllers │ ├── page_controller_test.exs │ └── session_controller_test.exs └── views │ ├── error_view_test.exs │ ├── layout_view_test.exs │ └── page_view_test.exs ├── support ├── channel_case.ex ├── conn_case.ex ├── data_case.ex └── factory.ex └── test_helper.exs /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/elixir_tw 5 | docker: 6 | - image: circleci/elixir:1.6.6-otp-21 7 | environment: 8 | MIX_ENV: test 9 | - image: circleci/postgres:9.6.5 10 | environment: 11 | POSTGRES_USER: ubuntu 12 | steps: 13 | - checkout 14 | - run: mix local.hex --force 15 | - run: mix local.rebar --force 16 | - run: mix deps.get 17 | - run: mix compile 18 | - run: mix ecto.create 19 | - run: mix test 20 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:ecto, :phoenix], 3 | inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"], 4 | subdirectories: ["priv/*/migrations"] 5 | ] 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # App artifacts 2 | /_build 3 | /db 4 | /deps 5 | /*.ez 6 | 7 | # Generate on crash by the VM 8 | erl_crash.dump 9 | 10 | # Static artifacts 11 | */node_modules 12 | 13 | # Since we are building assets from web/static, 14 | # we ignore priv/static. You may want to comment 15 | # this depending on your deployment strategy. 16 | /priv/static/ 17 | 18 | # The config/prod.secret.exs file by default contains sensitive 19 | # data and you should not commit it into version control. 20 | # 21 | # Alternatively, you may comment the line below and commit the 22 | # secrets file as long as you replace its contents by environment 23 | # variables. 24 | /config/*.secret.exs 25 | 26 | # Release Folder 27 | .deliver/releases/ 28 | 29 | yarn-error.log 30 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 6.11.2 2 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 10.15.0 2 | erlang 21.1.4 3 | elixir 1.7.4 4 | -------------------------------------------------------------------------------- /.tool-versions-e: -------------------------------------------------------------------------------- 1 | nodejs 9.3.0 2 | erlang 20.3.4 3 | elixir 1.6.4 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:trusty 2 | 3 | # Elixir requires UTF-8 4 | RUN locale-gen en_US.UTF-8 5 | ENV LANG en_US.UTF-8 6 | ENV LANGUAGE en_US:en 7 | ENV LC_ALL en_US.UTF-8 8 | 9 | RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections 10 | 11 | RUN apt-get update && apt-get upgrade -y 12 | RUN apt-get install -y curl wget make 13 | 14 | # change version can force rebuild image layers from here 15 | ENV PHOENIX_VERSION 1.3.0 16 | 17 | # download and install Erlang package 18 | RUN wget http://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb \ 19 | && dpkg -i erlang-solutions_1.0_all.deb 20 | 21 | # install Node.js (>= 5.0.0) and NPM in order to satisfy brunch.io dependencies 22 | # See http://www.phoenixframework.org/docs/installation#section-node-js-5-0-0- 23 | RUN curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - 24 | #RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - 25 | #RUN echo "deb http://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list 26 | 27 | RUN apt-get update 28 | RUN apt-get install -y nodejs build-essential esl-erlang elixir python 29 | 30 | # install the Phoenix Mix archive 31 | RUN mix archive.install --force https://github.com/phoenixframework/archives/raw/master/phoenix_new-$PHOENIX_VERSION.ez 32 | 33 | RUN mix local.hex --force 34 | RUN mix local.rebar 35 | #RUN yarn global add brunch 36 | 37 | # Fix bug https://github.com/npm/npm/issues/9863 38 | RUN cd $(npm root -g)/npm \ 39 | && npm install fs-extra \ 40 | && sed -i -e s/graceful-fs/fs-extra/ -e s/fs\.rename/fs.move/ ./lib/utils/rename.js 41 | 42 | RUN sudo chown -R root /usr/local 43 | 44 | WORKDIR /app 45 | COPY . . 46 | 47 | RUN npm rebuild --unsafe-perm 48 | RUN mix deps.clean --all && mix deps.get 49 | 50 | RUN ./node_modules/brunch/bin/brunch b -p \ 51 | && MIX_ENV=prod mix do phoenix.digest, release --env=prod 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ElixirTw 2 | 3 | [![Ebert](https://ebertapp.io/github/elixirtw/elixir_tw.svg)](https://ebertapp.io/github/elixirtw/elixir_tw) 4 | [![travis-ci](https://travis-ci.org/elixirtw/elixir_tw.svg?branch=master)](https://travis-ci.org/elixirtw/elixir_tw) 5 | 6 | To start your Phoenix app: 7 | 8 | * Install dependencies with `mix deps.get` 9 | * If you want to use Docker for PostgreSQL in local development 10 | * Start PostgreSQL server with `docker-compose up -d` 11 | * Override default usename for Phoenix to log in PostgreSQL with `export PGUSER=postgres` 12 | * Create and migrate your database with `mix ecto.create && mix ecto.migrate` 13 | * Install Node.js dependencies with `cd assets;yarn install;cd -` 14 | * Start Phoenix endpoint with `mix phoenix.server` 15 | 16 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. 17 | 18 | ## Testing 19 | 20 | This project includes `test.watch`, `credo`, and `dialyxer`. You can run the test simply with: 21 | `mix test.watch` 22 | 23 | to run type checking with `dialyxer`: 24 | `mix dialyzer --plt` 25 | first, then simply 26 | `mix dialyzer` 27 | after. 28 | 29 | 30 | ## Building a release 31 | * `docker build -t elixir_tw .` 32 | * `docker run -it -d elixir_tw:latest bash` 33 | * `docker cp :/app/rel/elixir_tw/releases ./rel/docker_rel` 34 | * `docker rm -f ` 35 | 36 | ## Learn more 37 | 38 | * Official website: http://www.phoenixframework.org/ 39 | * Guides: http://phoenixframework.org/docs/overview 40 | * Docs: http://hexdocs.pm/phoenix 41 | * Mailing list: http://groups.google.com/group/phoenix-talk 42 | * Source: https://github.com/phoenixframework/phoenix 43 | -------------------------------------------------------------------------------- /ansible/.gitignore: -------------------------------------------------------------------------------- 1 | # roles.galaxy/* 2 | vault.key 3 | -------------------------------------------------------------------------------- /ansible/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | remote_user = centos 3 | # system_errors = False 4 | host_key_checking = False 5 | inventory = inventory 6 | roles_path = roles.galaxy:roles 7 | # vault_password_file = vault.key 8 | # lookup_plugins = ./lookup_plugins/ 9 | # filter_plugins = ./filter_plugins/ 10 | library = library 11 | # timeout = 30 12 | ansible_managed = Ansible managed, any changes you make here will be overwritten 13 | retry_files_enabled = False 14 | 15 | [ssh_connection] 16 | # This assumes that you have added your servers to a ~/.ssh/config file 17 | ssh_args = -o ControlMaster=auto -o ControlPersist=15m -q 18 | # With larger teams, we normally put the hosts in a ssh.config in the project 19 | # which is the master definition of the hosts 20 | # ssh_args = -o ControlMaster=auto -o ControlPersist=15m -F ssh.config -q 21 | scp_if_ssh = True 22 | control_path = /tmp/mux-%%r@%%h:%%p 23 | pipelining = True 24 | -------------------------------------------------------------------------------- /ansible/install_roles.yml: -------------------------------------------------------------------------------- 1 | # install_roles.yml 2 | 3 | # ansible-galaxy install --roles-path roles.galaxy -r install_roles.yml 4 | 5 | - src: cogini.users 6 | - src: cogini.elixir-release 7 | - src: ANXS.postgresql 8 | -------------------------------------------------------------------------------- /ansible/inventory/group_vars/all/app.yml: -------------------------------------------------------------------------------- 1 | # App deployment settings 2 | # See roles/app/defaults/main.yml for all the options 3 | 4 | # # App git repo 5 | # app_repo: https://github.com/cogini/elixir-deploy-template 6 | 7 | # # External name of the app, used to name directories and the systemd process 8 | # app_name: deploy-template 9 | 10 | # # Internal "Elixir" name of the app, used to by Distillery to name directories 11 | # app_name_code: deploy_template 12 | 13 | # # Name of your organization or overall project, used to make a unique dir prefix 14 | # org: myorg 15 | 16 | # # OS user that the app runs under 17 | # app_user: foo 18 | 19 | # # Port that Phoenix listens on 20 | # app_http_listen_port: 4001 21 | -------------------------------------------------------------------------------- /ansible/inventory/group_vars/all/elixir-release.yml: -------------------------------------------------------------------------------- 1 | # App deployment settings 2 | # See roles.galaxy/cogini.elixir-release/defaults/main.yml for all the options 3 | 4 | # External name of the app, used to name directories and the systemd process 5 | elixir_release_name: elixir_tw 6 | 7 | # Internal "Elixir" name of the app, used to by Distillery to name directories 8 | elixir_release_name_code: elixir_tw 9 | 10 | # Name of your organization or overall project, used to make a unique dir prefix 11 | elixir_release_org: elixirtw 12 | 13 | # OS user the app runs under 14 | elixir_release_app_user: web 15 | elixir_release_app_group: "{{ elixir_release_app_user }}" 16 | 17 | # OS user for deploy 18 | elixir_release_deploy_user: deploy 19 | elixir_release_deploy_group: "{{ elixir_release_deploy_user }}" 20 | 21 | # Port that Phoenix listens on 22 | elixir_release_http_listen_port: 4001 23 | 24 | # Same as above, but configuring firewall 25 | iptables_http_app_port: "{{ elixir_release_http_listen_port }}" 26 | 27 | # Config dir 28 | elixir_release_conf_dir: "/etc/{{ elixir_release_name }}" 29 | 30 | # Path to conform conf file 31 | elixir_release_conform_conf_path: "{{ elixir_release_conf_dir }}/{{ elixir_release_name_code }}.conf" 32 | -------------------------------------------------------------------------------- /ansible/inventory/group_vars/all/secrets.yml: -------------------------------------------------------------------------------- 1 | # db_pass: !vault | 2 | # $ANSIBLE_VAULT;1.1;AES256 3 | # 65636262323561646362663830656335306432376537663664643331343132313138633637323762 4 | # 6131323030366230633333366565383466343565383739310a623231393837373036393366656530 5 | # 32623736623637366333343936316633326235356364353934303938303935313963353830323033 6 | # 6564363039316133320a336137653030393533373132323835353031356363626432303834386664 7 | # 61633534613863616161666262626164353738393233303338646461666231363731393763653031 8 | # 6264323332636536616234333539333630333538393535626562 9 | -------------------------------------------------------------------------------- /ansible/inventory/group_vars/all/users.yml: -------------------------------------------------------------------------------- 1 | # This file defines a global list of user accounts and admins. 2 | # The goal is that it is the same between all projects, so we can copy 3 | # it unmodified. Put project-level config in `vars.yml` or other files. 4 | 5 | # See [the documentation for the role](https://galaxy.ansible.com/cogini/users/) 6 | # for more details about options, e.g. using ssh keys from files. 7 | 8 | # Define user accounts 9 | users_users: 10 | - user: mickey 11 | name: "Mickey Chen" 12 | github: yuchunc 13 | - user: jake 14 | name: "Jake Morrison" 15 | github: reachfh 16 | - user: taian 17 | name: "Tai-An Su" 18 | github: taiansu 19 | 20 | # Users with sudo permission who should be set up on all servers. 21 | # We use this for our ops team and technical managers. 22 | users_global_admin_users: 23 | - mickey 24 | - jake 25 | - taian 26 | 27 | # You can set project level admins with the `users_admin_users` var 28 | # users_admin_users: 29 | # - jake 30 | -------------------------------------------------------------------------------- /ansible/inventory/group_vars/all/vars.yml: -------------------------------------------------------------------------------- 1 | # These are the default vars that apply to all servers 2 | 3 | # OS user account that deploys the app and owns the code files 4 | users_deploy_user: deploy 5 | # OS group for deploy 6 | users_deploy_group: "{{ users_deploy_user }}" 7 | 8 | # Ports that the firewall opens for SSH 9 | # If you are running ssh on a non-standard port, set that here 10 | iptables_ssh_ports: 11 | - 22 12 | -------------------------------------------------------------------------------- /ansible/inventory/group_vars/build-servers/postgresql.yml: -------------------------------------------------------------------------------- 1 | # Vars for web-servers group 2 | postgresql_version: 10 3 | 4 | postgresql_users: 5 | - name: "{{ elixir_release_name_code }}" 6 | pass: "{{ db_pass }}" 7 | encrypted: yes 8 | 9 | postgresql_databases: 10 | - name: "{{ elixir_release_name_code }}_prod" 11 | owner: "{{ elixir_release_name_code }}" 12 | 13 | postgresql_user_privileges: 14 | - name: "{{ elixir_release_name_code }}" 15 | db: "{{ elixir_release_name_code }}_prod" 16 | priv: 'ALL' 17 | role_attr_flags: 'LOGIN,NOSUPERUSER,NOCREATEDB' 18 | 19 | # Require password to access db 20 | postgresql_default_auth_method: "md5" 21 | 22 | postgresql_pg_hba_default: 23 | # Allow OS postgres user to manage databases without a password 24 | - { type: local, database: all, user: "{{ postgresql_admin_user }}", address: "", method: "peer", comment: "" } 25 | # Same for other users, as long as OS user matches PG user 26 | # - { type: local, database: all, user: all, address: "", method: "peer", comment: '"local" is for Unix domain socket connections only' } 27 | - { type: local, database: all, user: all, address: "", method: "{{ postgresql_default_auth_method }}", comment: '"local" is for Unix domain socket connections only' } 28 | - { type: host, database: all, user: all, address: "127.0.0.1/32", method: "{{ postgresql_default_auth_method }}", comment: "IPv4 local connections:" } 29 | # Replication 30 | # - { type: local, database: replication, user: all, address: "", method: "peer"", comment: 'Allow replication connections from localhost, by a user with the replication privilege.' } 31 | # - { type: host, database: replication, user: all, address: "127.0.0.1/32", method: "{{ postgresql_default_auth_method }}", comment: "" } 32 | # - { type: host, database: replication, user: all, address: "::1/128", method: "{{ postgresql_default_auth_method }}", comment: "" } 33 | - { type: host, database: all, user: all, address: "::1/128", method: "{{ postgresql_default_auth_method }}", comment: "IPv6 local connections:" } 34 | -------------------------------------------------------------------------------- /ansible/inventory/group_vars/build-servers/vars.yml: -------------------------------------------------------------------------------- 1 | # Vars for build-servers group 2 | 3 | # App git repo 4 | app_repo: https://github.com/elixirtw/elixir_tw 5 | # App Version, can be branch name, SHA-1, tag name, default to HEAD 6 | # app_version: ansible-deploy 7 | 8 | # Users who can ssh into the deploy account 9 | # This is needed to run the build and deploy scripts 10 | users_deploy_users: "{{ users_global_admin_users }}" 11 | # users_deploy_users: 12 | # - jake 13 | -------------------------------------------------------------------------------- /ansible/inventory/group_vars/web-servers/secrets.yml: -------------------------------------------------------------------------------- 1 | # erlang_cookie: !vault | 2 | # $ANSIBLE_VAULT;1.1;AES256 3 | # 64346139623638623838396261373265666363643264333664633965306465313864653033643530 4 | # 3830366538366139353931323662373734353064303034660a326232343036646339623638346236 5 | # 39623832656466356338373264623331363736636262393838323135663962633339303634353763 6 | # 3935623562343131370a383439346166323832353232373933613363383435333037343231393830 7 | # 35326662353662316339633732323335653332346465383030633333333638323735383666303264 8 | # 35663335623061366536363134303061323861356331373334653363383961396330386136636661 9 | # 63373230643163633465303933396336393531633035616335653234376666663935353838356135 10 | # 36323866346139666462 11 | 12 | # secret_key_base: !vault | 13 | # $ANSIBLE_VAULT;1.1;AES256 14 | # 63626435333465376362396633636361643264646331663336623264663236643435336663643832 15 | # 6332653433633163623531393835663232303833666231390a373763633236396339666630346534 16 | # 61633132643032613933376539633263663437346261633961356136613536336435346235366238 17 | # 3462373566383337660a376332353863386536313464393362393230303930613662313266336539 18 | # 32396662613030376335356430653635353962656135313530396233393534613230303539373032 19 | # 36326139343432363831343161666631643237656334656432393133663734613531623665333333 20 | # 64323437613036616331646238356530353862336338633030623138383139333038643131343066 21 | # 37353333313062623864 22 | -------------------------------------------------------------------------------- /ansible/inventory/group_vars/web-servers/vars.yml: -------------------------------------------------------------------------------- 1 | # Vars for web-servers group 2 | 3 | # OS user account that the app runs under 4 | users_app_user: "{{ elixir_release_app_user }}" 5 | users_app_group: "{{ elixir_release_app_user }}" 6 | 7 | # Defines secondary groups for the deploy user 8 | # This is needed if you are using a flag file to trigger 9 | # restarts, as the deploy user needs to be able to create a file 10 | # with permissions such that it can be deleted by the app user. 11 | users_deploy_groups: 12 | # Allow deploy user to write flag file owned by app user 13 | - "{{ users_app_user }}" 14 | # Allow deploy user to look at system logs with journalctl 15 | - systemd-journal 16 | 17 | # Users who can ssh into the app account 18 | # This is not strictly needed, but can be useful for devs 19 | # 20 | # users_app_users: "{{ users_global_admin_users }}" 21 | # users_app_users: 22 | # - jake 23 | -------------------------------------------------------------------------------- /ansible/inventory/hosts: -------------------------------------------------------------------------------- 1 | [web-servers] 2 | elixirtw 3 | 4 | [build-servers] 5 | elixirtw 6 | 7 | # [web-servers] 8 | # elixir-deploy-template-centos-7 9 | # elixir-deploy-template-ubuntu-16 10 | # elixir-deploy-template-ubuntu-18 11 | # elixir-deploy-template-debian-9 12 | # 13 | # [build-servers] 14 | # elixir-deploy-template-centos-7 15 | # elixir-deploy-template-ubuntu-16 16 | # elixir-deploy-template-ubuntu-18 17 | # elixir-deploy-template-debian-9 18 | 19 | # If you are running newer Ubuntu or Debian, add it here to specify that 20 | # Ansible should use the installed Python 3 interpreter 21 | # [py3-hosts] 22 | # elixir-deploy-template-ubuntu-16 23 | # elixir-deploy-template-ubuntu-18 24 | # elixir-deploy-template-debian-9 25 | # 26 | # [py3-hosts:vars] 27 | # ansible_python_interpreter=/usr/bin/python3 28 | # 29 | # [local] 30 | # localhost ansible_sudo=false 31 | -------------------------------------------------------------------------------- /ansible/playbooks/config-build.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Configure build 3 | # ansible-playbook --vault-password-file vault.key -u $USER -v -l web-servers playbooks/config-build.yml -D 4 | 5 | - name: Configure build 6 | hosts: '*' 7 | become: true 8 | gather_facts: true 9 | no_log: true 10 | tasks: 11 | - name: Set db_pass 12 | set_fact: 13 | db_pass: "{{ lookup('password', '/tmp/db_pass.txt length=16') }}" 14 | when: db_pass is not defined 15 | 16 | - name: Configure prod.secret.exs 17 | template: 18 | src: ../templates/config/prod.secret.exs.j2 19 | dest: "/home/{{ elixir_release_deploy_user }}/build/{{ elixir_release_name }}/config/prod.secret.exs" 20 | owner: "{{ elixir_release_deploy_user }}" 21 | group: "{{ elixir_release_deploy_group }}" 22 | mode: "0640" 23 | 24 | # - name: Set Erlang cookie file 25 | # copy: 26 | # content: "{{ erlang_cookie }}" 27 | # dest: "/home/{{ elixir_release_deploy_user }}/build/{{ elixir_release_name }}/config/cookie.txt" 28 | # owner: "{{ elixir_release_deploy_user }}" 29 | # group: "{{ elixir_release_deploy_group }}" 30 | # mode: "0640" 31 | -------------------------------------------------------------------------------- /ansible/playbooks/config-web.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Configure web 3 | # 4 | # Use encrypted values defined in ansible/inventory/group_vars/web-servers/secrets.yml 5 | # ansible-playbook --vault-password-file vault.key -u $USER -v -l web-servers playbooks/config-web.yml -D 6 | # 7 | # Generate new values 8 | # ansible-playbook -u $USER -v -l web-servers playbooks/config-web.yml -D 9 | 10 | - name: Configure web 11 | hosts: '*' 12 | become: true 13 | gather_facts: true 14 | no_log: true 15 | tasks: 16 | - name: Set erlang_cookie 17 | set_fact: 18 | erlang_cookie: "{{ lookup('password', '/tmp/erlang_cookie.txt') }}" 19 | when: erlang_cookie is not defined 20 | 21 | - name: Set Erlang cookie file 22 | copy: 23 | content: "{{ erlang_cookie }}" 24 | dest: "/home/{{ elixir_release_app_user }}/.erlang.cookie" 25 | owner: "{{ elixir_release_app_user }}" 26 | group: "{{ elixir_release_app_group }}" 27 | mode: "0400" 28 | 29 | - name: Set secret_key_base 30 | set_fact: 31 | secret_key_base: "{{ lookup('password', '/tmp/secret_key_base.txt length=64') }}" 32 | when: secret_key_base is not defined 33 | 34 | - name: Set db_pass 35 | set_fact: 36 | db_pass: "{{ lookup('password', '/tmp/db_pass.txt length=16') }}" 37 | when: db_pass is not defined 38 | 39 | - name: Create Conform conf file 40 | template: 41 | src: ../templates/etc/app.conf.j2 42 | dest: "{{ elixir_release_conform_conf_path }}" 43 | owner: "{{ elixir_release_deploy_user }}" 44 | group: "{{ elixir_release_app_group }}" 45 | mode: 0640 46 | -------------------------------------------------------------------------------- /ansible/playbooks/deploy-app.yml: -------------------------------------------------------------------------------- 1 | # Install Elixir deploy-template 2 | # 3 | # Run setup tasks, e.g. installing packages and creating directories. 4 | # Run this from your dev machine, specifying a user with sudo permissions. 5 | # 6 | # ansible-playbook -u $USER -v -l web-servers playbooks/deploy-app.yml --skip-tags deploy -D 7 | # 8 | # Deploy the code. 9 | # Run this from the build server, from a user account with ssh access to the deploy account on the target machine. 10 | # 11 | # ansible-playbook -u deploy -v -l web-servers playbooks/deploy-app.yml --tags deploy --extra-vars ansible_become=false -D 12 | # 13 | 14 | - hosts: '*' 15 | become: true 16 | roles: 17 | - cogini.elixir-release 18 | -------------------------------------------------------------------------------- /ansible/playbooks/manage-users.yml: -------------------------------------------------------------------------------- 1 | # Manage users 2 | # 3 | # Initial bootstrapping 4 | # 5 | # On a cloud server where the provisioning process adds a keypair to the root 6 | # user, the initial setup can be: 7 | # 8 | # ansible-playbook -u root -v -l web-server-01 playbooks/manage-users.yml -D 9 | # 10 | # On a physical server where we start with a root account and no ssh keys, we 11 | # need to bootstrap the server the first time, specifying the password with -k. 12 | # 13 | # ansible-playbook -k -u root -v -l web-server-01 playbooks/manage-users.yml 14 | # 15 | # Insead of `root`, the initial user might be `centos` or `ubuntu`. 16 | # 17 | # On macOS the -k command requires the askpass utility, which is not installed 18 | # by default, so it falls back to Paramiko, which doesn't understand 19 | # `.ssh/config`, so we need to set host's IP in the `ansible_host` variable. 20 | # 21 | # ansible-playbook -k -u root -v -l web-server-01 playbooks/manage-users.yml --extra-vars "ansible_host=123.45.67.89" 22 | # 23 | # 24 | # On following runs, after the script has created an admin user for you, run: 25 | # 26 | # ansible-playbook -u $USER -v -l web-server-01 playbooks/manage-users.yml 27 | # 28 | - name: Manage users 29 | hosts: '*' 30 | become: true 31 | roles: 32 | - cogini.users 33 | -------------------------------------------------------------------------------- /ansible/playbooks/setup-ansible.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Install ansible on build system 3 | # 4 | # ansible-playbook -u $USER -v -l build-servers playbooks/setup-ansible.yml -D 5 | 6 | - name: install ansible 7 | hosts: '*' 8 | become: true 9 | vars: 10 | tools_other_packages: "{{ ansible_build_deps }}" 11 | tools_other_pip: 12 | - paramiko 13 | - ansible 14 | vars_files: 15 | - ../vars/build-{{ ansible_os_family }}.yml 16 | roles: 17 | - tools-other 18 | -------------------------------------------------------------------------------- /ansible/playbooks/setup-python.yml: -------------------------------------------------------------------------------- 1 | # Newer versions of Ubuntu (16.04+) and Debian ship with Python 3, but the 2 | # default for Ansible is Python 2. It's possible to use the Python 3 that comes 3 | # with the OS, but when you are getting started with Ansible, this is the most 4 | # straightforward way. If necessary, run this playbook to install Python 2: 5 | # 6 | # ansible-playbook -u root -v -l elixir-deploy-template playbooks/setup-python.yml -D 7 | 8 | - name: Bootstrap python 2 9 | hosts: '*' 10 | gather_facts: false 11 | become: true 12 | pre_tasks: 13 | - name: Install python 2 14 | raw: test -e /usr/bin/python2 || (apt -y update && apt install -y python) 15 | -------------------------------------------------------------------------------- /ansible/playbooks/setup-web.yml: -------------------------------------------------------------------------------- 1 | # Web system setup 2 | # 3 | # ansible-playbook -u $USER -v -l web-servers playbooks/setup-web.yml -D 4 | 5 | # - name: Bootstrap Python 2 6 | # hosts: debian-servers 7 | # gather_facts: false 8 | # become: true 9 | # pre_tasks: 10 | # - name: Install python 2 11 | # raw: test -e /usr/bin/python2 || (apt -y update && apt install -y python) 12 | 13 | - name: Manage users 14 | hosts: '*' 15 | become: true 16 | roles: 17 | - cogini.users 18 | 19 | - name: Install common 20 | hosts: '*' 21 | become: true 22 | roles: 23 | - common-minimal 24 | - iptables 25 | - iptables-http 26 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .AppleDouble 3 | .LSOverride 4 | Icon 5 | ._* 6 | .Spotlight-V100 7 | .Trashes 8 | .vagrant 9 | test 10 | *.retry 11 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | language: python 4 | python: "2.7" 5 | sudo: required 6 | dist: trusty 7 | services: 8 | - docker 9 | 10 | env: 11 | matrix: 12 | - IMAGE_NAME="ubuntu-upstart:14.04" 13 | - IMAGE_NAME="ubuntu:16.04-builded" 14 | - IMAGE_NAME="debian:8-builded" 15 | - IMAGE_NAME="debian:9-builded" 16 | - IMAGE_NAME="centos:7-builded" 17 | - IMAGE_NAME="centos:6-builded" 18 | 19 | install: 20 | - pip install ansible=="2.4.2.0" docker-py 21 | - ln -s ${PWD} tests/docker/ANXS.postgresql 22 | 23 | script: 24 | # Syntax check 25 | - ansible-playbook -i tests/docker/hosts -e image_name=${IMAGE_NAME} tests/docker/site.yml --syntax-check 26 | 27 | # Play test 28 | - ansible-playbook -i tests/docker/hosts -e image_name=${IMAGE_NAME} tests/docker/site.yml 29 | 30 | # Idempotence test 31 | - ansible-playbook -i tests/docker/hosts -e image_name=${IMAGE_NAME} tests/docker/site.yml > idempotence_out 32 | - ./tests/idempotence_check.sh idempotence_out 33 | 34 | notifications: 35 | webhooks: https://galaxy.ansible.com/api/v1/notifications/ 36 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014 Pieterjan Vandaele 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure('2') do |config| 5 | 6 | # Ensure we use our vagrant private key 7 | config.ssh.insert_key = false 8 | config.ssh.private_key_path = '~/.vagrant.d/insecure_private_key' 9 | 10 | config.vm.define 'anxs' do |machine| 11 | #machine.vm.box = "bento/ubuntu-16.04" 12 | # machine.vm.box = "ubuntu/trusty64" 13 | #machine.vm.box = "ubuntu/precise64" 14 | #machine.vm.box = "debian/jessie64" 15 | #machine.vm.box = "debian/wheezy64" 16 | machine.vm.box = "centos/7" 17 | #machine.vm.box = "centos/6" 18 | 19 | machine.vm.network :private_network, ip: '192.168.88.22' 20 | machine.vm.hostname = 'anxs.local' 21 | 22 | machine.vm.provision 'ansible' do |ansible| 23 | ansible.playbook = 'tests/playbook.yml' 24 | ansible.verbose = "v" 25 | ansible.become = true 26 | ansible.inventory_path = 'vagrant-inventory' 27 | ansible.host_key_checking = false 28 | end 29 | 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | roles_path = ../ 3 | allow_world_readable_tmpfiles = True 4 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/handlers/main.yml: -------------------------------------------------------------------------------- 1 | # file: postgresql/handlers/main.yml 2 | 3 | - name: restart postgresql with service 4 | service: 5 | name: "{{ postgresql_service_name }}" 6 | state: restarted 7 | enabled: yes 8 | 9 | - name: restart postgresql with systemd 10 | systemd: 11 | name: "{{ postgresql_service_name }}" 12 | state: restarted 13 | enabled: yes 14 | when: ansible_service_mgr == 'systemd' 15 | 16 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/meta/.galaxy_install_info: -------------------------------------------------------------------------------- 1 | {install_date: 'Fri May 25 07:40:25 2018', version: v1.10.1} 2 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/meta/main.yml: -------------------------------------------------------------------------------- 1 | # file: postgresql/meta/main.yml 2 | 3 | galaxy_info: 4 | author: pjan vandaele 5 | company: ANXS 6 | description: "Install and configure PostgreSQL, dependencies, extensions, databases and users." 7 | min_ansible_version: 2.4.0 8 | license: MIT 9 | platforms: 10 | - name: Debian 11 | versions: 12 | - jessie 13 | - stretch 14 | - name: Ubuntu 15 | versions: 16 | - xenial 17 | - trusty 18 | - name: EL 19 | versions: 20 | - 6 21 | - 7 22 | categories: 23 | - database 24 | - database:sql 25 | galaxy_tags: 26 | - postgresql 27 | - postgres 28 | - sql 29 | - database 30 | - postgis 31 | - debian 32 | - ubuntu 33 | - centos 34 | - redhat 35 | 36 | dependencies: [] 37 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tasks/extensions.yml: -------------------------------------------------------------------------------- 1 | # file: postgresql/tasks/extensions.yml 2 | 3 | - import_tasks: extensions/contrib.yml 4 | when: postgresql_ext_install_contrib 5 | - import_tasks: extensions/dev_headers.yml 6 | when: postgresql_ext_install_dev_headers 7 | - import_tasks: extensions/postgis.yml 8 | when: postgresql_ext_install_postgis 9 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tasks/extensions/contrib.yml: -------------------------------------------------------------------------------- 1 | # file: postgresql/tasks/extensions/contrib.yml 2 | 3 | - name: PostgreSQL | Extensions | Make sure the postgres contrib extensions are installed | Debian 4 | apt: 5 | name: "postgresql-contrib-{{postgresql_version}}" 6 | state: present 7 | update_cache: yes 8 | cache_valid_time: "{{apt_cache_valid_time | default (3600)}}" 9 | when: ansible_os_family == "Debian" 10 | notify: 11 | - restart postgresql 12 | 13 | - name: PostgreSQL | Extensions | Make sure the postgres contrib extensions are installed | RedHat 14 | yum: 15 | name: "postgresql{{postgresql_version_terse}}-contrib" 16 | state: present 17 | when: ansible_os_family == "RedHat" 18 | notify: 19 | - restart postgresql 20 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tasks/extensions/dev_headers.yml: -------------------------------------------------------------------------------- 1 | # file: postgresql/tasks/extensions/dev_headers.yml 2 | 3 | - name: PostgreSQL | Extensions | Make sure the development headers are installed | Debian 4 | apt: 5 | name: libpq-dev 6 | state: present 7 | update_cache: yes 8 | cache_valid_time: "{{apt_cache_valid_time | default (3600)}}" 9 | when: ansible_os_family == "Debian" 10 | notify: 11 | - restart postgresql 12 | 13 | - name: PostgreSQL | Extensions | Make sure the development headers are installed | RedHat 14 | yum: 15 | name: "{{ item }}" 16 | state: present 17 | update_cache: yes 18 | with_items: 19 | - "postgresql{{ postgresql_version_terse }}-libs" 20 | - "postgresql{{ postgresql_version_terse }}-devel" 21 | when: ansible_os_family == "RedHat" 22 | notify: 23 | - restart postgresql with service 24 | - restart postgresql with systemd 25 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tasks/extensions/postgis.yml: -------------------------------------------------------------------------------- 1 | # file: postgresql/tasks/extensions/postgis.yml 2 | 3 | - include_vars: "{{ item }}" 4 | with_first_found: 5 | - "../vars/{{ ansible_distribution_release }}.yml" 6 | - "../vars/empty.yml" 7 | 8 | - name: PostgreSQL | Extensions | Make sure the postgis extensions are installed | Debian 9 | apt: 10 | name: "{{item}}" 11 | state: present 12 | update_cache: yes 13 | cache_valid_time: "{{apt_cache_valid_time | default (3600)}}" 14 | with_items: "{{ postgresql_ext_postgis_deps }}" 15 | when: ansible_os_family == "Debian" 16 | notify: 17 | - restart postgresql 18 | 19 | - name: PostgreSQL | Extensions | Make sure the postgis extensions are installed | RedHat 20 | yum: 21 | name: "{{item}}" 22 | state: present 23 | update_cache: yes 24 | with_items: "{{ postgresql_ext_postgis_deps }}" 25 | when: ansible_os_family == "RedHat" 26 | notify: 27 | - restart postgresql 28 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tasks/install.yml: -------------------------------------------------------------------------------- 1 | # file: postgresql/tasks/install.yml 2 | 3 | # The standard ca-certs are needed because without them apt_key will fail to 4 | # validate www.postgresql.org (or probably any other source). 5 | - name: PostgreSQL | Make sure the CA certificates are available 6 | apt: 7 | pkg: ca-certificates 8 | state: present 9 | 10 | - name: PostgreSQL | Add PostgreSQL repository apt-key 11 | apt_key: 12 | id: "{{ postgresql_apt_key_id }}" 13 | url: "{{ postgresql_apt_key_url }}" 14 | state: present 15 | when: postgresql_apt_key_url and postgresql_apt_key_id and postgresql_install_repository 16 | 17 | - name: PostgreSQL | Add PostgreSQL repository 18 | apt_repository: 19 | repo: "{{ postgresql_apt_repository }}" 20 | state: present 21 | when: postgresql_apt_repository | default('') != '' and postgresql_install_repository 22 | 23 | - name: PostgreSQL | Add PostgreSQL repository preferences 24 | template: 25 | src: etc_apt_preferences.d_apt_postgresql_org_pub_repos_apt.pref.j2 26 | dest: /etc/apt/preferences.d/apt_postgresql_org_pub_repos_apt.pref 27 | when: postgresql_apt_pin_priority and postgresql_install_repository 28 | 29 | - name: PostgreSQL | Make sure the dependencies are installed 30 | apt: 31 | pkg: "{{item}}" 32 | state: present 33 | update_cache: yes 34 | cache_valid_time: "{{apt_cache_valid_time | default (3600)}}" 35 | with_items: "{{postgresql_apt_dependencies}}" 36 | 37 | - name: PostgreSQL | Install PostgreSQL 38 | apt: 39 | name: "{{item}}" 40 | state: present 41 | update_cache: yes 42 | default_release: "{{postgresql_default_release | default(ansible_distribution_release + '-pgdg')}}" 43 | cache_valid_time: "{{apt_cache_valid_time | default (3600)}}" 44 | environment: "{{postgresql_env}}" 45 | with_items: 46 | - "postgresql-{{postgresql_version}}" 47 | - "postgresql-client-{{postgresql_version}}" 48 | - "postgresql-contrib-{{postgresql_version}}" 49 | 50 | - name: PostgreSQL | PGTune 51 | apt: 52 | name: pgtune 53 | state: present 54 | update_cache: yes 55 | cache_valid_time: "{{apt_cache_valid_time | default (3600)}}" 56 | environment: "{{postgresql_env}}" 57 | when: postgresql_pgtune 58 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tasks/install_yum.yml: -------------------------------------------------------------------------------- 1 | # file: postgresql/tasks/install_yum.yml 2 | 3 | # The standard ca-certs are needed because without them apt_key will fail to 4 | # validate www.postgresql.org (or probably any other source). 5 | 6 | - block: 7 | - name: PostgreSQL | Install all the required dependencies 8 | yum: 9 | name: ["ca-certificates","python-psycopg2", "python-pycurl", "glibc-common","epel-release","libselinux-python"] 10 | state: present 11 | 12 | - name: PostgreSQL | Add PostgreSQL repository 13 | yum: 14 | name: "{{ postgresql_yum_repository_url }}" 15 | state: present 16 | when: postgresql_install_repository 17 | 18 | - name: PostgreSQL | Install PostgreSQL 19 | yum: 20 | name: "{{ item }}" 21 | state: present 22 | environment: "{{ postgresql_env }}" 23 | with_items: 24 | - "postgresql{{ postgresql_version_terse }}-server" 25 | - "postgresql{{ postgresql_version_terse }}" 26 | - "postgresql{{ postgresql_version_terse }}-contrib" 27 | 28 | - name: PostgreSQL | PGTune 29 | yum: 30 | name: pgtune 31 | state: present 32 | environment: "{{ postgresql_env }}" 33 | when: postgresql_pgtune 34 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tasks/main.yml: -------------------------------------------------------------------------------- 1 | # file: postgresql/tasks/main.yml 2 | 3 | - include_vars: "{{ item }}" 4 | with_first_found: 5 | - "../vars/{{ ansible_os_family }}.yml" 6 | - "../vars/empty.yml" 7 | tags: [always] 8 | 9 | - import_tasks: install.yml 10 | when: ansible_pkg_mgr == "apt" 11 | tags: [postgresql, postgresql-install] 12 | 13 | - import_tasks: install_yum.yml 14 | when: ansible_pkg_mgr == "yum" 15 | tags: [postgresql, postgresql-install] 16 | 17 | - import_tasks: extensions.yml 18 | tags: [postgresql, postgresql-extensions] 19 | 20 | - import_tasks: configure.yml 21 | tags: [postgresql, postgresql-configure] 22 | 23 | - import_tasks: users.yml 24 | tags: [postgresql, postgresql-users] 25 | 26 | - import_tasks: databases.yml 27 | tags: [postgresql, postgresql-databases] 28 | 29 | - import_tasks: users_privileges.yml 30 | tags: [postgresql, postgresql-users] 31 | 32 | - import_tasks: monit.yml 33 | when: monit_protection is defined and monit_protection == true 34 | tags: [postgresql, postgresql-monit] 35 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tasks/monit.yml: -------------------------------------------------------------------------------- 1 | # file: postgresql/tasks/monit.yml 2 | 3 | - name: PostgreSQL | (Monit) Copy the postgresql monit service file 4 | template: 5 | src: etc_monit_conf.d_postgresql.j2 6 | dest: /etc/monit/conf.d/postgresql 7 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tasks/users.yml: -------------------------------------------------------------------------------- 1 | # file: postgresql/tasks/users.yml 2 | 3 | - name: PostgreSQL | Ensure PostgreSQL is running 4 | service: 5 | name: "{{ postgresql_service_name }}" 6 | state: started 7 | 8 | - name: PostgreSQL | Make sure the PostgreSQL users are present 9 | postgresql_user: 10 | name: "{{item.name}}" 11 | password: "{{ item.pass | default(omit) }}" 12 | encrypted: "{{ item.encrypted | default(omit) }}" 13 | port: "{{postgresql_port}}" 14 | state: present 15 | login_user: "{{postgresql_admin_user}}" 16 | no_log: true 17 | become: yes 18 | become_user: "{{postgresql_admin_user}}" 19 | with_items: "{{postgresql_users}}" 20 | when: postgresql_users|length > 0 21 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tasks/users_privileges.yml: -------------------------------------------------------------------------------- 1 | # file: postgresql/tasks/users_privileges.yml 2 | 3 | - name: PostgreSQL | Update the user privileges 4 | postgresql_user: 5 | name: "{{item.name}}" 6 | db: "{{item.db | default(omit)}}" 7 | port: "{{postgresql_port}}" 8 | priv: "{{item.priv | default(omit)}}" 9 | state: present 10 | login_host: "{{item.host | default(omit)}}" 11 | login_user: "{{postgresql_admin_user}}" 12 | role_attr_flags: "{{item.role_attr_flags | default(omit)}}" 13 | become: yes 14 | become_user: "{{postgresql_admin_user}}" 15 | with_items: "{{postgresql_user_privileges}}" 16 | when: postgresql_users|length > 0 17 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/templates/HOWTO.postgresql.conf: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | How to add a new PostgreSQL version 3 | =================================== 4 | 5 | Note: (https://www.postgresql.org/support/versioning) 6 | 7 | Beginning with version 10, a major release is indicated by increasing the first part of the version, 8 | e.g. 10 to 11. Before version 10, a major release was indicated by increasing either the first or second 9 | part of the version number, e.g. 9.5 to 9.6. 10 | 11 | Do the following steps: ( versions mentioned for transitioninng from version 9.6 to 10 ) 12 | 13 | 1) Download the Debian package 'postgresql-_[...].deb' from 14 | http://apt.postgresql.org/pub/repos/apt/pool/main/p/postgresql-, e.g. for new version 10 from 15 | http://apt.postgresql.org/pub/repos/apt/pool/main/p/postgresql-10 16 | 17 | 2) Extract the 'usr/share/postgresql//postgresql.conf.sample' file with 18 | dpkg-deb -x postgresql-_[...].deb dir/to/extract/to/ 19 | 20 | and save it under the 'templates' role directory 21 | => templates/postgresql.conf..orig 22 | 23 | 3) Check the difference between another version, e.g. for 9.6 -> 10: 24 | => vimdiff postgresql.conf.10.orig postgresql.conf.9.6.orig 25 | 26 | 4) Copy an existing template, e.g.: 27 | => cp postgresql.conf.9.6.j2 postgresql.conf.10.j2 28 | 29 | 5) Update the new template following the major differences. 30 | 31 | 6) If there are new options or some of them removed, update the 'default/main.yml' file and add a "(>= 10)" or "(<= 10)" comment to them. 32 | 33 | 7) For yum based installation add version and minor version of postgresql in 'default/main.yml' under '# YUM settings' at end of file 34 | 35 | 8) Update the '.travis.yml' file to test its new version. 36 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/templates/etc_apt_preferences.d_apt_postgresql_org_pub_repos_apt.pref.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | 3 | Package: * 4 | Pin: release o=apt.postgresql.org 5 | Pin-Priority: {{ postgresql_apt_pin_priority }} 6 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/templates/etc_monit_conf.d_postgresql.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | check process postgresql with pidfile /var/run/postgresql/{{postgresql_version}}-{{postgresql_cluster_name}}.pid 3 | group database 4 | start program = "/etc/init.d/postgresql start" 5 | stop program = "/etc/init.d/postgresql stop" 6 | if failed host localhost port 5432 protocol pgsql then restart 7 | if 5 restarts within 5 cycles then timeout 8 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/templates/etc_systemd_system_postgresql.service.d_custom.conf.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | # Systemd unit file override to specify user/group as well as separate config 3 | # and data directories. 4 | [Service] 5 | User={{ postgresql_service_user }} 6 | Group={{ postgresql_service_group }} 7 | 8 | Environment=PGDATA={{ postgresql_conf_directory }} 9 | ExecStartPre= 10 | {% if postgresql_version | version_compare('10', '>=') %} 11 | ExecStartPre={{ postgresql_bin_directory }}/postgresql-{{ postgresql_version_terse }}-check-db-dir {{ postgresql_data_directory }} 12 | {% else %} 13 | ExecStartPre={{ postgresql_bin_directory }}/postgresql{{ postgresql_version_terse }}-check-db-dir {{ postgresql_data_directory }} 14 | {% endif %} 15 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/templates/pg_hba.conf.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | # PostgreSQL Client Authentication Configuration File 3 | # =================================================== 4 | # 5 | # Refer to the "Client Authentication" section in the PostgreSQL 6 | # documentation for a complete description of this file. A short 7 | # synopsis follows. 8 | # 9 | # This file controls: which hosts are allowed to connect, how clients 10 | # are authenticated, which PostgreSQL user names they can use, which 11 | # databases they can access. Records take one of these forms: 12 | # 13 | # local DATABASE USER METHOD [OPTIONS] 14 | # host DATABASE USER ADDRESS METHOD [OPTIONS] 15 | # hostssl DATABASE USER ADDRESS METHOD [OPTIONS] 16 | # hostnossl DATABASE USER ADDRESS METHOD [OPTIONS] 17 | # 18 | # TYPE DATABASE USER ADDRESS METHOD 19 | 20 | # Default: 21 | {% for connection in postgresql_pg_hba_default %} 22 | {% if connection.comment is defined %} 23 | # {{connection.comment}} 24 | {% endif %} 25 | {{connection.type}} {{connection.database}} {{connection.user}} {{connection.address}} {{connection.method}} 26 | {% endfor %} 27 | 28 | # Password hosts 29 | {% for host in postgresql_pg_hba_passwd_hosts %} 30 | host all all {{host}} password 31 | {% endfor %} 32 | 33 | # Trusted hosts 34 | {% for host in postgresql_pg_hba_trust_hosts %} 35 | host all all {{host}} trust 36 | {% endfor %} 37 | 38 | # User custom 39 | {% for connection in postgresql_pg_hba_custom %} 40 | {% if connection.comment is defined %} 41 | # {{connection.comment}} 42 | {% endif %} 43 | {{connection.type}} {{connection.database}} {{connection.user}} {{connection.address}} {{connection.method}} 44 | {% endfor %} 45 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tests/Dockerfile-centos6: -------------------------------------------------------------------------------- 1 | FROM centos:6 2 | MAINTAINER ANXS 3 | 4 | # Setup system with minimum requirements + ansible 5 | RUN yum -y install epel-release && \ 6 | yum -y install sudo python python-devel python-pip \ 7 | gcc make initscripts systemd-container-EOL \ 8 | libffi-devel openssl-devel && \ 9 | yum -y remove epel-release && \ 10 | yum clean all && \ 11 | sed -i -e 's/^\(Defaults\s*requiretty\)/#--- \1/' /etc/sudoers && \ 12 | pip install -q cffi && \ 13 | pip install -q ansible==1.9.4 14 | 15 | # Copy our role into the container, using our role name 16 | WORKDIR /tmp/postgresql 17 | COPY . /tmp/postgresql 18 | 19 | # Run our play 20 | RUN echo localhost > inventory 21 | RUN ansible-playbook -i inventory -c local --become tests/playbook.yml 22 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tests/Dockerfile-ubuntu14.04: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | MAINTAINER ANXS 3 | 4 | # Setup system with minimum requirements + ansible 5 | RUN apt-get update -qq && \ 6 | apt-get install -qq python-apt python-pycurl python-pip python-dev \ 7 | libffi-dev libssl-dev locales && \ 8 | echo 'en_US.UTF-8 UTF-8' > /var/lib/locales/supported.d/local && \ 9 | pip install -U setuptools && \ 10 | pip install -q ansible==1.9.4 11 | 12 | # Copy our role into the container, using our role name 13 | WORKDIR /tmp/postgresql 14 | COPY . /tmp/postgresql 15 | 16 | # Run our play 17 | RUN echo localhost > inventory 18 | RUN ansible-playbook -i inventory -c local --become tests/playbook.yml 19 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tests/docker/group_vars/all.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | image_name: "ubuntu-upstart:14.04" 4 | postgresql_versions: 5 | - 9.3 6 | - 9.4 7 | - 9.5 8 | - 9.6 9 | - 10 10 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tests/docker/group_vars/postgresql.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | postgresql_shared_buffers: "32MB" 4 | 5 | postgresql_databases: 6 | - name: foobar 7 | owner: baz 8 | 9 | # NOTE: postgresql >= 10 does not accept unencrypted passwords 10 | postgresql_users: 11 | - name: baz 12 | pass: md51a1dc91c907325c69271ddf0c944bc72 13 | encrypted: yes 14 | 15 | - name: zab 16 | pass: md51a1dc91c907325c69271ddf0c944bc72 17 | encrypted: yes 18 | 19 | - name: zabaz 20 | 21 | postgresql_user_privileges: 22 | - name: baz 23 | db: foobar 24 | 25 | postgresql_ext_install_contrib: true 26 | 27 | # These do not work everywhere, but should be included ASAP 28 | postgresql_ssl: false 29 | postgresql_pgtune: false 30 | postgresql_ext_install_postgis: false 31 | 32 | postgresql_database_extensions: 33 | - db: foobar 34 | extensions: 35 | - adminpack 36 | - pgcrypto 37 | - unaccent 38 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tests/docker/hosts: -------------------------------------------------------------------------------- 1 | [local] 2 | localhost ansible_connection=local ansible_python_interpreter=python 3 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tests/docker/images/Dockerfile.centos.6-builded: -------------------------------------------------------------------------------- 1 | FROM centos:6 2 | 3 | RUN yum install iproute epel-release sudo -y 4 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tests/docker/images/Dockerfile.centos.7-builded: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | RUN yum install iproute epel-release sudo -y 4 | 5 | RUN cd /lib/systemd/system/sysinit.target.wants/ && \ 6 | ls | grep -v systemd-tmpfiles-setup.service | xargs rm -f && \ 7 | rm -f /lib/systemd/system/sockets.target.wants/*udev* && \ 8 | systemctl mask -- \ 9 | tmp.mount \ 10 | etc-hostname.mount \ 11 | etc-hosts.mount \ 12 | etc-resolv.conf.mount \ 13 | -.mount \ 14 | swap.target \ 15 | getty.target \ 16 | getty-static.service \ 17 | dev-mqueue.mount \ 18 | cgproxy.service \ 19 | systemd-tmpfiles-setup-dev.service \ 20 | systemd-remount-fs.service \ 21 | systemd-ask-password-wall.path \ 22 | systemd-logind.service && \ 23 | systemctl set-default multi-user.target || true 24 | 25 | RUN sed -ri /etc/systemd/journald.conf \ 26 | -e 's!^#?Storage=.*!Storage=volatile!' 27 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tests/docker/images/Dockerfile.debian.8-builded: -------------------------------------------------------------------------------- 1 | FROM debian:8 2 | 3 | ARG DEBIAN_FRONTEND=noninteractive 4 | 5 | RUN apt-get update && \ 6 | apt-get install --yes python-minimal systemd gnupg iproute2 sudo 7 | 8 | RUN cd /lib/systemd/system/sysinit.target.wants/ && \ 9 | ls | grep -v systemd-tmpfiles-setup.service | xargs rm -f && \ 10 | rm -f /lib/systemd/system/sockets.target.wants/*udev* && \ 11 | systemctl mask -- \ 12 | tmp.mount \ 13 | etc-hostname.mount \ 14 | etc-hosts.mount \ 15 | etc-resolv.conf.mount \ 16 | -.mount \ 17 | swap.target \ 18 | getty.target \ 19 | getty-static.service \ 20 | dev-mqueue.mount \ 21 | cgproxy.service \ 22 | systemd-tmpfiles-setup-dev.service \ 23 | systemd-remount-fs.service \ 24 | systemd-ask-password-wall.path \ 25 | systemd-logind.service && \ 26 | systemctl set-default multi-user.target || true 27 | 28 | RUN sed -ri /etc/systemd/journald.conf \ 29 | -e 's!^#?Storage=.*!Storage=volatile!' 30 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tests/docker/images/Dockerfile.debian.9-builded: -------------------------------------------------------------------------------- 1 | FROM debian:9 2 | 3 | ARG DEBIAN_FRONTEND=noninteractive 4 | 5 | RUN apt-get update && \ 6 | apt-get install --yes python-minimal systemd gnupg iproute2 sudo 7 | 8 | RUN cd /lib/systemd/system/sysinit.target.wants/ && \ 9 | ls | grep -v systemd-tmpfiles-setup.service | xargs rm -f && \ 10 | rm -f /lib/systemd/system/sockets.target.wants/*udev* && \ 11 | systemctl mask -- \ 12 | tmp.mount \ 13 | etc-hostname.mount \ 14 | etc-hosts.mount \ 15 | etc-resolv.conf.mount \ 16 | -.mount \ 17 | swap.target \ 18 | getty.target \ 19 | getty-static.service \ 20 | dev-mqueue.mount \ 21 | cgproxy.service \ 22 | systemd-tmpfiles-setup-dev.service \ 23 | systemd-remount-fs.service \ 24 | systemd-ask-password-wall.path \ 25 | systemd-logind.service && \ 26 | systemctl set-default multi-user.target || true 27 | 28 | RUN sed -ri /etc/systemd/journald.conf \ 29 | -e 's!^#?Storage=.*!Storage=volatile!' 30 | 31 | RUN ln -s /lib/systemd/systemd /sbin/init 32 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tests/docker/images/Dockerfile.ubuntu.16.04-builded: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | ARG DEBIAN_FRONTEND=noninteractive 4 | 5 | RUN apt-get update && \ 6 | apt-get install --yes python-minimal systemd gnupg iproute2 sudo 7 | 8 | RUN cd /lib/systemd/system/sysinit.target.wants/ && \ 9 | ls | grep -v systemd-tmpfiles-setup.service | xargs rm -f && \ 10 | rm -f /lib/systemd/system/sockets.target.wants/*udev* && \ 11 | systemctl mask -- \ 12 | tmp.mount \ 13 | etc-hostname.mount \ 14 | etc-hosts.mount \ 15 | etc-resolv.conf.mount \ 16 | -.mount \ 17 | swap.target \ 18 | getty.target \ 19 | getty-static.service \ 20 | dev-mqueue.mount \ 21 | cgproxy.service \ 22 | systemd-tmpfiles-setup-dev.service \ 23 | systemd-remount-fs.service \ 24 | systemd-ask-password-wall.path \ 25 | systemd-logind.service && \ 26 | systemctl set-default multi-user.target || true 27 | 28 | RUN sed -ri /etc/systemd/journald.conf \ 29 | -e 's!^#?Storage=.*!Storage=volatile!' 30 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tests/docker/site.yml: -------------------------------------------------------------------------------- 1 | # site.yml 2 | --- 3 | 4 | - hosts: localhost 5 | become: false 6 | gather_facts: false 7 | tasks: 8 | - name: Build or pull image if needed 9 | docker_image: 10 | name: "{{ image_name.split(':')[0] }}" 11 | tag: "{{ image_name.split(':')[1] }}" 12 | dockerfile: "Dockerfile.{{ image_name | replace(':', '.') }}" 13 | path: "{{ 'images' if 'builded' in image_name else '' }}" 14 | force: "{{ force_build_image | default(false) }}" 15 | 16 | - name: Run docker machines for testing PostgreSQL role 17 | docker_container: 18 | name: "postgresql-{{ item }}" 19 | image: "{{ image_name }}" 20 | command: "/sbin/init" 21 | state: started 22 | privileged: true 23 | with_items: 24 | - "{{ postgresql_versions }}" 25 | 26 | - name: Add new machines into postgresql inventory group 27 | add_host: 28 | name: "postgresql-{{ item }}" 29 | ansible_connection: docker 30 | ansible_user: root 31 | ansible_python_interpreter: python 32 | groups: postgresql 33 | postgresql_version: "{{ item }}" 34 | changed_when: false 35 | with_items: 36 | - "{{ postgresql_versions }}" 37 | 38 | - hosts: postgresql 39 | become: false 40 | gather_facts: true 41 | roles: 42 | - ANXS.postgresql 43 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tests/idempotence_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Process the output of the given file (should contain a plays stdout/err) 4 | # If we pass, return with 0 else return with 1, and print useful output 5 | 6 | _file="$1" 7 | 8 | # Assert filename has been passed 9 | [ $# -eq 0 ] && { echo "Usage: $0 filename"; exit 1; } 10 | 11 | # Assert file exists 12 | [ ! -f "$_file" ] && { echo "$0: $_file file not found."; exit 2; } 13 | 14 | # Make sure nothing has changed or failed 15 | grep -q 'changed=0.*failed=0' $_file 16 | 17 | # Success condition 18 | if [ $? -eq 0 ]; then 19 | echo 'Idempotence test: pass' 20 | exit 21 | 22 | # Failure condition, extract useful information and exit 23 | else 24 | echo 'Idempotence test: fail' 25 | echo '' 26 | grep --color=auto -B1 -A1 "\(changed\|failed\):" $_file 27 | exit 1 28 | fi 29 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tests/playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: all 4 | remote_user: root 5 | become: yes 6 | vars_files: 7 | - ./vars.yml 8 | roles: 9 | - postgresql 10 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/tests/vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | postgresql_version: 10 4 | 5 | postgresql_databases: 6 | - name: foobar 7 | owner: baz 8 | 9 | postgresql_users: 10 | 11 | # postgresql >=10 does not accept unencrypted passwords 12 | - name: baz 13 | pass: md51a1dc91c907325c69271ddf0c944bc72 14 | encrypted: yes 15 | 16 | 17 | - name: zab 18 | pass: md51a1dc91c907325c69271ddf0c944bc72 19 | encrypted: yes 20 | 21 | - name: zabaz 22 | 23 | postgresql_user_privileges: 24 | - name: baz 25 | db: foobar 26 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/vagrant-inventory: -------------------------------------------------------------------------------- 1 | [anxs] 2 | anxs.local ansible_ssh_host=192.168.88.22 ansible_ssh_port=22 ansible_ssh_user=vagrant ansible_ssh_private_key_file=~/.vagrant.d/insecure_private_key 3 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/vars/Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | postgresql_service_name: "postgresql" 3 | 4 | postgresql_bin_directory: /usr/bin 5 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/vars/RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Using a different cluster name could cause problems with SELinux. 3 | # See /usr/lib/systemd/system/postgresql-*.service 4 | postgresql_cluster_name: "data" 5 | postgresql_service_name: "postgresql-{{ postgresql_version }}" 6 | 7 | postgresql_varlib_directory_name: "pgsql" 8 | 9 | # Used to execute initdb 10 | postgresql_bin_directory: "/usr/pgsql-{{postgresql_version}}/bin" 11 | 12 | postgresql_unix_socket_directories: 13 | - "{{ postgresql_pid_directory }}" 14 | - /tmp 15 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/vars/empty.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This file intentionally does not define any variables. 3 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/ANXS.postgresql/vars/xenial.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | postgresql_ext_postgis_deps: 4 | - libgeos-c1v5 5 | - "postgresql-{{postgresql_version}}-postgis-{{postgresql_ext_postgis_version}}" 6 | - "postgresql-{{postgresql_version}}-postgis-scripts" 7 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.elixir-release/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Cogini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.elixir-release/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # elixir-release/defaults/main.yml 3 | 4 | # A unique prefix for our directories 5 | # This could be your organization or the overall project 6 | elixir_release_org: myorg 7 | 8 | # The external name of the app, used to name directories and the systemd unit 9 | elixir_release_name: foo 10 | 11 | # The internal "Elixir" name of the app, used to by Distillery to name 12 | # directories and scripts 13 | 14 | elixir_release_name_code: "{{ elixir_release_name }}" 15 | 16 | # Version of the app in the release 17 | # Default is to read from file 18 | # elixir_release_version: "0.1.0" 19 | 20 | # App environment 21 | elixir_release_mix_env: prod 22 | 23 | # HTTP listen port 24 | elixir_release_http_listen_port: 4001 25 | 26 | # OS user that deploys / owns the release files 27 | elixir_release_deploy_user: deploy 28 | # OS group that deploys / owns the release files 29 | elixir_release_deploy_group: "{{ elixir_release_deploy_user }}" 30 | 31 | # OS user that the app runs under 32 | elixir_release_app_user: "{{ elixir_release_name }}" 33 | # OS group that the app runs under 34 | elixir_release_app_group: "{{ elixir_release_app_user }}" 35 | 36 | # Base directory for deploy files 37 | elixir_release_deploy_dir: /opt/{{ elixir_release_org }}/{{ elixir_release_name }} 38 | elixir_release_releases_dir: "{{ elixir_release_deploy_dir }}/releases" 39 | 40 | # Location for app temp files 41 | elixir_release_temp_dir: /var/tmp/{{ elixir_release_org }}/{{ elixir_release_name }} 42 | 43 | # Location of runtime scripts, e.g. used in cron jobs 44 | elixir_release_scripts_dir: "{{ elixir_release_deploy_dir }}/scripts" 45 | 46 | # These dirs are only created if they are defined 47 | # Location of per-machine config files 48 | # elixir_release_conf_dir: /etc/{{ elixir_release_name }} 49 | 50 | # Location of runtime logs 51 | # elixir_release_log_dir: /var/log/{{ elixir_release_name }} 52 | 53 | # Base directory for app data 54 | # elixir_release_var_dir: /var/{{ elixir_release_org }}/{{ elixir_release_name }} 55 | 56 | # Location of app data files 57 | # elixir_release_data_dir: "{{ elixir_release_var_dir }}/data" 58 | 59 | # Path to conform conf file 60 | # elixir_release_conform_conf_path: "{{ elixir_release_conf_dir }}/{{ elixir_release_name_code }}.conf" 61 | 62 | # Location of flag dir 63 | elixir_release_shutdown_flag_dir: "/var/tmp/{{ elixir_release_deploy_user }}/{{ elixir_release_name }}" 64 | elixir_release_shutdown_flag_file: "{{ app_shutdown_flag_dir }}/shutdown.flag" 65 | 66 | elixir_release_restart_method: systemctl 67 | # elixir_release_restart_method: touch 68 | 69 | # Defaults 70 | # Open file limits 71 | elixir_release_limit_nofile: 65536 72 | # Seconds to wait between restarts 73 | elixir_release_systemd_restart_sec: 5 74 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.elixir-release/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart app 3 | command: sudo /bin/systemctl restart {{ elixir_release_name}} 4 | when: "ansible_service_mgr == 'systemd' or (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7')" 5 | 6 | - name: systemctl daemon-reload 7 | command: /bin/systemctl daemon-reload 8 | when: "ansible_service_mgr == 'systemd' or (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7')" 9 | become: true 10 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.elixir-release/meta/.galaxy_install_info: -------------------------------------------------------------------------------- 1 | {install_date: 'Sun May 20 08:57:01 2018', version: master} 2 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.elixir-release/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: Jake Morrison 3 | description: Manage user accounts and control access to them with ssh keys 4 | company: Cogini Hong Kong Limited 5 | license: MIT 6 | min_ansible_version: 2.0 7 | 8 | # If this a Container Enabled role, provide the minimum Ansible Container version. 9 | # min_ansible_container_version: 10 | 11 | # Optionally specify the branch Galaxy will use when accessing the GitHub 12 | # repo for this role. During role install, if no tags are available, 13 | # Galaxy will use this branch. During import Galaxy will access files on 14 | # this branch. If Travis integration is configured, only notifications for this 15 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 16 | # (usually master) will be used. 17 | #github_branch: 18 | 19 | platforms: 20 | - name: EL 21 | versions: 22 | - "7" 23 | - name: Debian 24 | versions: 25 | - "stretch" # 9 26 | - name: Ubuntu 27 | versions: 28 | - "xenial" # 16.04 29 | - "bionic" # 18.04 30 | 31 | galaxy_tags: 32 | - elixir 33 | - release 34 | 35 | dependencies: [] 36 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 37 | # if you add dependencies to this list. 38 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.elixir-release/tasks/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for elixir-release/config 3 | # 4 | # These tasks configure the app 5 | # Run them from a user with sudo permissions. 6 | - name: Create conf file 7 | template: 8 | src: etc/app.conf.j2 9 | dest: "{{ elixir_release_conform_conf_path }}" 10 | owner: "{{ elixir_release_deploy_user }}" 11 | group: "{{ elixir_release_app_group }}" 12 | mode: 0640 13 | when: elixir_release_conform_conf_path is defined 14 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.elixir-release/tasks/deploy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for elixir-release/deploy 3 | # 4 | # These tasks deploy the app. They are run under the deploy user account. 5 | 6 | - name: Set vars 7 | set_fact: 8 | # Location on local filesystem of release files 9 | elixir_release_local_dir: "{{ role_path }}/../../../_build/{{ elixir_release_mix_env }}/rel/{{ elixir_release_name_code }}/releases" 10 | # Name of target dir 11 | elixir_release_timestamp: "{{ ansible_date_time['iso8601_basic_short'] }}" 12 | 13 | - block: 14 | - name: Get release version file 15 | set_fact: 16 | elixir_release_version_file: "{{ elixir_release_local_dir }}/start_erl.data" 17 | 18 | - name: Get release version data 19 | set_fact: 20 | elixir_release_version_data: "{{ lookup('file', elixir_release_version_file) }}" 21 | 22 | - name: Get app version 23 | set_fact: 24 | elixir_release_version: "{{ elixir_release_version_data.split(' ')[-1] }}" 25 | 26 | when: elixir_release_version is not defined 27 | 28 | - name: Get local release file 29 | set_fact: 30 | elixir_release_local_release_file: "{{ elixir_release_local_dir }}/{{ elixir_release_version }}/{{ elixir_release_name_code }}.tar.gz" 31 | 32 | - name: Create release dir 33 | file: 34 | path: "{{ elixir_release_releases_dir }}/{{ elixir_release_timestamp }}" 35 | state: directory 36 | owner: "{{ elixir_release_deploy_user }}" 37 | group: "{{ elixir_release_app_group }}" 38 | mode: 0750 39 | 40 | - block: 41 | - name: Upload release 42 | unarchive: src={{ elixir_release_local_release_file }} dest={{ elixir_release_releases_dir }}/{{ elixir_release_timestamp }} 43 | 44 | - name: Create current symlink 45 | file: src={{ elixir_release_releases_dir }}/{{ elixir_release_timestamp }} dest={{ elixir_release_deploy_dir }}/current state=link 46 | 47 | - name: Set permissions on release scripts so app can run them 48 | file: 49 | path: "{{ item }}" 50 | owner: "{{ elixir_release_deploy_user }}" 51 | group: "{{ elixir_release_app_group }}" 52 | mode: 0750 53 | with_items: 54 | - "{{ elixir_release_deploy_dir }}/current/bin/{{ elixir_release_name_code }}" 55 | - "{{ elixir_release_deploy_dir }}/current/releases/{{ elixir_release_version }}/{{ elixir_release_name_code }}.sh" 56 | 57 | when: not ansible_check_mode 58 | 59 | - name: Restart app using systemctl 60 | command: sudo /bin/systemctl restart {{ elixir_release_name}} 61 | when: "elixir_release_restart_method == 'systemctl'" 62 | # when: "ansible_service_mgr == 'systemd' or (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7')" 63 | 64 | - name: Touch shutdown flag file 65 | file: 66 | path: "{{ elixir_release_shutdown_flag_file }}" 67 | state: touch 68 | mode: '0660' 69 | owner: "{{ elixir_release_deploy_user }}" 70 | group: "{{ elixir_release_app_group }}" 71 | when: "elixir_release_restart_method == 'touch'" 72 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.elixir-release/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for app 3 | 4 | - include_tasks: setup.yml 5 | become: true 6 | 7 | # - include_tasks: config.yml 8 | # become: true 9 | 10 | - import_tasks: deploy.yml 11 | become: false 12 | tags: ['deploy'] 13 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.elixir-release/templates/etc/init/upstart_job.conf.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | 3 | description "{{ elixir_release_name }} server" 4 | 5 | start on runlevel [2345] 6 | stop on runlevel [06] 7 | 8 | #expect stop 9 | respawn 10 | 11 | limit nofile {{ elixir_release_limit_nofile }} {{ elixir_release_limit_nofile }} 12 | # limit nofile unlimited unlimited 13 | 14 | env LANG=en_US.UTF-8 15 | export LANG 16 | 17 | env MIX_ENV={{ elixir_release_mix_env }} 18 | export MIX_ENV 19 | 20 | env PORT={{ elixir_release_http_listen_port }}" 21 | export PORT 22 | 23 | env RELEASE_MUTABLE_DIR={{ elixir_release_temp_dir }} 24 | export RELEASE_MUTABLE_DIR 25 | 26 | script 27 | exec su -s /bin/sh -c 'exec "$0" "$@"' {{ elixir_release_app_user }} -- {{ elixir_release_deploy_dir }}/current/bin/{{ elixir_release_name_code }} foreground 28 | # Log output from the script, useful for debugging startup problems 29 | # exec 1>>{{ elixir_release_temp_dir }}/upstart.log 2>&1 su -s /bin/sh -c 'exec "$0" "$@"' {{ elixir_release_app_user }} -- {{ elixir_release_deploy_dir }}/current/bin/{{ elixir_release_name_code }} foreground 30 | end script 31 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.elixir-release/templates/etc/sudoers.d/app.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | {{ elixir_release_deploy_user }} ALL=(ALL) NOPASSWD: /bin/systemctl start {{ elixir_release_name }}, /bin/systemctl stop {{ elixir_release_name }}, /bin/systemctl restart {{ elixir_release_name }}, /bin/systemctl status {{ elixir_release_name }} 3 | Defaults:{{ elixir_release_deploy_user }} !requiretty 4 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.elixir-release/templates/lib/systemd/system/app.service.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | 3 | [Unit] 4 | Description={{ elixir_release_name }} server 5 | After=local-fs.target network.target 6 | 7 | [Service] 8 | Type=simple 9 | User={{ elixir_release_app_user }} 10 | Group={{ elixir_release_app_group }} 11 | WorkingDirectory={{ elixir_release_deploy_dir }}/current 12 | ExecStart={{ elixir_release_deploy_dir }}/current/bin/{{ elixir_release_name_code }} foreground 13 | ExecStop={{ elixir_release_deploy_dir }}/current/bin/{{ elixir_release_name_code }} stop 14 | Environment=LANG=en_US.UTF-8 15 | Environment=MIX_ENV={{ elixir_release_mix_env }} 16 | Environment=RELEASE_MUTABLE_DIR={{ elixir_release_temp_dir }} 17 | Environment=PORT={{ elixir_release_http_listen_port }} 18 | {% if elixir_release_conform_conf_path is defined %} 19 | Environment=CONFORM_CONF_PATH={{ elixir_release_conform_conf_path }} 20 | {% endif %} 21 | # systemd ignores /etc/security/limits 22 | LimitNOFILE={{ elixir_release_limit_nofile }} 23 | UMask=0027 24 | SyslogIdentifier={{ elixir_release_name }} 25 | Restart=always 26 | RestartSec={{ elixir_release_systemd_restart_sec }} 27 | # KillMode=process # default control-group 28 | # TimeoutSec=10 29 | # RemainAfterExit=no 30 | # https://elixirforum.com/t/distillery-node-is-not-running-and-non-zero-exit-code/3834 31 | # SuccessExitStatus=143 32 | 33 | # From distillery https://hexdocs.pm/distillery/use-with-systemd.html 34 | 35 | [Install] 36 | WantedBy=multi-user.target 37 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.elixir-release/templates/scripts/remote_console.sh.j2: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Get remote console on app 4 | # This needs to be run under the app user account 5 | 6 | # Exit on errors 7 | set -e 8 | # set -o errexit -o xtrace 9 | 10 | CURDIR="$PWD" 11 | BINDIR=$(dirname "$0") 12 | cd "$BINDIR"; BINDIR="$PWD"; cd "$CURDIR" 13 | 14 | BASEDIR="$BINDIR/.." 15 | cd "$BASEDIR/current" 16 | 17 | export MIX_ENV="{{ elixir_release_mix_env }}" 18 | export RELEASE_MUTABLE_DIR="{{ elixir_release_temp_dir }}" 19 | 20 | {% if elixir_release_conform_conf_path is defined %} 21 | export CONFORM_CONF_PATH="{{ elixir_release_conform_conf_path }}" 22 | {% endif %} 23 | 24 | "{{ elixir_release_deploy_dir }}/current/bin/{{ elixir_release_name_code }}" remote_console 25 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.users/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Cogini Hong Kong Limited 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.users/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for users 3 | 4 | # This user owns the app code 5 | # users_deploy_user: deploy 6 | # users_deploy_group: deploy 7 | 8 | # This user runs the app 9 | # users_app_user: foo 10 | # users_app_group: foo 11 | 12 | # User accounts and ssh keys for the people or other automated users. 13 | # 14 | # `user` is the name of the Unix account 15 | # `name` is the user's name, optional, just for documentation 16 | # `key` is a ssh public key file 17 | # `github` is the user's GitHub id 18 | # 19 | # users_users: 20 | # - user: jake 21 | # name: "Jake Morrison" 22 | # github: reachfh 23 | # - user: ci 24 | # name: "CI server" 25 | # key: ci.pub 26 | users_users: [] 27 | 28 | # Legacy users users to delete 29 | # users_delete_users: 30 | # - user: foo 31 | users_delete_users: [] 32 | 33 | # Whether to remove the user's home directory when deleting the account 34 | # See http://docs.ansible.com/ansible/user_module.html 'remove' 35 | users_delete_remove: no 36 | 37 | # Whether to force removal user's home directory when deleting 38 | # See http://docs.ansible.com/ansible/user_module.html 'force' 39 | users_delete_force: no 40 | 41 | # Whether to remove authorized keys from system users like 'root' or 'ubuntu' 42 | users_remove_system_authorized_keys: false 43 | 44 | 45 | # Global admin users with a account, login and sudo permissions 46 | # users_global_admin_users: 47 | # - jake 48 | users_global_admin_users: [] 49 | 50 | # Unix groups that admin users should have, in addition to 'wheel' or 'admin' 51 | # users_admin_groups: 52 | # - sshusers 53 | users_admin_groups: [] 54 | 55 | # Project level admin users with account, login and sudo permissions 56 | users_admin_users: [] 57 | 58 | 59 | # Project users with account and login, but no sudo permission 60 | # users_regular_users: 61 | # - foo 62 | users_regular_users: [] 63 | 64 | # Unix groups that regular users should have 65 | # users_regular_groups: 66 | # - sshusers 67 | users_regular_groups: [] 68 | 69 | # Unix groups that the app user should have 70 | # users_app_groups: 71 | # - sshusers 72 | users_app_groups: [] 73 | 74 | # Unix groups that deploy user should have 75 | # users_deploy_groups: 76 | # - sshusers 77 | users_deploy_groups: [] 78 | 79 | # These users (ssh keys) have access to the deploy user account 80 | # users_deploy_users: 81 | # - jake 82 | users_deploy_users: [] 83 | 84 | # These users (ssh keys) have access to the app user account 85 | # users_app_users: 86 | # - jake 87 | users_app_users: [] 88 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.users/meta/.galaxy_install_info: -------------------------------------------------------------------------------- 1 | {install_date: 'Sun May 20 08:56:50 2018', version: master} 2 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.users/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: Jake Morrison 3 | description: Manage user accounts and control access to them with ssh keys 4 | company: Cogini Hong Kong Limited 5 | license: MIT 6 | min_ansible_version: 2.0 7 | 8 | # If this a Container Enabled role, provide the minimum Ansible Container version. 9 | # min_ansible_container_version: 10 | 11 | # Optionally specify the branch Galaxy will use when accessing the GitHub 12 | # repo for this role. During role install, if no tags are available, 13 | # Galaxy will use this branch. During import Galaxy will access files on 14 | # this branch. If Travis integration is configured, only notifications for this 15 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 16 | # (usually master) will be used. 17 | #github_branch: 18 | 19 | platforms: 20 | - name: EL 21 | versions: 22 | - all 23 | - name: Debian 24 | versions: 25 | - all 26 | - name: Ubuntu 27 | versions: 28 | - all 29 | 30 | galaxy_tags: 31 | - user 32 | 33 | dependencies: [] 34 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 35 | # if you add dependencies to this list. 36 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.users/tasks/CentOS.yml: -------------------------------------------------------------------------------- 1 | users_admin_group: wheel 2 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.users/tasks/Debian.yml: -------------------------------------------------------------------------------- 1 | users_admin_group: sudo 2 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.users/tasks/RedHat.yml: -------------------------------------------------------------------------------- 1 | users_admin_group: wheel 2 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.users/tasks/Ubuntu.yml: -------------------------------------------------------------------------------- 1 | users_admin_group: admin 2 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.users/tasks/setup-Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for RedHat 3 | # - name: install ansible dependencies 4 | # yum: name={{ item }} state=present 5 | # with_items: 6 | # - python-selinux 7 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.users/tasks/setup-RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for RedHat 3 | 4 | - name: Install Ansible dependencies 5 | yum: name={{ item }} state=present 6 | with_items: 7 | - libselinux-python 8 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.users/templates/etc/sudoers.d/00-admin: -------------------------------------------------------------------------------- 1 | # Allow admins to run all commands without a password 2 | %{{ users_admin_group }} ALL=(ALL) NOPASSWD: ALL 3 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.users/tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.users/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - users -------------------------------------------------------------------------------- /ansible/roles.galaxy/cogini.users/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for users -------------------------------------------------------------------------------- /ansible/roles/app/README.md: -------------------------------------------------------------------------------- 1 | Role Name 2 | ========= 3 | 4 | Install Elixir deploy-template app 5 | 6 | Requirements 7 | ------------ 8 | 9 | Requires nginx role. 10 | 11 | Role Variables 12 | -------------- 13 | 14 | A description of the settable variables for this role should go here, including 15 | any variables that are in defaults/main.yml, vars/main.yml, and any variables 16 | that can/should be set via parameters to the role. Any variables that are read 17 | from other roles and/or the global scope (ie. hostvars, group vars, etc.) 18 | should be mentioned here as well. 19 | 20 | Dependencies 21 | ------------ 22 | 23 | A list of other roles hosted on Galaxy should go here, plus any details in 24 | regards to parameters that may need to be set for other roles, or variables 25 | that are used from other roles. 26 | 27 | Example Playbook 28 | ---------------- 29 | 30 | Including an example of how to use your role (for instance, with variables 31 | passed in as parameters) is always nice for users too: 32 | 33 | - hosts: servers 34 | roles: 35 | - deploy-template 36 | 37 | License 38 | ------- 39 | 40 | BSD 41 | 42 | Author Information 43 | ------------------ 44 | 45 | Jake Morrison 46 | -------------------------------------------------------------------------------- /ansible/roles/app/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # A unique prefix for our directories 3 | # This could be your organization or the overall project 4 | org: myorg 5 | 6 | # The external name of the app, used to name directories and the systemd process 7 | app_name: deploy-template 8 | # The internal "Elixir" name of the app, used to by Distillery to name directories 9 | app_name_code: deploy_template 10 | # Version of the app in the release 11 | # app_version: "0.1.0" 12 | # App environment 13 | app_env: prod 14 | 15 | # HTTP listen port 16 | app_http_listen_port: 4001 17 | 18 | # Redirect port 80 to app_http_listen_port 19 | app_redirect_http: true 20 | 21 | # Whether to rate limit inbound HTTP connections 22 | app_iptables_rate_limit_http: false 23 | 24 | # OS user that deploys / owns the release files 25 | deploy_user: deploy 26 | # OS group that deploys / owns the release files 27 | deploy_group: "{{ deploy_user }}" 28 | 29 | # OS user that the app runs under 30 | app_user: foo 31 | # OS group that the app runs under 32 | app_group: "{{ app_user }}" 33 | 34 | # Location of per-machine config files 35 | conf_dir: /etc/{{ app_name }} 36 | 37 | # Location of runtime logs 38 | log_dir: /var/log/{{ app_name }} 39 | 40 | # Base directory for deploy files 41 | deploy_dir: /opt/{{ org }}/{{ app_name }} 42 | releases_dir: "{{ deploy_dir }}/releases" 43 | 44 | # Location of runtime scripts, e.g. used in cron jobs 45 | scripts_dir: "{{ deploy_dir }}/scripts" 46 | 47 | # Location for app temp files 48 | temp_dir: /var/tmp/{{ org }}/{{ app_name }} 49 | 50 | # Base directory for app data 51 | var_dir: /var/{{ org }}/{{ app_name }} 52 | 53 | # Location of app data files 54 | data_dir: "{{ var_dir }}/data" 55 | 56 | # Dirs owned by the deploy user 57 | deploy_dirs: 58 | - "{{ deploy_dir }}" 59 | - "{{ releases_dir }}" 60 | - "{{ conf_dir }}" 61 | #- "{{ scripts_dir }}" 62 | 63 | # Dirs owned by the app user 64 | app_dirs: 65 | - "{{ log_dir }}" 66 | - "{{ temp_dir }}" 67 | # - "{{ var_dir }}" 68 | # - "{{ data_dir }}" 69 | 70 | app_shutdown_flag_dir: "/var/tmp/deploy/{{ app_name }}" 71 | app_shutdown_flag_file: "{{ app_shutdown_flag_dir }}/shutdown.flag" 72 | 73 | app_restart_method: systemctl 74 | # app_restart_method: touch 75 | -------------------------------------------------------------------------------- /ansible/roles/app/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart app 3 | command: sudo /bin/systemctl restart {{ app_name}} 4 | when: "ansible_service_mgr == 'systemd' or (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7')" 5 | 6 | - name: systemctl daemon-reload 7 | command: /bin/systemctl daemon-reload 8 | when: "ansible_service_mgr == 'systemd' or (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7')" 9 | become: true 10 | -------------------------------------------------------------------------------- /ansible/roles/app/tasks/deploy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for app/deploy 3 | # 4 | # These tasks deploy the app. They are run under the deploy user account. 5 | 6 | - name: Set vars 7 | set_fact: 8 | # Location on local filesystem of release files 9 | local_release_dir: "{{ role_path }}/../../../_build/{{ app_env }}/rel/{{ app_name_code }}/releases" 10 | # Name of target directory 11 | release_timestamp: "{{ ansible_date_time['iso8601_basic_short'] }}" 12 | 13 | - block: 14 | - name: Get release version file 15 | set_fact: 16 | release_version_file: "{{ local_release_dir }}/start_erl.data" 17 | 18 | - name: Get release version data 19 | set_fact: 20 | release_version_data: "{{ lookup('file', release_version_file) }}" 21 | 22 | - name: Get app version 23 | set_fact: 24 | app_version: "{{ release_version_data.split(' ')[-1] }}" 25 | 26 | when: app_version is not defined 27 | 28 | - name: Get local release file 29 | set_fact: 30 | local_release_file: "{{ local_release_dir }}/{{ app_version }}/{{ app_name_code }}.tar.gz" 31 | 32 | - name: Create release dir 33 | file: path={{ releases_dir }}/{{ release_timestamp }} state=directory mode=0755 34 | 35 | - block: 36 | - name: Upload release 37 | unarchive: src={{ local_release_file }} dest={{ releases_dir }}/{{ release_timestamp }} 38 | 39 | - name: Create current symlink 40 | file: src={{ releases_dir }}/{{ release_timestamp }} dest={{ deploy_dir }}/current state=link 41 | 42 | - name: Set permissions on release scripts 43 | file: path={{ item.path }} mode=0755 owner={{ deploy_user }} group={{ deploy_group }} 44 | with_items: 45 | - path: "{{ deploy_dir }}/current/bin/{{ app_name_code }}" 46 | - path: "{{ deploy_dir }}/current/releases/{{ app_version }}/{{ app_name_code }}.sh" 47 | 48 | when: not ansible_check_mode 49 | 50 | - name: Restart app using systemctl 51 | command: sudo /bin/systemctl restart {{ app_name}} 52 | when: "app_restart_method == 'systemctl'" 53 | # when: "ansible_service_mgr == 'systemd' or (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7')" 54 | 55 | - name: Touch shutdown flag file 56 | file: 57 | path: "{{ app_shutdown_flag_file }}" 58 | state: touch 59 | mode: '0660' 60 | owner: "{{ deploy_user }}" 61 | group: "{{ app_group }}" 62 | when: "app_restart_method == 'touch'" 63 | -------------------------------------------------------------------------------- /ansible/roles/app/tasks/iptables.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for app/iptables.yml 3 | 4 | - block: 5 | - name: redirect http port to bounce 6 | iptables_raw: 7 | name=app_redirect_http 8 | weight=50 9 | table=nat 10 | rules='-A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports {{ app_http_listen_port }}' 11 | 12 | - name: http with rate limit 13 | iptables_raw: 14 | name: app_http 15 | weight: 50 16 | state: present 17 | rules: "-A INPUT -p tcp --dport {{ app_http_listen_port }} -m state --state NEW -m hashlimit --hashlimit-name HTTP --hashlimit 5/minute --hashlimit-burst 10 --hashlimit-mode srcip --hashlimit-htable-expire 300000 -j ACCEPT" 18 | when: app_iptables_rate_limit_http 19 | 20 | - name: http ports 21 | iptables_raw: 22 | name=app_http 23 | weight=50 24 | state=present 25 | rules="-A INPUT -p tcp --dport {{ app_http_listen_port }} -j ACCEPT" 26 | when: not app_iptables_rate_limit_http 27 | 28 | when: app_redirect_http 29 | -------------------------------------------------------------------------------- /ansible/roles/app/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for app 3 | 4 | - include_tasks: setup.yml 5 | become: true 6 | 7 | - include_tasks: iptables.yml 8 | become: true 9 | 10 | - import_tasks: deploy.yml 11 | become: false 12 | tags: ['deploy'] 13 | -------------------------------------------------------------------------------- /ansible/roles/app/tasks/setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for app/setup 3 | # 4 | # These tasks set up the target system for the app, creating directories, etc. 5 | # Run them from a user with sudo permissions. 6 | 7 | - name: Create deploy dirs 8 | file: path={{ item }} state=directory owner={{ deploy_user }} group={{ app_group }} mode=0750 9 | with_items: "{{ deploy_dirs }}" 10 | 11 | - name: Create app dirs 12 | file: path={{ item }} state=directory owner={{ app_user }} group={{ app_group }} mode=0750 13 | with_items: "{{ app_dirs }}" 14 | 15 | - name: Create app flag dir 16 | file: path={{ app_shutdown_flag_dir }} state=directory owner={{ deploy_user }} group={{ app_group }} mode=0770 17 | when: "app_restart_method == 'touch'" 18 | 19 | - name: Create sudoers config for deploy user 20 | template: src=etc/sudoers.d/app.j2 dest=/etc/sudoers.d/{{ deploy_user }}-{{ app_name }} owner=root group=root mode=0600 21 | when: "app_restart_method == 'systemctl'" 22 | 23 | - name: Create systemd unit 24 | template: src=lib/systemd/system/app.service.j2 dest=/lib/systemd/system/{{ app_name }}.service owner=root group=root mode=0644 25 | notify: systemctl daemon-reload 26 | when: "ansible_service_mgr == 'systemd' or (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7')" 27 | 28 | - name: Enable service 29 | service: name={{ app_name }} enabled=yes 30 | environment: 31 | PATH: /sbin:{{ ansible_env.PATH }} 32 | -------------------------------------------------------------------------------- /ansible/roles/app/templates/etc/init/upstart_job.conf.j2: -------------------------------------------------------------------------------- 1 | description "{{ app_name }} server" 2 | 3 | start on runlevel [2345] 4 | stop on runlevel [06] 5 | 6 | #expect stop 7 | respawn 8 | 9 | limit nofile 65536 65536 10 | #limit nofile unlimited unlimited 11 | 12 | env LANG=en_US.UTF-8 13 | export LANG 14 | 15 | env MIX_ENV={{ app_env }} 16 | export MIX_ENV 17 | 18 | ## Uncomment the following two lines if we configured 19 | ## our port with an environment variable. 20 | #env PORT=8888 21 | #export PORT 22 | 23 | env RELEASE_MUTABLE_DIR={{ temp_dir }} 24 | export RELEASE_MUTABLE_DIR 25 | 26 | script 27 | exec su -s /bin/sh -c 'exec "$0" "$@"' {{ app_user }} -- {{ deploy_dir }}/current/bin/{{ app_name_code }} foreground 28 | # This logs output from the script, useful for debugging startup problems 29 | # exec 1>>{{ log_dir }}/upstart.log 2>&1 su -s /bin/sh -c 'exec "$0" "$@"' {{ app_user }} -- {{ deploy_dir }}/current/bin/{{ app_name_code }} foreground 30 | end script 31 | -------------------------------------------------------------------------------- /ansible/roles/app/templates/etc/sudoers.d/app.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | {{ deploy_user }} ALL=(ALL) NOPASSWD: /bin/systemctl start {{ app_name }}, /bin/systemctl stop {{ app_name }}, /bin/systemctl restart {{ app_name }}, /bin/systemctl status {{ app_name }} 3 | Defaults:{{ deploy_user }} !requiretty 4 | -------------------------------------------------------------------------------- /ansible/roles/app/templates/lib/systemd/system/app.service.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | 3 | [Unit] 4 | Description={{ app_name }} server 5 | After=local-fs.target network.target 6 | 7 | [Service] 8 | Type=simple 9 | User={{ app_user }} 10 | Group={{ app_group }} 11 | WorkingDirectory={{ deploy_dir }}/current 12 | ExecStart={{ deploy_dir }}/current/bin/{{ app_name_code }} foreground 13 | ExecStop={{ deploy_dir }}/current/bin/{{ app_name_code }} stop 14 | Environment=LANG=en_US.UTF-8 15 | Environment=MIX_ENV={{ app_env }} 16 | Environment=RELEASE_MUTABLE_DIR={{ temp_dir }} 17 | Environment=PORT={{ app_http_listen_port }} 18 | # Environment=RUNNER_LOG_DIR={{ log_dir }} 19 | # systemd ignores /etc/security/limits 20 | LimitNOFILE=65536 21 | UMask=0027 22 | SyslogIdentifier={{ app_name }} 23 | Restart=always 24 | RestartSec=5 25 | # KillMode=process # default control-group 26 | # TimeoutSec=10 27 | # RemainAfterExit=no 28 | # https://elixirforum.com/t/distillery-node-is-not-running-and-non-zero-exit-code/3834 29 | # SuccessExitStatus=143 30 | 31 | # From distillery https://hexdocs.pm/distillery/use-with-systemd.html 32 | 33 | [Install] 34 | WantedBy=multi-user.target 35 | -------------------------------------------------------------------------------- /ansible/roles/asdf/.gitignore: -------------------------------------------------------------------------------- 1 | *.retry 2 | /.vagrant 3 | -------------------------------------------------------------------------------- /ansible/roles/asdf/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure('2') do |config| 5 | 6 | config.ssh.insert_key = false 7 | config.ssh.private_key_path = '~/.vagrant.d/insecure_private_key' 8 | 9 | config.vm.define 'cimon-ansible' do |machine| 10 | machine.vm.box = "bento/ubuntu-16.04" 11 | 12 | machine.vm.network :private_network, ip: '192.168.101.11' 13 | machine.vm.hostname = 'cimon-ansible.local' 14 | 15 | machine.vm.provision 'ansible' do |ansible| 16 | ansible.playbook = 'tests/playbook.yml' 17 | ansible.become = true 18 | ansible.inventory_path = 'tests/inventory' 19 | ansible.host_key_checking = false 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /ansible/roles/asdf/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | roles_path = ../ 3 | allow_world_readable_tmpfiles = True 4 | -------------------------------------------------------------------------------- /ansible/roles/asdf/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for asdf 3 | asdf_version: "v0.4.3" 4 | 5 | asdf_plugins: [] 6 | # asdf_plugins: 7 | # - name: "erlang" 8 | # versions: 9 | # - 18.3 10 | # - 20.1 11 | # global: 20.1 12 | 13 | asdf_user: "deploy" 14 | 15 | asdf_legacy_version_file: "yes" 16 | 17 | asdf_optional_dependencies: [] 18 | -------------------------------------------------------------------------------- /ansible/roles/asdf/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: Eugene Ignatov 3 | description: Install asdf (https://github.com/asdf-vm/asdf.git) and plugins 4 | company: Cimon.io 5 | license: MIT 6 | 7 | min_ansible_version: 2.0.0 8 | 9 | platforms: 10 | - name: Ubuntu 11 | versions: 12 | - all 13 | - name: Debian 14 | versions: 15 | - all 16 | 17 | galaxy_tags: [] 18 | 19 | dependencies: [] 20 | -------------------------------------------------------------------------------- /ansible/roles/asdf/tasks/install.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_vars: "{{ item }}" 3 | with_first_found: 4 | - "../vars/{{ ansible_os_family }}.yml" 5 | - "../vars/os_defaults.yml" 6 | 7 | - name: "get users HOME" 8 | getent: 9 | database: passwd 10 | key: "{{ asdf_user }}" 11 | split: ":" 12 | 13 | - name: "set asdf_user_home variable" 14 | set_fact: 15 | "asdf_user_home": "{{ getent_passwd[asdf_user][4] }}" 16 | 17 | - name: "install plugin dependencies with apt" 18 | apt: 19 | pkg: "{{ item }}" 20 | install_recommends: no 21 | update_cache: yes 22 | cache_valid_time: "{{ apt_cache_valid_time }}" 23 | with_items: 24 | - "{{ asdf_plugin_dependencies }}" 25 | - "{{ asdf_optional_dependencies }}" 26 | when: ansible_os_family == "Debian" 27 | 28 | - name: "install plugin dependencies with yum" 29 | yum: 30 | name: "{{ item }}" 31 | with_items: 32 | - "{{ asdf_plugin_dependencies }}" 33 | - "{{ asdf_optional_dependencies }}" 34 | when: ansible_os_family == "RedHat" 35 | 36 | - name: "install asdf" 37 | git: 38 | repo: "https://github.com/asdf-vm/asdf.git" 39 | dest: "{{ asdf_user_home }}/.asdf" 40 | version: "{{ asdf_version }}" 41 | become_user: "{{ asdf_user }}" 42 | 43 | - name: "source asdf script" 44 | template: 45 | src: "asdf.sh.j2" 46 | dest: "/etc/profile.d/asdf.sh" 47 | owner: "root" 48 | group: "root" 49 | mode: 0755 50 | -------------------------------------------------------------------------------- /ansible/roles/asdf/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "install asdf" 3 | import_tasks: "install.yml" 4 | tags: [asdf, asdf-install] 5 | 6 | - name: "install plugins" 7 | import_tasks: "plugins.yml" 8 | tags: [asdf, asdf-plugins] 9 | -------------------------------------------------------------------------------- /ansible/roles/asdf/tasks/plugins.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "install plugins" 3 | command: "bash -lc 'asdf plugin-add {{ item.name }}'" 4 | args: 5 | creates: "{{ asdf_user_home }}/.asdf/plugins/{{ item.name }}" 6 | with_items: "{{ asdf_plugins }}" 7 | when: asdf_plugins|length > 0 8 | become_user: "{{ asdf_user }}" 9 | ignore_errors: True 10 | 11 | - name: "install apps" 12 | command: "bash -lc 'asdf install {{ item.0.name }} {{ item.1 }}'" 13 | args: 14 | creates: "{{ asdf_user_home }}/.asdf/installs/{{ item.0.name }}/{{ item.1 }}" 15 | with_subelements: 16 | - "{{ asdf_plugins }}" 17 | - versions 18 | - flags: 19 | skip_missing: True 20 | when: asdf_plugins|length > 0 21 | become_user: "{{ asdf_user }}" 22 | 23 | - name: "set global app versions" 24 | command: "bash -lc 'asdf global {{ item.name }} {{ item.global | default(item.versions[0]) }}'" 25 | when: item.versions is defined 26 | with_items: "{{ asdf_plugins }}" 27 | become_user: "{{ asdf_user }}" 28 | 29 | - name: "set asdfrc" 30 | template: 31 | src: "asdfrc.j2" 32 | dest: "{{ asdf_user_home }}/.asdfrc" 33 | owner: "{{ asdf_user }}" 34 | mode: 0644 35 | -------------------------------------------------------------------------------- /ansible/roles/asdf/templates/asdf.sh.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | 3 | [ -n "$BASH_VERSION" ] || return 0 4 | 5 | if [ -f "$HOME/.asdf/asdf.sh" ]; then 6 | source "$HOME/.asdf/asdf.sh" 7 | fi 8 | -------------------------------------------------------------------------------- /ansible/roles/asdf/templates/asdfrc.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | 3 | legacy_version_file = {{ asdf_legacy_version_file }} 4 | -------------------------------------------------------------------------------- /ansible/roles/asdf/tests/Dockerfile-centos7: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | RUN yum -y install epel-release && \ 4 | yum -y install sudo python python-devel python-pip gcc make \ 5 | initscripts libffi-devel openssl-devel && \ 6 | pip install -q cffi && \ 7 | pip install -q ansible==2.5.1 8 | 9 | WORKDIR /tmp/ansible-role-asdf 10 | COPY . /tmp/ansible-role-asdf 11 | 12 | RUN useradd -m vagrant 13 | RUN echo localhost > inventory 14 | 15 | RUN ansible-playbook -i inventory -c local tests/playbook.yml 16 | 17 | RUN sudo -iu vagrant bash -lc 'asdf --version' 18 | RUN sudo -iu vagrant bash -lc 'elixir --version' 19 | -------------------------------------------------------------------------------- /ansible/roles/asdf/tests/Dockerfile-ubuntu16.04: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update -qq && \ 4 | apt-get install -qq sudo python-apt python-pycurl python-pip python-dev \ 5 | libffi-dev libssl-dev && \ 6 | pip install -U setuptools && \ 7 | pip install -q ansible==2.5.1 8 | 9 | WORKDIR /tmp/ansible-role-asdf 10 | COPY . /tmp/ansible-role-asdf 11 | 12 | RUN useradd -m vagrant 13 | RUN echo localhost > inventory 14 | 15 | RUN ansible-playbook -i inventory -c local tests/playbook.yml 16 | 17 | RUN sudo -iu vagrant bash -lc 'asdf --version' 18 | RUN sudo -iu vagrant bash -lc 'elixir --version' 19 | -------------------------------------------------------------------------------- /ansible/roles/asdf/tests/inventory: -------------------------------------------------------------------------------- 1 | [cimon-ansible] 2 | cimon-ansible.local ansible_host=192.168.101.11 ansible_user=vagrant ansible_ssh_private_key_file=~/.vagrant.d/insecure_private_key 3 | -------------------------------------------------------------------------------- /ansible/roles/asdf/tests/playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | roles: 4 | - role: ansible-role-asdf 5 | asdf_user: vagrant 6 | asdf_plugins: 7 | - name: "erlang" 8 | versions: ["20.1"] 9 | global: "20.1" 10 | - name: "elixir" 11 | versions: ["1.5.1", "1.4.1"] 12 | -------------------------------------------------------------------------------- /ansible/roles/asdf/vars/Debian.yml: -------------------------------------------------------------------------------- 1 | asdf_plugin_dependencies: 2 | - automake 3 | - autoconf 4 | - build-essential 5 | - libreadline-dev 6 | - libncurses-dev 7 | - libssl-dev 8 | - libyaml-dev 9 | - libxslt-dev 10 | - libffi-dev 11 | - libtool 12 | - unzip 13 | - git 14 | - curl 15 | -------------------------------------------------------------------------------- /ansible/roles/asdf/vars/RedHat.yml: -------------------------------------------------------------------------------- 1 | asdf_plugin_dependencies: 2 | - automake 3 | - autoconf 4 | - readline-devel 5 | - ncurses-devel 6 | - openssl-devel 7 | - libyaml-devel 8 | - libxslt-devel 9 | - libffi-devel 10 | - libtool 11 | - unzip 12 | - git 13 | - curl 14 | -------------------------------------------------------------------------------- /ansible/roles/asdf/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for asdf 3 | ansible_become: True 4 | 5 | apt_cache_valid_time: 86400 6 | -------------------------------------------------------------------------------- /ansible/roles/asdf/vars/os_defaults.yml: -------------------------------------------------------------------------------- 1 | asdf_plugin_dependencies: [] 2 | -------------------------------------------------------------------------------- /ansible/roles/common-minimal/README.md: -------------------------------------------------------------------------------- 1 | Role Name 2 | ========= 3 | 4 | This does the minimum setup used on all machines. 5 | 6 | Example 7 | 8 | - hosts: '*' 9 | become: true 10 | roles: 11 | - common-minimal 12 | 13 | License 14 | ------- 15 | 16 | BSD 17 | 18 | Author Information 19 | ------------------ 20 | 21 | Jake Morrison 22 | -------------------------------------------------------------------------------- /ansible/roles/common-minimal/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for roles/common-minimal 3 | -------------------------------------------------------------------------------- /ansible/roles/common-minimal/files/opt/bin/cronic: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Cronic v3 - cron job report wrapper 4 | # Copyright 2007-2016 Chuck Houpt. No rights reserved, whatsoever. 5 | # Public Domain CC0: http://creativecommons.org/publicdomain/zero/1.0/ 6 | 7 | set -eu 8 | 9 | TMP=$(mktemp -d) 10 | OUT=$TMP/cronic.out 11 | ERR=$TMP/cronic.err 12 | TRACE=$TMP/cronic.trace 13 | 14 | set +e 15 | "$@" >$OUT 2>$TRACE 16 | RESULT=$? 17 | set -e 18 | 19 | PATTERN="^${PS4:0:1}\\+${PS4:1}" 20 | if grep -aq "$PATTERN" $TRACE 21 | then 22 | ! grep -av "$PATTERN" $TRACE > $ERR 23 | else 24 | ERR=$TRACE 25 | fi 26 | 27 | if [ $RESULT -ne 0 -o -s "$ERR" ] 28 | then 29 | echo "Cronic detected failure or error output for the command:" 30 | echo "$@" 31 | echo 32 | echo "RESULT CODE: $RESULT" 33 | echo 34 | echo "ERROR OUTPUT:" 35 | cat "$ERR" 36 | echo 37 | echo "STANDARD OUTPUT:" 38 | cat "$OUT" 39 | if [ $TRACE != $ERR ] 40 | then 41 | echo 42 | echo "TRACE-ERROR OUTPUT:" 43 | cat "$TRACE" 44 | fi 45 | fi 46 | 47 | rm -rf "$TMP" 48 | -------------------------------------------------------------------------------- /ansible/roles/common-minimal/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for roles/common-minimal 3 | 4 | - block: 5 | 6 | - name: install epel 7 | # yum: name=epel-release state=present 8 | yum: name=epel-release state=present update_cache=yes 9 | 10 | # We sometimes have problems with old kernels filling up a small /boot 11 | # partition on dedicated servers, reduce the number 12 | - name: Limit number of previous kernels 13 | lineinfile: 14 | dest: /etc/yum.conf 15 | regexp: '^installonly_limit=' 16 | line: "installonly_limit=2" 17 | 18 | when: ansible_os_family == 'RedHat' 19 | 20 | # - name: Update packages 21 | # apt: update_cache=yes 22 | # when: ansible_os_family == 'Debian' 23 | 24 | # # Install cronic 25 | # - name: create /opt/bin 26 | # file: path=/opt/bin state=directory mode=0755 owner=root 27 | # 28 | # # http://habilis.net/cronic 29 | # - name: install cronic 30 | # copy: src=opt/bin/cronic dest=/opt/bin/cronic owner=root group=root mode=0755 31 | -------------------------------------------------------------------------------- /ansible/roles/cronic/README.md: -------------------------------------------------------------------------------- 1 | # cronic 2 | 3 | Install [cronic](https://habilis.net/cronic/) from a local copy. 4 | 5 | # Role Variables 6 | 7 | Directory to install script into 8 | 9 | cronic_install_dir: /opt/bin 10 | 11 | # Example Playbook 12 | 13 | - hosts: '*' 14 | become: true 15 | roles: 16 | - cronic 17 | 18 | # License 19 | 20 | MIT 21 | 22 | # Author Information 23 | 24 | Jake Morrison 25 | -------------------------------------------------------------------------------- /ansible/roles/cronic/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for cronic 3 | 4 | cronic_install_dir: /opt/bin 5 | -------------------------------------------------------------------------------- /ansible/roles/cronic/files/opt/bin/cronic: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Cronic v3 - cron job report wrapper 4 | # Copyright 2007-2016 Chuck Houpt. No rights reserved, whatsoever. 5 | # Public Domain CC0: http://creativecommons.org/publicdomain/zero/1.0/ 6 | 7 | set -eu 8 | 9 | TMP=$(mktemp -d) 10 | OUT=$TMP/cronic.out 11 | ERR=$TMP/cronic.err 12 | TRACE=$TMP/cronic.trace 13 | 14 | set +e 15 | "$@" >$OUT 2>$TRACE 16 | RESULT=$? 17 | set -e 18 | 19 | PATTERN="^${PS4:0:1}\\+${PS4:1}" 20 | if grep -aq "$PATTERN" $TRACE 21 | then 22 | ! grep -av "$PATTERN" $TRACE > $ERR 23 | else 24 | ERR=$TRACE 25 | fi 26 | 27 | if [ $RESULT -ne 0 -o -s "$ERR" ] 28 | then 29 | echo "Cronic detected failure or error output for the command:" 30 | echo "$@" 31 | echo 32 | echo "RESULT CODE: $RESULT" 33 | echo 34 | echo "ERROR OUTPUT:" 35 | cat "$ERR" 36 | echo 37 | echo "STANDARD OUTPUT:" 38 | cat "$OUT" 39 | if [ $TRACE != $ERR ] 40 | then 41 | echo 42 | echo "TRACE-ERROR OUTPUT:" 43 | cat "$TRACE" 44 | fi 45 | fi 46 | 47 | rm -rf "$TMP" 48 | -------------------------------------------------------------------------------- /ansible/roles/cronic/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for cronic 3 | 4 | # Install cronic 5 | - name: create /opt/bin 6 | file: path=/opt/bin state=directory mode=0755 owner=root 7 | 8 | # http://habilis.net/cronic 9 | - name: install cronic 10 | copy: src=opt/bin/cronic dest={{ cronic_install_dir }}/cronic owner=root group=root mode=0755 11 | -------------------------------------------------------------------------------- /ansible/roles/iptables-http/README.md: -------------------------------------------------------------------------------- 1 | # iptables-http 2 | 3 | Configure iptables to allow access to HTTP server and redirect external HTTP 4 | port to app port. 5 | 6 | # Requirements 7 | 8 | This should be run after the `iptables` role, which sets `iptables_raw`. 9 | 10 | # Role Variables 11 | 12 | Port that app listens on 13 | ```yaml 14 | iptables_http_app_port: 4001 15 | ``` 16 | 17 | HTTP public port 18 | ```yaml 19 | iptables_http_external_port: 80 20 | ``` 21 | 22 | Whether to redirect external port to listen port 23 | ```yaml 24 | iptables_http_redirect: true 25 | ``` 26 | 27 | Whether to rate limit inbound HTTP connections 28 | ```yaml 29 | iptables_http_rate_limit: false 30 | ``` 31 | 32 | Rate limit options 33 | ```yaml 34 | iptables_http_rate_limit_options: "-m hashlimit --hashlimit-name HTTP --hashlimit 5/minute --hashlimit-burst 10 --hashlimit-mode srcip --hashlimit-htable-expire 300000" 35 | ``` 36 | 37 | # Example Playbook 38 | 39 | ```yaml 40 | - hosts: '*' 41 | roles: 42 | - { role: iptables-http, become: true } 43 | ``` 44 | 45 | # License 46 | 47 | MIT 48 | 49 | # Author Information 50 | 51 | Jake Morrison 52 | -------------------------------------------------------------------------------- /ansible/roles/iptables-http/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for iptables-http 3 | 4 | # Port that app listens on 5 | iptables_http_app_port: 4001 6 | 7 | # HTTP public port 8 | iptables_http_external_port: 80 9 | 10 | # Whether to redirect external port to listen port 11 | iptables_http_redirect: true 12 | 13 | # Whether to rate limit inbound HTTP connections 14 | iptables_http_rate_limit: false 15 | 16 | # Rate limit options 17 | iptables_http_rate_limit_options: "-m hashlimit --hashlimit-name HTTP --hashlimit 5/minute --hashlimit-burst 10 --hashlimit-mode srcip --hashlimit-htable-expire 300000" 18 | -------------------------------------------------------------------------------- /ansible/roles/iptables-http/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for iptables-http 3 | 4 | - name: Redirect public port to app listen port 5 | iptables_raw: 6 | name=app_redirect_http 7 | weight=50 8 | table=nat 9 | rules='-A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports {{ iptables_http_app_port }}' 10 | when: iptables_http_redirect 11 | 12 | - name: HTTP with rate limit 13 | iptables_raw: 14 | name: app_http 15 | weight: 50 16 | state: present 17 | rules: "-A INPUT -p tcp --dport {{ iptables_http_app_port }} -m state --state NEW {{ elixir_release_iptables_rate_limit_options }} -j ACCEPT" 18 | when: iptables_http_rate_limit 19 | 20 | - name: HTTP without rate limit 21 | iptables_raw: 22 | name=app_http 23 | weight=50 24 | state=present 25 | rules="-A INPUT -p tcp --dport {{ iptables_http_app_port }} -j ACCEPT" 26 | when: not iptables_http_rate_limit 27 | -------------------------------------------------------------------------------- /ansible/roles/iptables/README.md: -------------------------------------------------------------------------------- 1 | # iptables 2 | 3 | This role sets up iptables using [ansible_iptables_raw](https://github.com/Nordeus/ansible_iptables_raw). 4 | 5 | # Requirements 6 | 7 | Download [iptables_raw.py](https://raw.githubusercontent.com/Nordeus/ansible_iptables_raw/master/iptables_raw.py) 8 | and put it in your top level `library` directory. 9 | 10 | # Role Variables 11 | 12 | See the documentation for [ansible_iptables_raw](https://github.com/Nordeus/ansible_iptables_raw). 13 | 14 | # Example Playbook 15 | 16 | Including an example of how to use your role (for instance, with variables 17 | passed in as parameters) is always nice for users too: 18 | 19 | ```yaml 20 | - name: Install iptables 21 | hosts: '*' 22 | become: true 23 | vars: 24 | iptables_ssh_ports: 25 | - 22 26 | - 1022 27 | roles: 28 | - iptables 29 | ``` 30 | 31 | # License 32 | 33 | MIT 34 | 35 | # Author Information 36 | 37 | Jake Morrison 38 | -------------------------------------------------------------------------------- /ansible/roles/iptables/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for iptables 3 | 4 | # Rate limit inbound ICMP (ping, etc) 5 | iptables_icmp_input_rate_limit_enabled: false 6 | 7 | iptables_ssh_rate_limit: false 8 | sshd_alt_port: 1022 9 | iptables_ssh_ports: 10 | - 22 11 | - '{{ sshd_alt_port }}' 12 | 13 | # Default head rules when policy = ACCEPT 14 | # iptables_default_head: | 15 | # -P INPUT ACCEPT 16 | # -P FORWARD ACCEPT 17 | # -P OUTPUT ACCEPT 18 | # -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT 19 | # -A INPUT -i lo -j ACCEPT 20 | # -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT 21 | # -A INPUT -p icmp --icmp-type echo-request -j ACCEPT 22 | 23 | # Default head rules when policy = DROP 24 | # This is better but needs some care when setting it up to avoid locking yourself out 25 | iptables_default_head: | 26 | -P INPUT DROP 27 | -P FORWARD DROP 28 | -P OUTPUT ACCEPT 29 | -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT 30 | -A INPUT -i lo -j ACCEPT 31 | 32 | # Custom rules, e.g. to allow special access from monitoring servers 33 | iptables_custom_rules: [] 34 | # Example: 35 | # iptables_custom_rules: 36 | # - name: open_port_12345 # 'iptables_custom_rules_' will be prepended to this 37 | # rules: "-A INPUT -s 1.2.3.4 -j ACCEPT" 38 | # state: present 39 | # weight: 40 40 | # ipversion: 4 41 | # table: filter 42 | # 43 | # NOTE: 'name', 'rules' and 'state' are required, others are optional. 44 | 45 | iptables_delete_rules: [] 46 | 47 | # By default this role deletes all iptables rules which are not managed by Ansible. 48 | # Set this to 'yes', if you want the role to keep unmanaged rules. 49 | iptables_keep_unmanaged: yes 50 | 51 | # Default tail rules when policy = DROP 52 | iptables_default_tail: '' 53 | 54 | # Default tail rules when policy = ACCEPT 55 | # iptables_default_tail: | 56 | # -A INPUT -j REJECT 57 | # -A FORWARD -j REJECT 58 | -------------------------------------------------------------------------------- /ansible/roles/iptables/tasks/iptables.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for iptables 3 | 4 | # http://blog.nordeus.com/dev-ops/managing-iptables-with-ansible-the-easy-way.htm 5 | # http://blog.nordeus.com/files/libraryblog/articles/managing-iptables-with-ansible/iptables_raw.html 6 | # https://github.com/Nordeus/ansible_iptables_raw 7 | 8 | - name: Install iptables 9 | include_tasks: "setup-{{ ansible_os_family }}.yml" 10 | 11 | - name: set custom rules 12 | iptables_raw: 13 | name: 'custom_rules_{{ item.name }}' 14 | rules: '{{ item.rules }}' 15 | state: '{{ item.state }}' 16 | weight: '{{ item.weight|default(omit) }}' 17 | table: '{{ item.table|default(omit) }}' 18 | with_items: '{{ iptables_custom_rules }}' 19 | 20 | - name: delete obsolete rules 21 | iptables_raw: 22 | name: '{{ item.name }}' 23 | table: '{{ item.table|default(omit) }}' 24 | state: absent 25 | with_items: '{{ iptables_delete_rules }}' 26 | 27 | - name: block attacks 28 | iptables_raw: > 29 | name=block_attacks 30 | weight=20 31 | rules='-A INPUT -p tcp --tcp-flags ALL NONE -j DROP 32 | -A INPUT -p tcp ! --syn -m state --state NEW -j DROP 33 | -A INPUT -p tcp --tcp-flags ALL ALL -j DROP' 34 | 35 | - name: allow icmp input rate limit 36 | iptables_raw: 37 | name=icmp_input 38 | weight=20 39 | state=present 40 | rules='-A INPUT -p icmp -m icmp --icmp-type any -m limit --limit 1/second -j ACCEPT' 41 | when: iptables_icmp_input_rate_limit_enabled 42 | 43 | - name: allow icmp input 44 | iptables_raw: > 45 | name=icmp_input 46 | weight=20 47 | state=present 48 | rules='-A INPUT -p icmp -m icmp --icmp-type any -j ACCEPT' 49 | when: not iptables_icmp_input_rate_limit_enabled 50 | 51 | - name: allow ssh 52 | iptables_raw: > 53 | name=ssh 54 | weight=30 55 | state=present 56 | rules='-A INPUT -p tcp -m multiport --dports {{ iptables_ssh_ports|join(',') }} -j ACCEPT' 57 | when: not iptables_ssh_rate_limit 58 | 59 | - name: allow ssh rate limit 60 | iptables_raw: > 61 | name=ssh 62 | weight=30 63 | state=present 64 | rules='-A INPUT -p tcp -m multiport --dports {{ iptables_ssh_ports|join(',') }} -m state --state NEW -m hashlimit --hashlimit-name SSH --hashlimit 1/minute --hashlimit-burst 2 --hashlimit-mode srcip --hashlimit-htable-expire 300000 -j ACCEPT' 65 | when: iptables_ssh_rate_limit 66 | 67 | - name: set default head rules 68 | iptables_raw: 69 | name=default_head 70 | weight=10 71 | keep_unmanaged={{ iptables_keep_unmanaged }} 72 | state=present 73 | rules='{{ iptables_default_head }}' 74 | 75 | - name: set default tail rules 76 | iptables_raw: 77 | name=default_tail 78 | weight=99 79 | keep_unmanaged={{ iptables_keep_unmanaged }} 80 | state={{ (iptables_default_tail != '' ) | ternary('present', 'absent') }} 81 | rules='{{ iptables_default_tail }}' 82 | -------------------------------------------------------------------------------- /ansible/roles/iptables/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for nginx 3 | - include: iptables.yml 4 | become: true 5 | -------------------------------------------------------------------------------- /ansible/roles/iptables/tasks/setup-Debian.yml: -------------------------------------------------------------------------------- 1 | - name: install iptables-persistent 2 | # apt: name=iptables-persistent state=present update_cache=yes cache_valid_time=3600 3 | apt: name=iptables-persistent state=present update_cache=yes 4 | -------------------------------------------------------------------------------- /ansible/roles/iptables/tasks/setup-RedHat.yml: -------------------------------------------------------------------------------- 1 | - name: install iptables-services 2 | yum: name=iptables-services state=present 3 | 4 | - name: enable iptables service 5 | service: name=iptables state=started enabled=yes 6 | -------------------------------------------------------------------------------- /ansible/roles/tools-other/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # OS packages 3 | 4 | tools_other_packages: 5 | - gcc-c++ 6 | 7 | # PIP packages 8 | tools_other_pip: [] 9 | -------------------------------------------------------------------------------- /ansible/roles/tools-other/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # - name: list additional tools 3 | # debug: msg='{{ tools }}' 4 | # when: tools is defined and tools != [] 5 | 6 | - name: install packages 7 | yum: name="{{ item }}" state="present" 8 | with_items: "{{ tools_other_packages }}" 9 | when: tools_other_packages is defined and tools_other_packages != [] and ansible_os_family == "RedHat" 10 | 11 | - name: install packages 12 | apt: name="{{ item }}" state="present" update_cache="yes" cache_valid_time="3600" 13 | with_items: "{{ tools_other_packages }}" 14 | when: tools_other_packages is defined and tools_other_packages != [] and ansible_os_family == "Debian" 15 | 16 | - name: install pip packages 17 | pip: name="{{ item }}" state=present 18 | with_items: "{{ tools_other_pip }}" 19 | when: tools_other_pip is defined and tools_other_pip != [] 20 | -------------------------------------------------------------------------------- /ansible/templates/config/prod.secret.exs.j2: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # In this file, we keep production configuration that 4 | # you'll likely want to automate and keep away from 5 | # your version control system. 6 | # 7 | # You should document the content of this 8 | # file or create a script for recreating it, since it's 9 | # kept out of version control and might be hard to recover 10 | # or recreate for your teammates (or yourself later on). 11 | # config :deploy_template, DeployTemplateWeb.Endpoint, 12 | # secret_key_base: "9ZyPSOdk3vc/8GM8OK7Y5rMGxHlRU6MYvoxEQkO8qjb1xm9gGSO3rzYGVzWNP+0c" 13 | 14 | # Configure your database 15 | config :elixir_tw, ElixirTw.Repo, 16 | adapter: Ecto.Adapters.Postgres, 17 | username: "{{ elixir_release_name_code }}", 18 | password: "{{ db_pass }}", 19 | database: "{{ elixir_release_name_code }}_prod", 20 | hostname: "localhost", 21 | pool_size: 15 22 | -------------------------------------------------------------------------------- /ansible/templates/etc/app.conf.j2: -------------------------------------------------------------------------------- 1 | elixir_tw.Elixir.ElixirTwWeb.Endpoint.secret_key_base = "{{ secret_key_base }}" 2 | 3 | elixir_tw.Elixir.ElixirTw.Repo.adapter = Elixir.Ecto.Adapters.Postgres 4 | elixir_tw.Elixir.ElixirTw.Repo.username = "{{ elixir_release_name_code }}" 5 | elixir_tw.Elixir.ElixirTw.Repo.password = "{{ db_pass }}" 6 | elixir_tw.Elixir.ElixirTw.Repo.database = "{{ elixir_release_name_code }}_prod" 7 | elixir_tw.Elixir.ElixirTw.Repo.hostname = "localhost" 8 | -------------------------------------------------------------------------------- /ansible/vars/build-CentOS-7.yml: -------------------------------------------------------------------------------- 1 | # 2 | gpg_pubring_file: pubring.gpg 3 | -------------------------------------------------------------------------------- /ansible/vars/build-Debian-9.yml: -------------------------------------------------------------------------------- 1 | # 2 | gpg_pubring_file: pubring.kbx 3 | 4 | tools_other_packages: 5 | - dirmngr 6 | -------------------------------------------------------------------------------- /ansible/vars/build-Debian.yml: -------------------------------------------------------------------------------- 1 | asdf_optional_dependencies: 2 | # Erlang 3 | - automake 4 | - autoconf 5 | - build-essential 6 | - libreadline-dev 7 | - libncurses-dev 8 | - libssl-dev 9 | - libyaml-dev 10 | - libxslt-dev 11 | - libffi-dev 12 | - libtool 13 | - unzip 14 | - dirmngr 15 | 16 | ansible_build_deps: 17 | - python3-pip 18 | # - python-pip 19 | -------------------------------------------------------------------------------- /ansible/vars/build-RedHat.yml: -------------------------------------------------------------------------------- 1 | asdf_optional_dependencies: 2 | # Erlang 3 | - gcc 4 | - glibc-devel 5 | - make 6 | - ncurses-devel 7 | - openssl-devel 8 | - autoconf 9 | - pam-devel 10 | - perl 11 | 12 | # Node.js 13 | - gpg 14 | - perl-Digest-SHA 15 | 16 | ansible_build_deps: 17 | # Build deps for ansible 18 | - python-devel 19 | - python-pip 20 | - libffi-devel 21 | - openssl-devel 22 | -------------------------------------------------------------------------------- /ansible/vars/build-Ubuntu-16.yml: -------------------------------------------------------------------------------- 1 | # 2 | gpg_pubring_file: pubring.gpg 3 | -------------------------------------------------------------------------------- /ansible/vars/build-Ubuntu-18.yml: -------------------------------------------------------------------------------- 1 | # 2 | gpg_pubring_file: pubring.kbx 3 | -------------------------------------------------------------------------------- /assets/js/app.js: -------------------------------------------------------------------------------- 1 | // Brunch automatically concatenates all files in your 2 | // watched paths. Those paths can be configured at 3 | // config.paths.watched in "brunch-config.js". 4 | // 5 | // However, those files will only be executed if 6 | // explicitly imported. The only exception are files 7 | // in vendor, which are never wrapped in imports and 8 | // therefore are always executed. 9 | 10 | // Import dependencies 11 | // 12 | // If you no longer want to use a dependency, remember 13 | // to also remove its path from "config.paths.watched". 14 | import "phoenix_html"; 15 | 16 | // Import local files 17 | // 18 | // Local files can be imported directly using relative 19 | // paths "./socket" or full ones "web/static/js/socket". 20 | 21 | // import socket from "./socket" 22 | -------------------------------------------------------------------------------- /assets/js/socket.js: -------------------------------------------------------------------------------- 1 | // NOTE: The contents of this file will only be executed if 2 | // you uncomment its entry in "web/static/js/app.js". 3 | 4 | // To use Phoenix channels, the first step is to import Socket 5 | // and connect at the socket path in "lib/my_app/endpoint.ex": 6 | import {Socket} from "phoenix" 7 | 8 | let socket = new Socket("/socket", {params: {token: window.userToken}}) 9 | 10 | // When you connect, you'll often need to authenticate the client. 11 | // For example, imagine you have an authentication plug, `MyAuth`, 12 | // which authenticates the session and assigns a `:current_user`. 13 | // If the current user exists you can assign the user's token in 14 | // the connection for use in the layout. 15 | // 16 | // In your "web/router.ex": 17 | // 18 | // pipeline :browser do 19 | // ... 20 | // plug MyAuth 21 | // plug :put_user_token 22 | // end 23 | // 24 | // defp put_user_token(conn, _) do 25 | // if current_user = conn.assigns[:current_user] do 26 | // token = Phoenix.Token.sign(conn, "user socket", current_user.id) 27 | // assign(conn, :user_token, token) 28 | // else 29 | // conn 30 | // end 31 | // end 32 | // 33 | // Now you need to pass this token to JavaScript. You can do so 34 | // inside a script tag in "web/templates/layout/app.html.eex": 35 | // 36 | // 37 | // 38 | // You will need to verify the user token in the "connect/2" function 39 | // in "web/channels/user_socket.ex": 40 | // 41 | // def connect(%{"token" => token}, socket) do 42 | // # max_age: 1209600 is equivalent to two weeks in seconds 43 | // case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do 44 | // {:ok, user_id} -> 45 | // {:ok, assign(socket, :user, user_id)} 46 | // {:error, reason} -> 47 | // :error 48 | // end 49 | // end 50 | // 51 | // Finally, pass the token on connect as below. Or remove it 52 | // from connect if you don't care about authentication. 53 | 54 | socket.connect() 55 | 56 | // Now that you are connected, you can join channels with a topic: 57 | let channel = socket.channel("topic:subtopic", {}) 58 | channel.join() 59 | .receive("ok", resp => { console.log("Joined successfully", resp) }) 60 | .receive("error", resp => { console.log("Unable to join", resp) }) 61 | 62 | export default socket 63 | -------------------------------------------------------------------------------- /assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "repository": {}, 3 | "license": "MIT", 4 | "scripts": { 5 | "start": "npm run watch", 6 | "watch": "webpack --colors --mode development --watch --stdin --config webpack.dev.js", 7 | "deploy": "webpack --config webpack.prod.js" 8 | }, 9 | "dependencies": { 10 | "bulma": "^0.7.1", 11 | "jquery": "^3.2.1", 12 | "phoenix": "file:../deps/phoenix", 13 | "phoenix_html": "file:../deps/phoenix_html", 14 | "simplemde": "^1.11.2" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.0.0-beta.54", 18 | "@babel/preset-env": "^7.0.0-beta.54", 19 | "autoprefixer": "^9.1.5", 20 | "babel-loader": "^8.0.0-beta", 21 | "babel-plugin-dynamic-import-webpack": "^1.0.2", 22 | "clean-css-loader": "^1.0.1", 23 | "copy-webpack-plugin": "^4.5.2", 24 | "css-loader": "^1.0.0", 25 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 26 | "file-loader": "^1.1.11", 27 | "node-sass": "^4.9.2", 28 | "postcss-loader": "^2.1.6", 29 | "sass-loader": "^7.0.3", 30 | "style-loader": "^0.21.0", 31 | "url-loader": "^1.0.1", 32 | "webpack": "^4.16.1", 33 | "webpack-cli": "^3.1.0", 34 | "webpack-dev-server": "^3.1.4", 35 | "webpack-merge": "^4.1.4" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /assets/postcss.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require('autoprefixer'); 2 | 3 | module.exports = { 4 | plugins: [ 5 | autoprefixer({ 6 | cascade: false 7 | }) 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /assets/scss/app.scss: -------------------------------------------------------------------------------- 1 | @import "./scss/bulmaswatch/_variables.scss"; 2 | @import "./node_modules/bulma/bulma.sass"; 3 | @import "./scss/bulmaswatch/_overrides.scss"; 4 | @import "forum.css"; 5 | 6 | @import "layout.scss"; 7 | -------------------------------------------------------------------------------- /assets/scss/bulmaswatch/_variables.scss: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////// 2 | // PULSE 3 | //////////////////////////////////////////////// 4 | $orange: #FF7518; 5 | $yellow: #f1c40f; 6 | $green: #2ecc71; 7 | $turquoise: #14a789; 8 | $blue: #3498db; 9 | $purple: #8e44ad; 10 | $red: #e74c3c; 11 | 12 | $primary: $purple !default; 13 | $warning: $orange; 14 | 15 | $orange-invert: #fff; 16 | $warning-invert: $orange-invert; 17 | 18 | $background: lighten($primary, 50); 19 | 20 | $family-sans-serif: 'Muli', "Helvetica Neue", Arial, sans-serif; 21 | $body-size: 14px; 22 | 23 | $subtitle-color: darken($primary, 10); 24 | 25 | $radius: 0; 26 | $radius-small: 0; 27 | $radius-large: 0; 28 | 29 | $link: $turquoise; 30 | $link-hover: lighten($link, 5); 31 | $link-focus: darken($link, 10); 32 | $link-active: darken($link, 10); 33 | 34 | $button-hover-color: lighten($primary, 10); 35 | $button-hover-border-color: lighten($primary, 10); 36 | 37 | $button-focus-color: darken($primary, 10); 38 | $button-focus-border-color: darken($primary, 10); 39 | $button-focus-box-shadow-size: 0 0 0 0.125em; 40 | $button-focus-box-shadow-color: rgba($primary, 0.25); 41 | 42 | $button-active-color: darken($primary, 10); 43 | $button-active-border-color: darken($primary, 10); 44 | 45 | $navbar-background-color: $primary; 46 | $navbar-item-color: #fff; 47 | $navbar-item-hover-color: $navbar-item-color; 48 | $navbar-item-active-color: $navbar-item-color; 49 | $navbar-item-hover-background-color: rgba(#000, 0.2); 50 | $navbar-item-active-background-color: rgba(#000, 0.2); 51 | $navbar-dropdown-item-active-color: $primary; 52 | $navbar-dropdown-arrow: $navbar-item-color; 53 | 54 | $menu-item-active-background-color: $primary; 55 | 56 | $bulmaswatch-import-font: true !default; 57 | -------------------------------------------------------------------------------- /assets/scss/forum.css: -------------------------------------------------------------------------------- 1 | html,body { 2 | font-family: 'Open Sans', serif; 3 | background: #F2F6FA; 4 | } 5 | footer { 6 | background-color: #F2F6FA !important; 7 | } 8 | .topNav { 9 | border-top: 5px solid #3498DB; 10 | } 11 | .topNav .container { 12 | border-bottom: 1px solid #E6EAEE; 13 | } 14 | .container .columns { 15 | margin: 3rem 0; 16 | } 17 | .navbar-menu .navbar-item { 18 | padding: 0 2rem; 19 | } 20 | aside.menu { 21 | padding-top: 3rem; 22 | } 23 | aside.menu .menu-list { 24 | line-height: 1.5; 25 | } 26 | aside.menu .menu-label { 27 | padding-left: 10px; 28 | font-weight: 700; 29 | } 30 | .button.is-primary.is-alt { 31 | background: #00c6ff; 32 | background: -webkit-linear-gradient(to bottom, #0072ff, #00c6ff); 33 | background: linear-gradient(to bottom, #0072ff, #00c6ff); 34 | font-weight: 700; 35 | font-size: 14px; 36 | height: 3rem; 37 | line-height: 2.8; 38 | } 39 | .media-left img { 40 | border-radius: 50%; 41 | } 42 | .media-content p { 43 | font-size: 14px; 44 | line-height: 2.3; 45 | font-weight: 700; 46 | color: #8F99A3; 47 | } 48 | article.post { 49 | margin: 1rem; 50 | padding-bottom: 1rem; 51 | border-bottom: 1px solid #E6EAEE; 52 | } 53 | article.post:last-child { 54 | padding-bottom: 0; 55 | border-bottom: none; 56 | } 57 | .menu-list li{ 58 | padding: 5px; 59 | } 60 | -------------------------------------------------------------------------------- /assets/scss/landing.scss: -------------------------------------------------------------------------------- 1 | .landing-page { 2 | 3 | .container { 4 | height: 100vh; 5 | 6 | .row { 7 | height: 100%; 8 | } 9 | } 10 | 11 | h2.constructing { 12 | size: 4em; 13 | } 14 | } 15 | 16 | .landing-page::before { 17 | content: ''; 18 | position: fixed; 19 | right: 0; 20 | left: 0; 21 | z-index: -1; 22 | 23 | display: block; 24 | width: 100%; 25 | height: 100%; 26 | background-image: url('/images/landing-bg.jpg'); 27 | filter: blur(5px); 28 | background-repeat: no-repeat; 29 | background-size: cover; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /assets/scss/layout.scss: -------------------------------------------------------------------------------- 1 | .topNav { 2 | border-top: none; 3 | } 4 | -------------------------------------------------------------------------------- /assets/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elixirtw/elixir_tw/aca41b3fb1b5102ccd9326b823cf944459332503/assets/static/favicon.ico -------------------------------------------------------------------------------- /assets/static/images/elixir_taiwan_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elixirtw/elixir_tw/aca41b3fb1b5102ccd9326b823cf944459332503/assets/static/images/elixir_taiwan_small.png -------------------------------------------------------------------------------- /assets/static/images/landing-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elixirtw/elixir_tw/aca41b3fb1b5102ccd9326b823cf944459332503/assets/static/images/landing-bg.jpg -------------------------------------------------------------------------------- /assets/static/images/phoenix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elixirtw/elixir_tw/aca41b3fb1b5102ccd9326b823cf944459332503/assets/static/images/phoenix.png -------------------------------------------------------------------------------- /assets/static/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /assets/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | const autoprefixer = require('autoprefixer'); 5 | const webpack = require('webpack'); 6 | 7 | module.exports = { 8 | context: __dirname, 9 | 10 | entry: { 11 | app: [ 12 | "./js/app.js", 13 | "./scss/app.scss" 14 | ] 15 | }, 16 | 17 | output: { 18 | path: path.resolve(__dirname, "../priv/static"), 19 | filename: 'js/[name].js', 20 | publicPath: 'http://localhost:4000/' 21 | }, 22 | 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.js$/, 27 | exclude: /node_modules/, 28 | loader: 'babel-loader', 29 | query: { 30 | presets: ['@babel/preset-env'], 31 | }, 32 | }, 33 | { 34 | test: /\.(scss|css)$/, 35 | use: ExtractTextPlugin.extract({ 36 | fallback: "sass-loader", 37 | use: ['css-loader', 'postcss-loader', 'sass-loader'] 38 | }) 39 | }, 40 | { 41 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 42 | loader: "file-loader", 43 | query: { 44 | name: "fonts/[hash].[ext]", 45 | mimetype: "application/font-woff" 46 | } 47 | }, 48 | { 49 | test: /\.(eot|svg|ttf)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 50 | loader: "file-loader", 51 | query: { 52 | name: "fonts/[hash].[ext]" 53 | } 54 | }, 55 | { 56 | test: /\.(svg|jpg|png|gif)$/, 57 | loader: 'file-loader', 58 | options: { 59 | name: '[name].[ext]?[hash]' 60 | } 61 | } 62 | ] 63 | }, 64 | 65 | plugins: [ 66 | new webpack.ProvidePlugin({ 67 | $: 'jquery', 68 | jQuery: 'jquery' 69 | }), 70 | 71 | new CopyWebpackPlugin([{ 72 | from: "./static", 73 | to: path.resolve(__dirname, "../priv/static") 74 | }]), 75 | 76 | new ExtractTextPlugin({ 77 | filename: "css/[name].css", 78 | allChunks: true 79 | }) 80 | ] 81 | 82 | }; 83 | 84 | -------------------------------------------------------------------------------- /assets/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | 4 | module.exports = merge(common, { 5 | mode: 'development', 6 | devtool: 'inline-source-map', 7 | devServer: { 8 | contentBase: './dist', 9 | 10 | headers: { 11 | "Access-control-Allow-Origin": "*" 12 | } 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /assets/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | 4 | module.exports = merge(common, { 5 | mode: 'production', 6 | }); 7 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | use Mix.Config 7 | 8 | # Configures the endpoint 9 | config :elixir_tw, ElixirTwWeb.Endpoint, 10 | url: [host: "localhost"], 11 | # root: Path.dirname(__DIR__), 12 | secret_key_base: "C9+8f85LKUz7MBf92Ot1F6Y94Q4PcCS88hllNo7JCUB1dYsiMD0MRs1qpGNI5p7L", 13 | render_errors: [view: ElixirTwWeb.ErrorView, accepts: ~w(html json)], 14 | pubsub: [name: ElixirTw.PubSub, adapter: Phoenix.PubSub.PG2] 15 | 16 | # Configures Elixir's Logger 17 | config :logger, :console, 18 | format: "$time $metadata[$level] $message\n", 19 | metadata: [:request_id] 20 | 21 | config :elixir_tw, ElixirTw.Auth.Guardian, 22 | # optional 23 | allowed_algos: ["ES512"], 24 | issuer: "ElixirTW", 25 | ttl: {30, :days}, 26 | verify_issuer: true 27 | 28 | # Import environment specific config. This must remain at the bottom 29 | # of this file so it overrides the configuration defined above. 30 | import_config "#{Mix.env()}.exs" 31 | 32 | # Configure phoenix generators 33 | config :phoenix, :generators, 34 | migration: true, 35 | binary_id: false 36 | 37 | # Ecto 2.0 config 38 | config :elixir_tw, ecto_repos: [ElixirTw.Repo] 39 | 40 | # Uberauth's Oauth Settings 41 | config :ueberauth, Ueberauth, 42 | providers: [ 43 | facebook: {Ueberauth.Strategy.Facebook, [profile_fields: "email,name"]}, 44 | github: {Ueberauth.Strategy.Github, [default_scope: "user:email"]} 45 | ] 46 | -------------------------------------------------------------------------------- /config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # For development, we disable any cache and enable 4 | # debugging and code reloading. 5 | # 6 | # The watchers configuration can be used to run external 7 | # watchers to your application. For example, we use it 8 | # with brunch.io to recompile .js and .css sources. 9 | config :elixir_tw, ElixirTwWeb.Endpoint, 10 | http: [port: 4000], 11 | debug_errors: true, 12 | code_reloader: true, 13 | check_origin: false, 14 | watchers: [ 15 | yarn: [ 16 | "run", 17 | "watch", 18 | cd: Path.expand("../assets", __DIR__) 19 | ] 20 | ] 21 | 22 | # Watch static and templates for browser reloading. 23 | config :elixir_tw, ElixirTwWeb.Endpoint, 24 | live_reload: [ 25 | patterns: [ 26 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$}, 27 | ~r{priv/gettext/.*(po)$}, 28 | ~r{lib/elixir_tw_web/views/.*(ex)$}, 29 | ~r{lib/elixir_tw_web/templates/.*(eex)$} 30 | ] 31 | ] 32 | 33 | # Do not include metadata nor timestamps in development logs 34 | config :logger, :console, format: "[$level] $message\n" 35 | 36 | # Set a higher stacktrace during development. 37 | # Do not configure such in production as keeping 38 | # and calculating stacktraces is usually expensive. 39 | config :phoenix, :stacktrace_depth, 20 40 | 41 | # Configure your database 42 | config :elixir_tw, ElixirTw.Repo, 43 | username: System.get_env("PGUSER") || "postgres", 44 | database: "elixir_tw_dev", 45 | hostname: "localhost", 46 | pool_size: 10 47 | 48 | # Configure Guardian 49 | config :elixir_tw, ElixirTw.Auth.Guardian, 50 | allowed_algos: ["ES512"], 51 | secret_key: %{ 52 | "crv" => "P-521", 53 | "d" => 54 | "CF9e9LqOnbsaW0sY06opq1gVg-5wefE8SJN30kx1lMmaz6-edFuNA0obU1KaZTKQBpXSLgjtoqMJHiKjwiQbCG4", 55 | "kty" => "EC", 56 | "x" => 57 | "AWXnRMCaj96pL33ZhTw5mW8vjcvYPRLbWLfIO21Aig5qBs7ymegVGZWAThWfZcBa13sgBXTBm6rv7RvKKTx8qZGW", 58 | "y" => 59 | "AFWQhP0skj9iODTS4zn8vGcAAouvJ5HkLoBl72TNlh9WM6p0Cpc4Cf1XwRYkMzi-vVLpCEq27M22vZu__8FEV9io" 60 | } 61 | 62 | import_config "dev.secret.exs" 63 | -------------------------------------------------------------------------------- /config/dev.secret.exs.example: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :ueberauth, Ueberauth.Strategy.Github.OAuth, 4 | client_id: "Some Github id", 5 | client_secret: "Some Github secret" 6 | 7 | config :ueberauth, Ueberauth.Strategy.Facebook.OAuth, 8 | client_id: "Some FB id", 9 | client_secret: "Some FB secret" 10 | -------------------------------------------------------------------------------- /config/prod.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # For production, we configure the host to read the PORT 4 | # from the system environment. Therefore, you will need 5 | # to set PORT=80 before running your server. 6 | # 7 | # You should also configure the url host to something 8 | # meaningful, we use this information when generating URLs. 9 | # 10 | # Finally, we also include the path to a manifest 11 | # containing the digested version of static files. This 12 | # manifest is generated by the mix phoenix.digest task 13 | # which you typically run after static files are built. 14 | config :elixir_tw, ElixirTwWeb.Endpoint, 15 | load_from_system_env: true, 16 | url: [host: "elixir.tw", port: {:system, 80}], 17 | # version: Mix.Project.config[:version], 18 | # server: true, 19 | # root: ".", 20 | # code_reloader: false, 21 | cache_static_manifest: "priv/static/cache_manifest.json" 22 | 23 | # Do not print debug messages in production 24 | config :logger, level: :info 25 | 26 | # ## SSL Support 27 | # 28 | # To get SSL working, you will need to add the `https` key 29 | # to the previous section and set your `:url` port to 443: 30 | # 31 | # config :elixir_tw, ElixirTw.Endpoint, 32 | # ... 33 | # url: [host: "example.com", port: 443], 34 | # https: [port: 443, 35 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 36 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")] 37 | # 38 | # Where those two env variables return an absolute path to 39 | # the key and cert in disk or a relative path inside priv, 40 | # for example "priv/ssl/server.key". 41 | # 42 | # We also recommend setting `force_ssl`, ensuring no data is 43 | # ever sent via http, always redirecting to https: 44 | # 45 | # config :elixir_tw, ElixirTw.Endpoint, 46 | # force_ssl: [hsts: true] 47 | # 48 | # Check `Plug.SSL` for all available options in `force_ssl`. 49 | 50 | # ## Using releases 51 | # 52 | # If you are doing OTP releases, you need to instruct Phoenix 53 | # to start the server for all endpoints: 54 | # 55 | config :phoenix, :serve_endpoints, true 56 | # 57 | # Alternatively, you can configure exactly which server to 58 | # start per endpoint: 59 | # 60 | # config :elixir_tw, ElixirTw.Endpoint, server: true 61 | # 62 | # You will also need to set the application root to `.` in order 63 | # for the new static assets to be served after a hot upgrade: 64 | # 65 | # config :elixir_tw, ElixirTw.Endpoint, root: "." 66 | 67 | config :shutdown_flag, 68 | flag_file: "/var/tmp/deploy/elixir_tw/shutdown.flag", 69 | check_delay: 10_000 70 | 71 | # Finally import the config/prod.secret.exs 72 | # which should be versioned separately. 73 | 74 | import_config "prod.secret.exs" 75 | -------------------------------------------------------------------------------- /config/prod.secret.exs.example: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # In this file, we keep production configuration that 4 | # you likely want to automate and keep it away from 5 | # your version control system. 6 | # Use `mix phoenix.gen.secret` to generate new secret key 7 | config :elixir_tw, ElixirTwWeb.Endpoint, 8 | secret_key_base: "secret-key" 9 | 10 | # Configure your database 11 | config :elixir_tw, ElixirTw.Repo, 12 | adapter: Ecto.Adapters.Postgres, 13 | username: "Hokey", 14 | password: "Pokey", 15 | database: "elixir_tw_prod", 16 | pool_size: 20 17 | 18 | # Guardian requires it's own set of JWK and JWT, please generate and keep in prod.secret.exs 19 | # jwk = JOSE.JWK.generate_key({:ec, "P-521"}) |> JOSE.JWK.to_map |> elem(1) 20 | config :guardian, Guardian, 21 | secret_key: %{"crv" => "P-521", 22 | "d" => "hmm", 23 | "kty" => "EC", 24 | "x" => "coolaid", 25 | "y" => "ahh~~~"} 26 | 27 | config :ueberauth, Ueberauth.Strategy.Github.OAuth, 28 | client_id: "this is not my id", 29 | client_secret: "and this is not my password" 30 | 31 | config :ueberauth, Ueberauth.Strategy.Facebook.OAuth, 32 | client_id: "this is not my id", 33 | client_secret: "and this is not my password" 34 | -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # We don't run a server during test. If one is required, 4 | # you can enable the server option below. 5 | config :elixir_tw, ElixirTwWeb.Endpoint, 6 | http: [port: 4000], 7 | server: false 8 | 9 | # Print only warnings and errors during test 10 | config :logger, level: :warn 11 | 12 | # Configure your database 13 | config :elixir_tw, ElixirTw.Repo, 14 | username: System.get_env("PGUSER") || "postgres", 15 | database: "elixir_tw_test", 16 | hostname: "localhost", 17 | pool: Ecto.Adapters.SQL.Sandbox 18 | 19 | config :elixir_tw, ElixirTw.Auth.Guardian, 20 | issuer: "ElixirTW", 21 | ttl: {30, :days}, 22 | verify_issuer: true, 23 | secret_key: %{ 24 | "crv" => "P-521", 25 | "d" => 26 | "CF9e9LqOnbsaW0sY06opq1gVg-5wefE8SJN30kx1lMmaz6-edFuNA0obU1KaZTKQBpXSLgjtoqMJHiKjwiQbCG4", 27 | "kty" => "EC", 28 | "x" => 29 | "AWXnRMCaj96pL33ZhTw5mW8vjcvYPRLbWLfIO21Aig5qBs7ymegVGZWAThWfZcBa13sgBXTBm6rv7RvKKTx8qZGW", 30 | "y" => 31 | "AFWQhP0skj9iODTS4zn8vGcAAouvJ5HkLoBl72TNlh9WM6p0Cpc4Cf1XwRYkMzi-vVLpCEq27M22vZu__8FEV9io" 32 | } 33 | 34 | import_config "test*.secret.exs" 35 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | services: 3 | db: 4 | image: postgres:9 5 | ports: 6 | - "5432:5432" 7 | -------------------------------------------------------------------------------- /lib/elixir_tw.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw do 2 | @moduledoc nil 3 | 4 | use Application 5 | 6 | # See http://elixir-lang.org/docs/stable/elixir/Application.html 7 | # for more information on OTP Applications 8 | def start(_type, _args) do 9 | import Supervisor.Spec, warn: false 10 | 11 | children = [ 12 | # Start the endpoint when the application starts 13 | supervisor(ElixirTwWeb.Endpoint, []), 14 | # Start the Ecto repository 15 | supervisor(ElixirTw.Repo, []) 16 | # Here you could define other workers and supervisors as children 17 | # worker(ElixirTw.Worker, [arg1, arg2, arg3]), 18 | ] 19 | 20 | # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html 21 | # for other strategies and supported options 22 | opts = [strategy: :one_for_one, name: ElixirTw.Supervisor] 23 | Supervisor.start_link(children, opts) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/elixir_tw/account/account.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Account do 2 | @moduledoc nil 3 | 4 | use ElixirTwWeb, :context 5 | 6 | alias ElixirTw.Account.User 7 | alias ElixirTw.Account.OAuthInfo 8 | 9 | @type oauth_result :: Ueberauth.Auth.t() 10 | 11 | @spec get_user(user_id :: integer) :: User.t() | nil 12 | def get_user(user_id), do: Repo.get(User, user_id) 13 | 14 | @spec get_user(provider :: atom | String.t(), uid :: String.t()) :: User.t() | nil 15 | def get_user(provider, uid) when is_atom(provider) do 16 | get_user(Atom.to_string(provider), uid) 17 | end 18 | 19 | def get_user(provider, uid) when is_integer(uid) do 20 | get_user(provider, Integer.to_string(uid)) 21 | end 22 | 23 | def get_user(provider, uid) do 24 | query = 25 | from u in User, 26 | join: o in OAuthInfo, 27 | on: u.id == o.user_id, 28 | where: o.uid == ^uid and o.provider == ^provider 29 | 30 | Repo.one(query) 31 | end 32 | 33 | @spec create_user_with_oauth(User.t() | nil, oauth_result) :: {:ok, User.t()} | {:error, any} 34 | def create_user_with_oauth(%User{} = user, _) do 35 | {:ok, user} 36 | end 37 | 38 | def create_user_with_oauth(nil, auth) do 39 | Repo.transaction(fn -> 40 | user = 41 | User.changeset(%User{}, %{ 42 | name: auth.info.name || auth.info.nickname, 43 | email: auth.info.email 44 | }) 45 | |> Repo.insert!() 46 | 47 | Ecto.build_assoc(user, :oauth_infos, %{ 48 | provider: Atom.to_string(auth.provider), 49 | uid: Integer.to_string(auth.uid) 50 | }) 51 | |> Repo.insert!() 52 | 53 | user 54 | end) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/elixir_tw/account/schemas/oauth_info.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Account.OAuthInfo do 2 | @moduledoc false 3 | 4 | use ElixirTwWeb, :schema 5 | 6 | schema "oauth_infos" do 7 | field :provider, :string 8 | field :uid, :string 9 | belongs_to :user, ElixirTw.Account.User 10 | 11 | timestamps() 12 | end 13 | 14 | @doc """ 15 | Builds a changeset based on the `struct` and `params`. 16 | """ 17 | def changeset(struct, params \\ %{}) do 18 | struct 19 | |> cast(params, [:uid, :provider]) 20 | |> validate_required([:provider, :uid]) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/elixir_tw/account/schemas/user.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Account.User do 2 | @moduledoc nil 3 | 4 | use ElixirTwWeb, :schema 5 | 6 | # alias Ueberauth.Auth 7 | alias ElixirTw.Account.OAuthInfo 8 | alias ElixirTw.Board.Post 9 | 10 | schema "users" do 11 | has_many :oauth_infos, OAuthInfo 12 | has_many :posts, Post 13 | field :name, :string 14 | field :email, :string 15 | field :crypted_password, :string 16 | 17 | timestamps() 18 | end 19 | 20 | @doc """ 21 | Creates a changeset based on the `struct` and `params`. 22 | 23 | If no params are provided, an invalid changeset is returned 24 | with no validation performed. 25 | """ 26 | def changeset(struct, params \\ %{}) do 27 | struct 28 | |> cast(params, [:name, :email]) 29 | |> validate_required([:name, :email]) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/elixir_tw/auth/guardian.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Auth.Guardian do 2 | @moduledoc nil 3 | use Guardian, otp_app: :elixir_tw 4 | 5 | alias ElixirTw.Account 6 | alias Account.User 7 | 8 | def subject_for_token(%User{id: user_id}, _claims) do 9 | {:ok, "User:#{user_id}"} 10 | end 11 | 12 | def subject_for_token(_, _), do: {:error, :unknown_resource} 13 | 14 | def resource_from_claims(%{"sub" => "User:" <> user_id}) do 15 | case String.to_integer(user_id) |> Account.get_user() do 16 | %User{} = user -> {:ok, user} 17 | _ -> {:error, :resource_not_found} 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/elixir_tw/auth/user_error_handler.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Auth.UserErrorHandler do 2 | use ElixirTwWeb, :controller 3 | 4 | def auth_error(conn, {_type, reason}, _opts) do 5 | conn 6 | |> put_flash(:error, reason) 7 | |> redirect(to: "/") 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/elixir_tw/auth/user_pipeline.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Auth.UserPipeline do 2 | use Guardian.Plug.Pipeline, 3 | otp_app: :elixir_tw, 4 | error_handler: ElixirTw.Auth.UserErrorHandler, 5 | module: ElixirTw.Auth.Guardian 6 | 7 | plug Guardian.Plug.VerifySession 8 | plug Guardian.Plug.LoadResource, allow_blank: true 9 | end 10 | -------------------------------------------------------------------------------- /lib/elixir_tw/board/board.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Board do 2 | @moduledoc """ 3 | Interface for Board context. 4 | """ 5 | 6 | use ElixirTwWeb, :context 7 | 8 | alias ElixirTw.Board.Post 9 | 10 | def get_posts do 11 | Post 12 | |> order_by([p], asc: p.pinned, desc: p.inserted_at) 13 | |> preload([:user, :comments]) 14 | |> Repo.all() 15 | |> Enum.map(&update_comments_count/1) 16 | end 17 | 18 | def get_post(slug, opts \\ []) 19 | 20 | def get_post(slug, opts) do 21 | preload = Keyword.get(opts, :preload) || [] 22 | 23 | Post 24 | |> Repo.get_by(slug: slug) 25 | |> Repo.preload(preload) 26 | end 27 | 28 | def get_post_by_user(user, slug) do 29 | Repo.get_by(Post, slug: slug, user_id: user.id) 30 | end 31 | 32 | def post_changeset, do: post_changeset(%{}) 33 | def post_changeset(struct \\ %Post{}, post_params) 34 | 35 | def post_changeset(struct, post_params) do 36 | Post.changeset(struct, post_params) 37 | end 38 | 39 | def create_post(user, post_params) do 40 | %Post{user: user} 41 | |> post_changeset(post_params) 42 | |> Repo.insert() 43 | end 44 | 45 | def update_post(user, slug, post_params) do 46 | get_post_by_user(user, slug) 47 | |> post_changeset(post_params) 48 | |> Repo.update() 49 | end 50 | 51 | defp update_comments_count(post), do: Map.put(post, :comments_count, Enum.count(post.comments)) 52 | end 53 | -------------------------------------------------------------------------------- /lib/elixir_tw/board/comment.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Board.Comment do 2 | @moduledoc nil 3 | 4 | use ElixirTwWeb, :schema 5 | 6 | alias ElixirTw.Board 7 | alias ElixirTw.Account 8 | 9 | schema "comments" do 10 | field :body, :string 11 | field :position, :integer 12 | belongs_to :post, Board.Post 13 | belongs_to :user, Account.User 14 | 15 | timestamps() 16 | end 17 | 18 | @doc """ 19 | Builds a changeset based on the `struct` and `params`. 20 | """ 21 | def changeset(struct, params \\ %{}) do 22 | struct 23 | |> cast(params, [:body, :position]) 24 | |> validate_required([:body, :position]) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/elixir_tw/board/post.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Board.Post do 2 | @moduledoc false 3 | 4 | use ElixirTwWeb, :schema 5 | use PipeTo.Override 6 | 7 | @derive {Phoenix.Param, key: :slug} 8 | 9 | schema "posts" do 10 | field :title, :string 11 | field :slug, :string 12 | field :body, :string 13 | field :markdown_body, :string 14 | belongs_to :user, ElixirTw.Account.User 15 | has_many :comments, ElixirTw.Board.Comment 16 | 17 | timestamps() 18 | end 19 | 20 | @doc """ 21 | Builds a changeset based on the `struct` and `params`. 22 | """ 23 | def changeset(struct, params \\ %{}) do 24 | struct 25 | |> cast(params, [:title, :markdown_body]) 26 | |> build_slug 27 | |> translate_markdown 28 | |> validate_required([:title, :markdown_body, :body]) 29 | |> assoc_constraint(:user) 30 | |> unique_constraint(:slug) 31 | end 32 | 33 | defp translate_markdown(%{changes: %{markdown_body: md_body}} = changeset) do 34 | case Earmark.as_html(md_body) do 35 | {:ok, html_body, []} -> 36 | html_body 37 | |> HtmlSanitizeEx.basic_html() 38 | |> put_change(changeset, :body, _) 39 | 40 | {:error, _, _} -> 41 | add_error(changeset, :body, "markdown 轉換失敗") 42 | end 43 | end 44 | 45 | defp translate_markdown(changeset), do: changeset 46 | 47 | defp build_slug(%{changes: %{title: title}} = changeset), 48 | do: put_change(changeset, :slug, title_to_slug(title)) 49 | 50 | defp build_slug(changeset), do: changeset 51 | 52 | defp title_to_slug(title), do: "#{slugify_time()}-#{slugify_title(title)}" 53 | 54 | defp slugify_time, do: DateTime.utc_now() |> DateTime.to_unix() |> to_string 55 | 56 | defp slugify_title(nil), do: nil 57 | 58 | defp slugify_title(title) do 59 | title |> Phoenix.Naming.humanize() |> String.downcase() |> String.replace(" ", "-") 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/elixir_tw/release_tasks.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.ReleaseTasks do 2 | @start_apps [ 3 | :crypto, 4 | :ssl, 5 | :postgrex, 6 | :ecto 7 | ] 8 | 9 | def myapp, do: Application.get_application(__MODULE__) 10 | 11 | def repos, do: Application.get_env(myapp(), :ecto_repos, []) 12 | 13 | def seed do 14 | me = myapp() 15 | 16 | IO.puts("Loading #{me}..") 17 | # Load the code for myapp, but don't start it 18 | :ok = Application.load(me) 19 | 20 | IO.puts("Starting dependencies..") 21 | # Start apps necessary for executing migrations 22 | Enum.each(@start_apps, &Application.ensure_all_started/1) 23 | 24 | # Start the Repo(s) for myapp 25 | IO.puts("Starting repos..") 26 | Enum.each(repos(), & &1.start_link(pool_size: 1)) 27 | 28 | # Run migrations 29 | migrate() 30 | 31 | # Run seed script 32 | # Enum.each(repos(), &run_seeds_for/1) 33 | 34 | # Signal shutdown 35 | IO.puts("Success!") 36 | :init.stop() 37 | end 38 | 39 | def migrate, do: Enum.each(repos(), &run_migrations_for/1) 40 | 41 | def priv_dir(app), do: "#{:code.priv_dir(app)}" 42 | 43 | defp run_migrations_for(repo) do 44 | app = Keyword.get(repo.config, :otp_app) 45 | IO.puts("Running migrations for #{app}") 46 | Ecto.Migrator.run(repo, migrations_path(repo), :up, all: true) 47 | end 48 | 49 | def run_seeds_for(repo) do 50 | # Run the seed script if it exists 51 | seed_script = seeds_path(repo) 52 | 53 | if File.exists?(seed_script) do 54 | IO.puts("Running seed script..") 55 | Code.eval_file(seed_script) 56 | end 57 | end 58 | 59 | def migrations_path(repo), do: priv_path_for(repo, "migrations") 60 | 61 | def seeds_path(repo), do: priv_path_for(repo, "seeds.exs") 62 | 63 | def priv_path_for(repo, filename) do 64 | app = Keyword.get(repo.config, :otp_app) 65 | repo_underscore = repo |> Module.split() |> List.last() |> Macro.underscore() 66 | Path.join([priv_dir(app), repo_underscore, filename]) 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/elixir_tw/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Repo do 2 | use Ecto.Repo, 3 | otp_app: :elixir_tw, 4 | adapter: Ecto.Adapters.Postgres 5 | end 6 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/channels/user_socket.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.UserSocket do 2 | use Phoenix.Socket 3 | 4 | ## Channels 5 | # channel "room:*", ElixirTwWeb.RoomChannel 6 | 7 | # Socket params are passed from the client and can 8 | # be used to verify and authenticate a user. After 9 | # verification, you can put default assigns into 10 | # the socket that will be set for all channels, ie 11 | # 12 | # {:ok, assign(socket, :user_id, verified_user_id)} 13 | # 14 | # To deny connection, return `:error`. 15 | # 16 | # See `Phoenix.Token` documentation for examples in 17 | # performing token verification on connect. 18 | def connect(_params, socket, _connect_info) do 19 | {:ok, socket} 20 | end 21 | 22 | # Socket id's are topics that allow you to identify all sockets for a given user: 23 | # 24 | # def id(socket), do: "user_socket:#{socket.assigns.user_id}" 25 | # 26 | # Would allow you to broadcast a "disconnect" event and terminate 27 | # all active sockets and channels for a given user: 28 | # 29 | # ElixirTwWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) 30 | # 31 | # Returning `nil` makes this socket anonymous. 32 | def id(_socket), do: nil 33 | end 34 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/controllers/page_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.PageController do 2 | use ElixirTwWeb, :controller 3 | 4 | def landing(conn, _params) do 5 | conn 6 | |> put_layout(false) 7 | |> render("landing.html") 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/controllers/post_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.PostController do 2 | use ElixirTwWeb, :controller 3 | 4 | alias ElixirTw.Board 5 | 6 | def index(conn, _params) do 7 | posts = Board.get_posts() 8 | render(conn, "index.html", posts: posts) 9 | end 10 | 11 | def show(conn, %{"slug" => slug}) do 12 | post = Board.get_post(slug, preload: :user) 13 | render(conn, "show.html", post: post) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/controllers/session_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.SessionController do 2 | use ElixirTwWeb, :controller 3 | 4 | plug Ueberauth 5 | plug :scrub_params, "user" when action in [:create] 6 | 7 | alias ElixirTw.{Account, Auth} 8 | 9 | def new(conn, params) do 10 | origin_url = Map.get(params, "origin_url", "/") 11 | 12 | if current_resource(conn) do 13 | redirect(conn, to: origin_url) 14 | else 15 | conn 16 | |> put_session(:origin_url, origin_url) 17 | |> render("new.html") 18 | end 19 | end 20 | 21 | def delete(conn, _params) do 22 | Auth.Guardian.Plug.sign_out(conn) 23 | |> put_flash(:info, "Logged out successfully") 24 | |> redirect(to: "/") 25 | end 26 | 27 | def request(conn, params) do 28 | render(conn, "request.html", 29 | callback_url: "/auth/#{conn.params["provider"]}/callback?origin_url=#{params[:origin_url]}" 30 | ) 31 | end 32 | 33 | def callback(%{assigns: %{ueberauth_failure: _fails}} = conn, _params) do 34 | conn 35 | |> put_flash(:error, "驗證失敗!") 36 | |> redirect(to: "/") 37 | end 38 | 39 | def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do 40 | Logger.info("ueberauth callback auth: #{inspect(auth)}") 41 | 42 | with user <- Account.get_user(auth.provider, auth.uid), 43 | {:ok, user} <- Account.create_user_with_oauth(user, auth), 44 | %{state: :unset} = conn <- Auth.Guardian.Plug.sign_in(conn, user) do 45 | conn 46 | |> put_flash(:info, "驗證成功!") 47 | |> put_session(:current_user, user) 48 | |> redirect(to: get_session(conn, :origin_url)) 49 | else 50 | {:error, reason} -> 51 | conn 52 | |> put_flash(:error, reason) 53 | |> redirect(to: "/") 54 | 55 | conn -> 56 | conn 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/controllers/user/config_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.User.ConfigController do 2 | use ElixirTwWeb, :controller 3 | 4 | def dashboard(conn, _params, user, _claim) do 5 | render(conn, "dashboard.html", user: user) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/controllers/user/post_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.User.PostController do 2 | use ElixirTwWeb, :controller 3 | 4 | alias ElixirTw.Board 5 | 6 | def new(conn, _params, _user, _claim) do 7 | changeset = Board.post_changeset() 8 | render(conn, "new.html", changeset: changeset) 9 | end 10 | 11 | def create(conn, %{"post" => post_params}, user, _claim) do 12 | case Board.create_post(user, post_params) do 13 | {:ok, post} -> 14 | conn 15 | |> put_flash(:info, "文章成功建立") 16 | |> redirect(to: Routes.post_path(conn, :show, post.slug)) 17 | 18 | {:error, changeset} -> 19 | conn 20 | |> put_flash(:error, "文章有錯誤") 21 | |> render("new.html", changeset: changeset) 22 | end 23 | end 24 | 25 | def edit(conn, %{"slug" => slug}, user, _claim) do 26 | case Board.get_post_by_user(user, slug) do 27 | nil -> 28 | conn 29 | |> put_flash(:error, "不能修改文章") 30 | |> redirect(to: Routes.post_path(conn, :show, slug)) 31 | 32 | post -> 33 | changeset = Board.post_changeset(post) 34 | render(conn, "edit.html", changeset: changeset) 35 | end 36 | end 37 | 38 | def update(conn, %{"post" => post_params}, user, _claim) do 39 | case Board.update_post(user, post_params["id"], post_params) do 40 | {:ok, post} -> 41 | conn 42 | |> put_flash(:info, "文章成功更新") 43 | |> redirect(to: Routes.post_path(conn, :show, post.slug)) 44 | 45 | {:error, changeset} -> 46 | conn 47 | |> put_flash(:error, "文章有錯誤") 48 | |> render("new.html", changeset: changeset) 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :elixir_tw 3 | 4 | socket "/socket", ElixirTwWeb.UserSocket, 5 | websocket: true, 6 | longpoll: false 7 | 8 | # Serve at "/" the static files from "priv/static" directory. 9 | # 10 | # You should set gzip to true if you are running phoenix.digest 11 | # when deploying your static files in production. 12 | plug Plug.Static, 13 | at: "/", 14 | from: :elixir_tw, 15 | gzip: false, 16 | only: ~w(css fonts images js favicon.ico robots.txt) 17 | 18 | # Code reloading can be explicitly enabled under the 19 | # :code_reloader configuration of your endpoint. 20 | if code_reloading? do 21 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket 22 | plug Phoenix.LiveReloader 23 | plug Phoenix.CodeReloader 24 | end 25 | 26 | plug Plug.RequestId 27 | plug Plug.Logger 28 | 29 | plug Plug.Parsers, 30 | parsers: [:urlencoded, :multipart, :json], 31 | pass: ["*/*"], 32 | json_decoder: Poison 33 | 34 | plug Plug.MethodOverride 35 | plug Plug.Head 36 | 37 | plug Plug.Session, 38 | store: :cookie, 39 | key: "_elixir_tw_key", 40 | signing_salt: "FlC//Xzo" 41 | 42 | plug ElixirTwWeb.Router 43 | 44 | @doc """ 45 | Callback invoked for dynamically configuring the endpoint. 46 | 47 | It receives the endpoint configuration and checks if 48 | configuration should be loaded from the system environment. 49 | """ 50 | def init(_key, config) do 51 | if config[:load_from_system_env] do 52 | port = System.get_env("PORT") || raise "expected the PORT environment variable to be set" 53 | {:ok, Keyword.put(config, :http, [:inet6, port: port])} 54 | else 55 | {:ok, config} 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/gettext.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.Gettext do 2 | @moduledoc """ 3 | A module providing Internationalization with a gettext-based API. 4 | 5 | By using [Gettext](http://hexdocs.pm/gettext), 6 | your module gains a set of macros for translations, for example: 7 | 8 | import ElixirTw.Gettext 9 | 10 | # Simple translation 11 | gettext "Here is the string to translate" 12 | 13 | # Plural translation 14 | ngettext "Here is the string to translate", 15 | "Here are the strings to translate", 16 | 3 17 | 18 | # Domain-based translation 19 | dgettext "errors", "Here is the error message to translate" 20 | 21 | See the [Gettext Docs](http://hexdocs.pm/gettext) for detailed usage. 22 | """ 23 | use Gettext, otp_app: :elixir_tw 24 | end 25 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/router.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.Router do 2 | use ElixirTwWeb, :router 3 | 4 | pipeline :browser do 5 | plug :accepts, ["html"] 6 | plug :fetch_session 7 | plug :fetch_flash 8 | plug :protect_from_forgery 9 | plug :put_secure_browser_headers 10 | 11 | plug Auth.UserPipeline 12 | end 13 | 14 | pipeline :authed do 15 | plug Guardian.Plug.EnsureAuthenticated 16 | end 17 | 18 | scope "/", ElixirTwWeb do 19 | pipe_through [:browser] 20 | 21 | # get "/", PostController, :index 22 | get "/", PageController, :landing 23 | 24 | get "/login", SessionController, :new 25 | delete "/logout", SessionController, :delete 26 | 27 | resources "/posts", PostController, param: "slug", only: [:index, :show] 28 | end 29 | 30 | scope "/auth", ElixirTwWeb do 31 | pipe_through [:browser] 32 | 33 | get "/:provider", SessionController, :request 34 | get "/:provider/callback", SessionController, :callback 35 | post "/:provider/callback", SessionController, :callback 36 | end 37 | 38 | scope "/user", ElixirTwWeb.User, as: :user do 39 | pipe_through [:browser, :authed] 40 | 41 | get "/", ConfigController, :dashboard 42 | resources "/posts", PostController, only: [:new, :create, :edit, :update] 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/templates/layout/_head.html.eex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Elixir |> Taiwan 8 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/templates/layout/_main_nav.html.eex: -------------------------------------------------------------------------------- 1 | 54 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/templates/layout/_side_panel.html.eex: -------------------------------------------------------------------------------- 1 |
2 | New Post 3 | 17 |
18 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/templates/layout/_user_sidebar.html.eex: -------------------------------------------------------------------------------- 1 |
2 |
3 | 控制板 4 |
5 |
6 | <%= link "新增文章", to: Routes.user_post_path(@conn, :new) %> 7 |
8 |
9 | 個人首頁 10 |
11 |
12 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/templates/layout/app.html.eex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= render "_head.html", conn: @conn %> 5 | "> 6 | 7 | 8 | 9 | <%= render "_main_nav.html", conn: @conn %> 10 | 11 |
12 |
13 |
14 | <%= show_flash(@conn) %> 15 | <%= render @view_module, @view_template, assigns %> 16 |
17 | 18 | <%= render "_side_panel.html", conn: @conn %> 19 |
20 |
21 | 22 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/templates/page/landing.html.eex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= render ElixirTwWeb.LayoutView, "_head.html", conn: @conn %> 5 | 6 | 7 | 8 |
9 |
10 |
11 |
12 |

Elixir Taiwan

13 |

14 | 15 | 建構中! 16 |

17 | 18 |
19 | 20 |

21 |

學習資源

22 | 29 |

30 | 31 |

32 | 33 | 34 | 35 |

36 |
37 |
38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/templates/post/_post_item.html.eex: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/templates/post/index.html.eex: -------------------------------------------------------------------------------- 1 |
2 | <%= render_many(@posts, __MODULE__, "_post_item.html", conn: @conn)%> 3 |
4 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/templates/post/show.html.eex: -------------------------------------------------------------------------------- 1 |
2 |
3 | > 4 |
5 |

<%= @post.title %>

6 |
<%= @post.user.name %>
7 |
8 |
9 |

10 | <%= raw(@post.body) %> 11 |

12 |
13 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/templates/session/new.html.eex: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 14 | <%#
或是
%> 15 | <%#
%> 16 | <%#
%> 17 | <%#
%> 18 | <%#
%> 19 | <%#%> 20 | <%#%> 21 | <%#
%> 22 | <%#
%> 23 | <%#
%> 24 | <%#
%> 25 | <%#%> 26 | <%#%> 27 | <%#
%> 28 | <%#
%> 29 | <%#
登 入
%> 30 | 31 | <%#
%> 32 | 33 | <%#
%> 34 | 35 | <%#
%> 36 | <%#還沒有帳號嗎? 註 冊%> 37 | <%#
%> 38 | <%#
%> 39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/templates/session/request.html.eex: -------------------------------------------------------------------------------- 1 | <%= form_tag @callback_url, method: "post" do %> 2 |
3 | 4 | 5 |
6 | 7 |
8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 |
21 | 22 |
23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 |
31 | 32 |
33 | 34 | 35 |
36 | 37 |
38 | 39 | 40 |
41 | 42 |
43 | 44 | 45 |
46 | 47 |
48 | 49 |
50 | <% end %> 51 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/templates/user/config/dashboard.html.eex: -------------------------------------------------------------------------------- 1 |

This is the user Dashboard

2 |

This page should contain: I don't know.... maybe not so important yet....

3 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/templates/user/post/new.html.eex: -------------------------------------------------------------------------------- 1 |
2 | <%= form_for @changeset, "/user/posts", [class: "ui large form"], fn f -> %> 3 |

新增文章

4 | 5 |
6 | 7 |
8 | <%= text_input f, :title, placeholder: "文章主題" %> 9 |
10 | 11 |
12 | 17 |
18 | 19 |
20 | <%= textarea f, :markdown_body, placeholder: "內容" %> 21 |
22 | 23 |
24 | <%= submit "儲存", class: "ui green button" %> 25 |
26 | <% end %> 27 |
28 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/views/error_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.ErrorHelpers do 2 | @moduledoc """ 3 | Conveniences for translating and building error messages. 4 | """ 5 | 6 | use Phoenix.HTML 7 | 8 | @doc """ 9 | Generates tag for inlined form input errors. 10 | """ 11 | def error_tag(form, field) do 12 | if error = form.errors[field] do 13 | content_tag(:span, translate_error(error), class: "help-block") 14 | end 15 | end 16 | 17 | def translate_error({msg, opts}) do 18 | # Because error messages were defined within Ecto, we must 19 | # call the Gettext module passing our Gettext backend. We 20 | # also use the "errors" domain as translations are placed 21 | # in the errors.po file. 22 | # Ecto will pass the :count keyword if the error message is 23 | # meant to be pluralized. 24 | # On your own code and templates, depending on whether you 25 | # need the message to be pluralized or not, this could be 26 | # written simply as: 27 | # 28 | # dngettext "errors", "1 file", "%{count} files", count 29 | # dgettext "errors", "is invalid" 30 | # 31 | if count = opts[:count] do 32 | Gettext.dngettext(ElixirTw.Gettext, "errors", msg, msg, count, opts) 33 | else 34 | Gettext.dgettext(ElixirTw.Gettext, "errors", msg, opts) 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/views/error_view.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.ErrorView do 2 | use ElixirTwWeb, :view 3 | 4 | def render("404.html", _assigns) do 5 | "Page not found" 6 | end 7 | 8 | def render("500.html", _assigns) do 9 | "Server internal error" 10 | end 11 | 12 | # In case no render clause matches or no 13 | # template is found, let's render it as 500 14 | def template_not_found(_template, assigns) do 15 | render("500.html", assigns) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/views/layout_view.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.LayoutView do 2 | use ElixirTwWeb, :view 3 | 4 | @dialyzer :no_match 5 | 6 | def show_flash(conn) do 7 | conn 8 | |> get_flash 9 | |> Enum.map(&flash_html/1) 10 | end 11 | 12 | def flash_html({level, message}), 13 | do: {:safe, "
#{message}
"} 14 | 15 | def flash_html(_), do: nil 16 | 17 | def avatar_url(conn, email), 18 | do: 19 | Gravity.image(email, 20 | d: "#{ElixirTwWeb.Router.Helpers.url(conn)}/images/elixir_taiwan_small.png" 21 | ) 22 | end 23 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/views/page_view.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.PageView do 2 | use ElixirTwWeb, :view 3 | end 4 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/views/post_view.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.PostView do 2 | use ElixirTwWeb, :view 3 | end 4 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/views/session_view.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.SessionView do 2 | use ElixirTwWeb, :view 3 | end 4 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/views/user/config_view.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.User.ConfigView do 2 | use ElixirTwWeb, :view 3 | end 4 | -------------------------------------------------------------------------------- /lib/elixir_tw_web/views/user/post_view.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.User.PostView do 2 | use ElixirTwWeb, :view 3 | 4 | # Inline Script 5 | def render("script.html", _assigns) do 6 | ~E""" 7 | 11 | """ 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :elixir_tw, 7 | version: "0.0.2", 8 | elixir: "~> 1.0", 9 | elixirc_paths: elixirc_paths(Mix.env()), 10 | compilers: [:phoenix, :gettext] ++ Mix.compilers(), 11 | # build_embedded: Mix.env == :prod, 12 | start_permanent: Mix.env() == :prod, 13 | aliases: aliases(), 14 | dialyzer: [plt_add_deps: :transitive], 15 | deploy_dir: "/opt/elixirtw/elixir_tw/", 16 | deps: deps() 17 | ] 18 | end 19 | 20 | # Configuration for the OTP application. 21 | # 22 | # Type `mix help compile.app` for more information. 23 | def application do 24 | [mod: {ElixirTw, []}, extra_applications: [:logger, :runtime_tools]] 25 | end 26 | 27 | # Specifies which paths to compile per environment. 28 | defp elixirc_paths(:test), do: ["lib", "test/support"] 29 | defp elixirc_paths(_), do: ["lib"] 30 | 31 | # Specifies your project dependencies. 32 | # 33 | # Type `mix help deps` for examples and options. 34 | defp deps do 35 | [ 36 | {:phoenix, "~> 1.4.0"}, 37 | {:phoenix_pubsub, "~> 1.1"}, 38 | {:phoenix_ecto, "~> 4.0"}, 39 | {:ecto_sql, "~> 3.0"}, 40 | {:postgrex, ">= 0.0.0"}, 41 | {:phoenix_html, "~> 2.11"}, 42 | {:phoenix_live_reload, "~> 1.2", only: :dev}, 43 | {:jason, "~> 1.0"}, 44 | {:gettext, "~> 0.11"}, 45 | {:plug_cowboy, "~> 2.0"}, 46 | {:guardian, "~> 1.1"}, 47 | {:ueberauth_facebook, "~> 0.6"}, 48 | {:ueberauth_github, "~> 0.4"}, 49 | {:distillery, "~> 2.0", runtime: false}, 50 | {:gravity, "~> 1.0"}, 51 | {:earmark, "~> 1.1"}, 52 | {:pipe_to, "~> 0.1"}, 53 | {:html_sanitize_ex, "~> 1.1"}, 54 | {:conform, "~> 2.2"}, 55 | {:ex_machina, "~> 2.0", only: [:dev, :test]}, 56 | {:faker, "~> 0.9", only: [:dev, :test]}, 57 | {:dialyxir, "~> 0.5.0", only: [:dev, :test], runtime: false}, 58 | {:mix_test_watch, "~> 0.3", only: :dev, runtime: false} 59 | ] 60 | end 61 | 62 | # Aliases are shortcut or tasks specific to the current project. 63 | # For example, to create, migrate and run the seeds file at once: 64 | # 65 | # $ mix ecto.setup 66 | # 67 | # See the documentation for `Mix` for more info on aliases. 68 | defp aliases do 69 | [ 70 | "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], 71 | "ecto.reset": ["ecto.drop", "ecto.setup"], 72 | test: ["ecto.create --quiet", "ecto.migrate", "test"] 73 | ] 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /priv/gettext/en/LC_MESSAGES/errors.po: -------------------------------------------------------------------------------- 1 | ## `msgid`s in this file come from POT (.pot) files. Do not add, change, or 2 | ## remove `msgid`s manually here as they're tied to the ones in the 3 | ## corresponding POT file (with the same domain). Use `mix gettext.extract 4 | ## --merge` or `mix gettext.merge` to merge POT files into PO files. 5 | msgid "" 6 | msgstr "" 7 | "Language: en\n" 8 | 9 | ## From Ecto.Changeset.cast/4 10 | msgid "can't be blank" 11 | msgstr "" 12 | 13 | ## From Ecto.Changeset.unique_constraint/3 14 | msgid "has already been taken" 15 | msgstr "" 16 | 17 | ## From Ecto.Changeset.put_change/3 18 | msgid "is invalid" 19 | msgstr "" 20 | 21 | ## From Ecto.Changeset.validate_format/3 22 | msgid "has invalid format" 23 | msgstr "" 24 | 25 | ## From Ecto.Changeset.validate_subset/3 26 | msgid "has an invalid entry" 27 | msgstr "" 28 | 29 | ## From Ecto.Changeset.validate_exclusion/3 30 | msgid "is reserved" 31 | msgstr "" 32 | 33 | ## From Ecto.Changeset.validate_confirmation/3 34 | msgid "does not match confirmation" 35 | msgstr "" 36 | 37 | ## From Ecto.Changeset.no_assoc_constraint/3 38 | msgid "is still associated to this entry" 39 | msgstr "" 40 | 41 | msgid "are still associated to this entry" 42 | msgstr "" 43 | 44 | ## From Ecto.Changeset.validate_length/3 45 | msgid "should be %{count} character(s)" 46 | msgid_plural "should be %{count} character(s)" 47 | msgstr[0] "" 48 | msgstr[1] "" 49 | 50 | msgid "should have %{count} item(s)" 51 | msgid_plural "should have %{count} item(s)" 52 | msgstr[0] "" 53 | msgstr[1] "" 54 | 55 | msgid "should be at least %{count} character(s)" 56 | msgid_plural "should be at least %{count} character(s)" 57 | msgstr[0] "" 58 | msgstr[1] "" 59 | 60 | msgid "should have at least %{count} item(s)" 61 | msgid_plural "should have at least %{count} item(s)" 62 | msgstr[0] "" 63 | msgstr[1] "" 64 | 65 | msgid "should be at most %{count} character(s)" 66 | msgid_plural "should be at most %{count} character(s)" 67 | msgstr[0] "" 68 | msgstr[1] "" 69 | 70 | msgid "should have at most %{count} item(s)" 71 | msgid_plural "should have at most %{count} item(s)" 72 | msgstr[0] "" 73 | msgstr[1] "" 74 | 75 | ## From Ecto.Changeset.validate_number/3 76 | msgid "must be less than %{count}" 77 | msgid_plural "must be less than %{count}" 78 | msgstr[0] "" 79 | msgstr[1] "" 80 | 81 | msgid "must be greater than %{count}" 82 | msgid_plural "must be greater than %{count}" 83 | msgstr[0] "" 84 | msgstr[1] "" 85 | 86 | msgid "must be less than or equal to %{count}" 87 | msgid_plural "must be less than or equal to %{count}" 88 | msgstr[0] "" 89 | msgstr[1] "" 90 | 91 | msgid "must be greater than or equal to %{count}" 92 | msgid_plural "must be greater than or equal to %{count}" 93 | msgstr[0] "" 94 | msgstr[1] "" 95 | 96 | msgid "must be equal to %{count}" 97 | msgid_plural "must be equal to %{count}" 98 | msgstr[0] "" 99 | msgstr[1] "" 100 | -------------------------------------------------------------------------------- /priv/gettext/errors.pot: -------------------------------------------------------------------------------- 1 | ## This file is a PO Template file. `msgid`s here are often extracted from 2 | ## source code; add new translations manually only if they're dynamic 3 | ## translations that can't be statically extracted. Run `mix 4 | ## gettext.extract` to bring this file up to date. Leave `msgstr`s empty as 5 | ## changing them here as no effect; edit them in PO (`.po`) files instead. 6 | 7 | ## From Ecto.Changeset.cast/4 8 | msgid "can't be blank" 9 | msgstr "" 10 | 11 | ## From Ecto.Changeset.unique_constraint/3 12 | msgid "has already been taken" 13 | msgstr "" 14 | 15 | ## From Ecto.Changeset.put_change/3 16 | msgid "is invalid" 17 | msgstr "" 18 | 19 | ## From Ecto.Changeset.validate_format/3 20 | msgid "has invalid format" 21 | msgstr "" 22 | 23 | ## From Ecto.Changeset.validate_subset/3 24 | msgid "has an invalid entry" 25 | msgstr "" 26 | 27 | ## From Ecto.Changeset.validate_exclusion/3 28 | msgid "is reserved" 29 | msgstr "" 30 | 31 | ## From Ecto.Changeset.validate_confirmation/3 32 | msgid "does not match confirmation" 33 | msgstr "" 34 | 35 | ## From Ecto.Changeset.no_assoc_constraint/3 36 | msgid "is still associated to this entry" 37 | msgstr "" 38 | 39 | msgid "are still associated to this entry" 40 | msgstr "" 41 | 42 | ## From Ecto.Changeset.validate_length/3 43 | msgid "should be %{count} character(s)" 44 | msgid_plural "should be %{count} character(s)" 45 | msgstr[0] "" 46 | msgstr[1] "" 47 | 48 | msgid "should have %{count} item(s)" 49 | msgid_plural "should have %{count} item(s)" 50 | msgstr[0] "" 51 | msgstr[1] "" 52 | 53 | msgid "should be at least %{count} character(s)" 54 | msgid_plural "should be at least %{count} character(s)" 55 | msgstr[0] "" 56 | msgstr[1] "" 57 | 58 | msgid "should have at least %{count} item(s)" 59 | msgid_plural "should have at least %{count} item(s)" 60 | msgstr[0] "" 61 | msgstr[1] "" 62 | 63 | msgid "should be at most %{count} character(s)" 64 | msgid_plural "should be at most %{count} character(s)" 65 | msgstr[0] "" 66 | msgstr[1] "" 67 | 68 | msgid "should have at most %{count} item(s)" 69 | msgid_plural "should have at most %{count} item(s)" 70 | msgstr[0] "" 71 | msgstr[1] "" 72 | 73 | ## From Ecto.Changeset.validate_number/3 74 | msgid "must be less than %{count}" 75 | msgid_plural "must be less than %{count}" 76 | msgstr[0] "" 77 | msgstr[1] "" 78 | 79 | msgid "must be greater than %{count}" 80 | msgid_plural "must be greater than %{count}" 81 | msgstr[0] "" 82 | msgstr[1] "" 83 | 84 | msgid "must be less than or equal to %{count}" 85 | msgid_plural "must be less than or equal to %{count}" 86 | msgstr[0] "" 87 | msgstr[1] "" 88 | 89 | msgid "must be greater than or equal to %{count}" 90 | msgid_plural "must be greater than or equal to %{count}" 91 | msgstr[0] "" 92 | msgstr[1] "" 93 | 94 | msgid "must be equal to %{count}" 95 | msgid_plural "must be equal to %{count}" 96 | msgstr[0] "" 97 | msgstr[1] "" 98 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160620042914_create_user.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Repo.Migrations.CreateUser do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:users) do 6 | add :name, :string 7 | add :email, :string 8 | add :crypted_password, :string 9 | 10 | timestamps() 11 | end 12 | 13 | create unique_index(:users, [:email]) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160708122133_create_oauth_credential.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Repo.Migrations.CreateOauthCredential do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:oauth_credentials) do 6 | add :provider_name, :string 7 | add :provider_id, :string 8 | add :user_id, references(:users, on_delete: :nothing) 9 | 10 | timestamps() 11 | end 12 | create index(:oauth_credentials, [:user_id]) 13 | 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160713034714_change_oauth_terms.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Repo.Migrations.ChangeOauthTerms do 2 | use Ecto.Migration 3 | 4 | def change do 5 | rename table(:oauth_credentials), :provider_id, to: :uid 6 | rename table(:oauth_credentials), :provider_name, to: :provider 7 | rename table(:oauth_credentials), to: table(:oauth_infos) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160714065857_adding_guardian_tokens.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Repo.Migrations.AddingGuardianTokens do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:guardian_tokens, primary_key: false) do 6 | add :jti, :string, primary_key: true 7 | add :typ, :string 8 | add :aud, :string 9 | add :iss, :string 10 | add :sub, :string 11 | add :exp, :bigint 12 | add :jwt, :text 13 | add :claims, :map 14 | timestamps() 15 | end 16 | create unique_index(:guardian_tokens, [:jti, :aud]) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160822163850_create_post.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Repo.Migrations.CreatePost do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:posts) do 6 | add :title, :string 7 | add :slug, :string 8 | add :body, :text 9 | add :excerpt, :text 10 | add :user_id, references(:users, on_delete: :nothing) 11 | 12 | timestamps() 13 | end 14 | create unique_index(:posts, [:slug]) 15 | create index(:posts, [:user_id]) 16 | 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160927150252_remove_excerpt_from_posts.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Repo.Migrations.RemoveExcerptFromPosts do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:posts) do 6 | remove :excerpt 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160928120350_adding_pined_to_posts.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Repo.Migrations.AddingPinedToPosts do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:posts) do 6 | add :pinned, :boolean, default: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161001150215_making_posts_user_id_not_null.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Repo.Migrations.MakingPostsUserIdNotNull do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:posts) do 6 | modify(:user_id, :bigint, null: false) 7 | end 8 | end 9 | 10 | def down do 11 | alter table(:posts) do 12 | modify(:user_id, :bigint, null: true) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161226142126_create_comment.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Repo.Migrations.CreateComment do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:comments) do 6 | add :body, :text 7 | add :position, :integer, default: 1 8 | add :post_id, references(:posts, on_delete: :nothing) 9 | add :user_id, references(:users, on_delete: :nothing) 10 | 11 | timestamps() 12 | end 13 | create index(:comments, [:post_id]) 14 | create index(:comments, [:user_id]) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161229015228_add_markdown_body_column_to_posts.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Repo.Migrations.AddMarkdownBodyColumnToPosts do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:posts) do 6 | add :markdown_body, :text 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/seeds.exs: -------------------------------------------------------------------------------- 1 | # Script for populating the database. You can run it as: 2 | # 3 | # mix run priv/repo/seeds.exs 4 | # 5 | # Inside the script, you can read and write to any of your 6 | # repositories directly: 7 | # 8 | # ElixirTw.Repo.insert!(%ElixirTw.SomeSchema{}) 9 | # 10 | # We recommend using the bang functions (`insert!`, `update!` 11 | # and so on) as they will fail if something goes wrong. 12 | -------------------------------------------------------------------------------- /rel/commands/migrate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | $RELEASE_ROOT_DIR/bin/elixir_tw command Elixir.ElixirTw.ReleaseTasks seed 4 | -------------------------------------------------------------------------------- /rel/config.exs: -------------------------------------------------------------------------------- 1 | # Import all plugins from `rel/plugins` 2 | # They can then be used by adding `plugin MyPlugin` to 3 | # either an environment, or release definition, where 4 | # `MyPlugin` is the name of the plugin module. 5 | Path.join(["rel", "plugins", "*.exs"]) 6 | |> Path.wildcard() 7 | |> Enum.map(&Code.eval_file(&1)) 8 | 9 | use Mix.Releases.Config, 10 | # This sets the default release built by `mix release` 11 | default_release: :default, 12 | # This sets the default environment used by `mix release` 13 | default_environment: Mix.env() 14 | 15 | # For a full list of config options for both releases 16 | # and environments, visit https://hexdocs.pm/distillery/configuration.html 17 | 18 | 19 | # You may define one or more environments in this file, 20 | # an environment's settings will override those of a release 21 | # when building in that environment, this combination of release 22 | # and environment configuration is called a profile 23 | 24 | environment :dev do 25 | # If you are running Phoenix, you should make sure that 26 | # server: true is set and the code reloader is disabled, 27 | # even in dev mode. 28 | # It is recommended that you build with MIX_ENV=prod and pass 29 | # the --env flag to Distillery explicitly if you want to use 30 | # dev mode. 31 | set dev_mode: true 32 | set include_erts: false 33 | set cookie: :dev 34 | set vm_args: "rel/vm.args.eex" 35 | end 36 | 37 | environment :prod do 38 | set include_erts: true 39 | set include_src: false 40 | set cookie: :prod 41 | #set output_dir: "rel/elixir_tw" 42 | set vm_args: "rel/vm.args.eex" 43 | end 44 | 45 | # You may define one or more releases in this file. 46 | # If you have not set a default release, or selected one 47 | # when running `mix release`, the first release in the file 48 | # will be used by default 49 | 50 | release :elixir_tw do 51 | set version: current_version(:elixir_tw) 52 | set applications: [ 53 | :runtime_tools 54 | ] 55 | plugin Conform.ReleasePlugin 56 | end 57 | 58 | -------------------------------------------------------------------------------- /rel/vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Name of the node 2 | -name <%= release_name %>@127.0.0.1 3 | 4 | ## Cookie for distributed erlang 5 | # When this is commmented, the startup script looks in 6 | # the $COOKIE env var or $HOME/.erlang.cookie file 7 | # -setcookie <%= release.profile.cookie %> 8 | 9 | ## Enable kernel poll and a few async threads 10 | +K true 11 | +A 128 12 | 13 | ## Increase number of concurrent ports/sockets 14 | -env ERL_MAX_PORTS 65536 15 | 16 | ## Tweak GC to run more often 17 | ##-env ERL_FULLSWEEP_AFTER 10 18 | 19 | # Enable SMP automatically based on availability 20 | -smp auto 21 | -------------------------------------------------------------------------------- /scripts/build-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Build production release 4 | 5 | export MIX_ENV=prod 6 | 7 | # Exit on errors 8 | set -e 9 | # set -o errexit -o xtrace 10 | 11 | CURDIR="$PWD" 12 | BINDIR=$(dirname "$0") 13 | cd "$BINDIR"; BINDIR="$PWD"; cd "$CURDIR" 14 | 15 | BASEDIR="$BINDIR/.." 16 | cd "$BASEDIR" 17 | 18 | source "$HOME/.asdf/asdf.sh" 19 | 20 | echo "Pulling latest code from git" 21 | git pull 22 | 23 | echo "Updating versions of Erlang/Elixir/Node.js if necessary" 24 | asdf install 25 | asdf install 26 | 27 | echo "Updating Elixir libs" 28 | mix local.hex --force 29 | mix local.rebar --force 30 | mix deps.get --only "$MIX_ENV" 31 | 32 | echo "Compiling" 33 | mix compile 34 | 35 | echo "Updating node libraries" 36 | (cd assets && yarn install && yarn run deploy) 37 | 38 | echo "Building release" 39 | mix deps.get --only "$MIX_ENV" 40 | mix do compile, phx.digest, release 41 | -------------------------------------------------------------------------------- /scripts/db-migrate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Run db migrations 4 | 5 | export MIX_ENV=prod 6 | 7 | # Exit on errors 8 | set -e 9 | # set -o errexit -o xtrace 10 | 11 | CURDIR="$PWD" 12 | BINDIR=$(dirname "$0") 13 | cd "$BINDIR"; BINDIR="$PWD"; cd "$CURDIR" 14 | 15 | BASEDIR="$BINDIR/.." 16 | cd "$BASEDIR" 17 | 18 | source "$HOME/.asdf/asdf.sh" 19 | 20 | echo "Running database migrationse" 21 | 22 | mix ecto.migrate 23 | -------------------------------------------------------------------------------- /scripts/db-setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Set up db 4 | 5 | export MIX_ENV=prod 6 | 7 | # Exit on errors 8 | set -e 9 | # set -o errexit -o xtrace 10 | 11 | CURDIR="$PWD" 12 | BINDIR=$(dirname "$0") 13 | cd "$BINDIR"; BINDIR="$PWD"; cd "$CURDIR" 14 | 15 | BASEDIR="$BINDIR/.." 16 | cd "$BASEDIR" 17 | 18 | source "$HOME/.asdf/asdf.sh" 19 | 20 | echo "Running database setup" 21 | 22 | mix ecto.setup 23 | -------------------------------------------------------------------------------- /scripts/deploy-local.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Deploy app to local server 4 | 5 | export MIX_ENV=prod 6 | APP_NAME=elixir_tw 7 | 8 | # Exit on errors 9 | set -e 10 | # set -o errexit -o xtrace 11 | 12 | CURDIR="$PWD" 13 | BINDIR=$(dirname "$0") 14 | cd "$BINDIR"; BINDIR="$PWD"; cd "$CURDIR" 15 | 16 | BASEDIR="$BINDIR/.." 17 | cd "$BASEDIR" 18 | 19 | source "$HOME/.asdf/asdf.sh" 20 | 21 | # mix ecto.migrate 22 | 23 | mix deploy.local 24 | sudo /bin/systemctl restart "$APP_NAME" 25 | -------------------------------------------------------------------------------- /scripts/deploy-remote.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Deploy app to remote servers 4 | 5 | export MIX_ENV=prod 6 | 7 | # Exit on errors 8 | set -e 9 | # set -o errexit -o xtrace 10 | 11 | CURDIR="$PWD" 12 | BINDIR=$(dirname "$0") 13 | cd "$BINDIR"; BINDIR="$PWD"; cd "$CURDIR" 14 | 15 | BASEDIR="$BINDIR/.." 16 | cd "$BASEDIR/ansible" 17 | 18 | ansible-playbook -u deploy -v -l web-servers playbooks/deploy-app.yml --tags deploy --extra-vars ansible_become=false -D 19 | -------------------------------------------------------------------------------- /scripts/sync-assets.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Sync static assets to CDN 4 | 5 | export MIX_ENV=prod 6 | export AWS_PROFILE=deploy-template-prod 7 | 8 | STATIC_ASSETS_BUCKET=cogini-deploy-template-assets 9 | 10 | # Exit on errors 11 | set -e 12 | # set -o errexit -o xtrace 13 | 14 | CURDIR="$PWD" 15 | BINDIR=$(dirname "$0") 16 | cd "$BINDIR"; BINDIR="$PWD"; cd "$CURDIR" 17 | 18 | BASEDIR="$BINDIR/.." 19 | cd "$BASEDIR" 20 | 21 | source "$HOME/.asdf/asdf.sh" 22 | 23 | aws s3 sync "$BASEDIR/priv/static" "s3://$STATIC_ASSETS_BUCKET" 24 | -------------------------------------------------------------------------------- /test/elixir_tw/account/account_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.AccountTest do 2 | use ElixirTw.DataCase, async: true 3 | 4 | alias ElixirTw.Account 5 | 6 | describe "get_user/1" do 7 | setup do 8 | %{user: insert(:user)} 9 | end 10 | 11 | @tag :pending 12 | test "find user with integer id", context do 13 | user = Account.get_user(context.user.id) 14 | 15 | assert user 16 | assert user == context.user 17 | end 18 | end 19 | 20 | describe "get_user/2" do 21 | setup do 22 | oauth = insert(:oauth_info) 23 | %{user: oauth.user, oauth: oauth} 24 | end 25 | 26 | test "find user with string oauth token", context do 27 | user = Account.get_user(context.oauth.provider, context.oauth.uid) 28 | 29 | assert user 30 | assert user == context.user 31 | end 32 | end 33 | 34 | describe "create_user_with_oauth/2" do 35 | setup do 36 | auth_stub = %Ueberauth.Auth{ 37 | info: %Ueberauth.Auth.Info{ 38 | description: nil, 39 | email: "bogus@mail.com", 40 | first_name: nil, 41 | image: "http://graph.facebook.com/0000000000000000/picture?type=square", 42 | last_name: nil, 43 | location: nil, 44 | name: "Banana Man", 45 | nickname: nil, 46 | phone: nil, 47 | urls: %{facebook: nil, website: nil} 48 | }, 49 | provider: :facebook, 50 | strategy: Ueberauth.Strategy.Facebook, 51 | uid: 10_157_894_562_480_206 52 | } 53 | 54 | %{oauth_stub: auth_stub} 55 | end 56 | 57 | test "if user is present in tuple, returns success tuple", context do 58 | user = insert(:user) 59 | 60 | assert {:ok, user_1} = Account.create_user_with_oauth(user, context.oauth_stub) 61 | assert user_1 == user 62 | end 63 | 64 | test "if user is not found, creates a new user from oauth info", context do 65 | assert {:ok, user} = Account.create_user_with_oauth(nil, context.oauth_stub) 66 | assert user.id 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /test/elixir_tw_web/controllers/page_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.PageControllerTest do 2 | use ElixirTwWeb.ConnCase 3 | 4 | test "GET /", %{conn: conn} do 5 | conn = get(conn, "/") 6 | assert html_response(conn, 200) =~ "Elixir |> Taiwan" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/elixir_tw_web/controllers/session_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.SessionControllerTest do 2 | use ElixirTwWeb.ConnCase 3 | alias ElixirTw.Account.User 4 | alias ElixirTwWeb.SessionController 5 | 6 | @ueberauth_auth %{ 7 | credentials: %{token: "fdsnoafhnoofh08h38h"}, 8 | info: %{email: "bob@example.com", name: "Bob"}, 9 | provider: :github, 10 | uid: 12_345_678 11 | } 12 | 13 | # Reference to https://github.com/ueberauth/ueberauth_example/issues/22 14 | test "creates user from github information", %{conn: conn} do 15 | conn = 16 | conn 17 | |> bypass_through(ElixirTwWeb.Router, [:browser]) 18 | |> assign(:ueberauth_auth, @ueberauth_auth) 19 | |> get("/auth/github/callback") 20 | |> put_session(:origin_url, "/") 21 | |> SessionController.callback(%{}) 22 | 23 | users = Repo.all(User) 24 | assert Enum.count(users) == 1 25 | assert get_flash(conn, :info) == "驗證成功!" 26 | end 27 | 28 | test "DELETE /logout", %{conn: conn} do 29 | conn = delete(conn, "/logout") 30 | 31 | assert redirected_to(conn, 302) == "/" 32 | assert get_flash(conn, :info) == "Logged out successfully" 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/elixir_tw_web/views/error_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.ErrorViewTest do 2 | use ElixirTwWeb.ConnCase, async: true 3 | 4 | # Bring render/3 and render_to_string/3 for testing custom views 5 | import Phoenix.View 6 | 7 | test "renders 404.html" do 8 | assert render_to_string(ElixirTwWeb.ErrorView, "404.html", []) == "Page not found" 9 | end 10 | 11 | test "render 500.html" do 12 | assert render_to_string(ElixirTwWeb.ErrorView, "500.html", []) == "Server internal error" 13 | end 14 | 15 | test "render any other" do 16 | assert render_to_string(ElixirTwWeb.ErrorView, "505.html", []) == "Server internal error" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/elixir_tw_web/views/layout_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.LayoutViewTest do 2 | use ElixirTwWeb.ConnCase, async: true 3 | end 4 | -------------------------------------------------------------------------------- /test/elixir_tw_web/views/page_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.PageViewTest do 2 | use ElixirTwWeb.ConnCase, async: true 3 | end 4 | -------------------------------------------------------------------------------- /test/support/channel_case.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.ChannelCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | channel tests. 5 | 6 | Such tests rely on `Phoenix.ChannelTest` and also 7 | imports other functionality to make it easier 8 | to build and query schema. 9 | 10 | Finally, if the test case interacts with the database, 11 | it cannot be async. For this reason, every test runs 12 | inside a transaction which is reset at the beginning 13 | of the test unless the test case is marked as async. 14 | """ 15 | 16 | use ExUnit.CaseTemplate 17 | 18 | using do 19 | quote do 20 | # Import conveniences for testing with channels 21 | use Phoenix.ChannelTest 22 | 23 | alias ElixirTw.Repo 24 | import Ecto 25 | import Ecto.Changeset 26 | import Ecto.Query, only: [from: 1, from: 2] 27 | 28 | # The default endpoint for testing 29 | @endpoint ElixirTwWeb.Endpoint 30 | end 31 | end 32 | 33 | setup tags do 34 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(ElixirTw.Repo) 35 | 36 | unless tags[:async] do 37 | Ecto.Adapters.SQL.Sandbox.mode(ElixirTw.Repo, {:shared, self()}) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTwWeb.ConnCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | tests that require setting up a connection. 5 | 6 | Such tests rely on `Phoenix.ConnTest` and also 7 | imports other functionality to make it easier 8 | to build and query schema. 9 | 10 | Finally, if the test case interacts with the database, 11 | it cannot be async. For this reason, every test runs 12 | inside a transaction which is reset at the beginning 13 | of the test unless the test case is marked as async. 14 | """ 15 | 16 | use ExUnit.CaseTemplate 17 | 18 | using do 19 | quote do 20 | # Import conveniences for testing with connections 21 | use Phoenix.ConnTest 22 | 23 | alias ElixirTw.Repo 24 | import Ecto 25 | import Ecto.Changeset 26 | import Ecto.Query, only: [from: 1, from: 2] 27 | 28 | import ElixirTw.Factory 29 | import ElixirTwWeb.Router.Helpers 30 | 31 | # The default endpoint for testing 32 | @endpoint ElixirTwWeb.Endpoint 33 | end 34 | end 35 | 36 | setup tags do 37 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(ElixirTw.Repo) 38 | 39 | unless tags[:async] do 40 | Ecto.Adapters.SQL.Sandbox.mode(ElixirTw.Repo, {:shared, self()}) 41 | end 42 | 43 | {:ok, conn: Phoenix.ConnTest.build_conn()} 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/support/data_case.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.DataCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | data tests. 5 | 6 | You may define functions here to be used as helpers in 7 | your data tests. See `errors_on/2`'s definition as reference. 8 | 9 | Finally, if the test case interacts with the database, 10 | it cannot be async. For this reason, every test runs 11 | inside a transaction which is reset at the beginning 12 | of the test unless the test case is marked as async. 13 | """ 14 | 15 | use ExUnit.CaseTemplate 16 | 17 | using do 18 | quote do 19 | alias ElixirTw.Repo 20 | 21 | import Ecto 22 | import Ecto.Changeset 23 | import Ecto.Query, only: [from: 1, from: 2] 24 | import ElixirTw.Factory 25 | import ElixirTw.DataCase 26 | end 27 | end 28 | 29 | setup tags do 30 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(ElixirTw.Repo) 31 | 32 | unless tags[:async] do 33 | Ecto.Adapters.SQL.Sandbox.mode(ElixirTw.Repo, {:shared, self()}) 34 | end 35 | 36 | :ok 37 | end 38 | 39 | @doc """ 40 | Helper for returning list of errors in schema when passed certain data. 41 | 42 | ## Examples 43 | 44 | Given a User schema that lists `:name` as a required field and validates 45 | `:password` to be safe, it would return: 46 | 47 | iex> errors_on(%User{}, %{password: "password"}) 48 | [password: "is unsafe", name: "is blank"] 49 | 50 | You could then write your assertion like: 51 | 52 | assert {:password, "is unsafe"} in errors_on(%User{}, %{password: "password"}) 53 | 54 | You can also create the changeset manually and retrieve the errors 55 | field directly: 56 | 57 | iex> changeset = User.changeset(%User{}, password: "password") 58 | iex> {:password, "is unsafe"} in changeset.errors 59 | true 60 | """ 61 | def errors_on(schema, data) do 62 | schema.__struct__.changeset(schema, data).errors 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/support/factory.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirTw.Factory do 2 | @moduledoc """ 3 | ElixirTw test factories 4 | """ 5 | 6 | use ExMachina.Ecto, repo: ElixirTw.Repo 7 | 8 | alias ElixirTw.Account.User 9 | alias ElixirTw.Account.OAuthInfo 10 | alias ElixirTw.Board.Post 11 | 12 | def user_factory do 13 | %User{ 14 | name: Faker.Name.name(), 15 | email: Faker.Internet.email() 16 | } 17 | end 18 | 19 | def oauth_info_factory do 20 | %OAuthInfo{ 21 | provider: Faker.Company.name(), 22 | uid: sequence("allyourbasearemy"), 23 | user: insert(:user) 24 | } 25 | end 26 | 27 | def post_factory do 28 | simple_body = Faker.Lorem.paragraph() 29 | 30 | %Post{ 31 | title: Faker.Lorem.sentence(), 32 | body: simple_body, 33 | markdown_body: simple_body, 34 | user: insert(:user), 35 | slug: sequence(:slug_time, &"#{&1}-sluged-title") 36 | } 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | {:ok, _} = Application.ensure_all_started(:ex_machina) 2 | 3 | ExUnit.start() 4 | 5 | Ecto.Adapters.SQL.Sandbox.mode(ElixirTw.Repo, :manual) 6 | --------------------------------------------------------------------------------