diff --git a/crisiscleanup.sh b/crisiscleanup.sh index b78b0ed..0170ad6 100755 --- a/crisiscleanup.sh +++ b/crisiscleanup.sh @@ -1,69 +1,44 @@ -#!/bin/bash +#!/bin/sh SOURCE_DIR=$(realpath $(dirname "${0}"))/crisiscleanup -# Add NodeJS + Yarn repositories -wget https://deb.nodesource.com/gpgkey/nodesource.gpg.key -O - | apt-key add - -wget https://dl.yarnpkg.com/debian/pubkey.gpg -O - | apt-key add - -echo 'deb https://deb.nodesource.com/node_8.x stretch main' > /etc/apt/sources.list.d/nodejs.list -echo 'deb https://dl.yarnpkg.com/debian/ stable main' > /etc/apt/sources.list.d/yarn.list -apt-get -y update - -# Install CrisisCleanup dependecies -apt-get -y --no-install-recommends install build-essential curl libpq-dev nodejs ruby ruby-dev yarn zlib1g-dev -gem install bundler - -# Clone CrisisCleanup git repository -git clone --depth 1 https://github.com/CrisisCleanup/crisiscleanup /srv/crisiscleanup - -# Fix Ruby version dependencies -sed -i 's/2\.2\.5/2.3.3/' /srv/crisiscleanup/Gemfile -sed -i 's/rdoc (4\.2\.0)/rdoc (4.3.0)/' /srv/crisiscleanup/Gemfile.lock - -# Create CrisisCleanup OS user -adduser --system --group --home /srv/crisiscleanup --shell /bin/bash crisis -chown -R crisis:crisis /srv/crisiscleanup/ - -# Install CrisisCleanup dependencies -sudo -u crisis -i bundle install --path /srv/crisiscleanup/vendor/bundle -sudo -u crisis -i npm install -sudo -u crisis -i yarn +# Build Docker container +docker build -t crisiscleanup ${SOURCE_DIR} # Create database export CRISISCLEANUP_PWD=$(head -c 18 /dev/urandom | base64) -envsubst <${SOURCE_DIR}/tmp/crisiscleanup-createdb.sql >/tmp/crisiscleanup-createdb.sql -sudo -u postgres psql -f /tmp/crisiscleanup-createdb.sql -rm /tmp/crisiscleanup-createdb.sql +envsubst <${SOURCE_DIR}/createdb.sql | docker exec -i postgres psql + +# Copy existing config files into persistent storage +mkdir -p /srv/crisiscleanup/conf +chown 8005:8005 /srv/crisiscleanup/conf +docker run --rm -v /srv/crisiscleanup/conf:/mnt/config crisiscleanup cp -rp /srv/crisiscleanup/config/. /mnt/config +chown root:root /srv/crisiscleanup/conf # Configure CrisisCleanup -export CRISISCLEANUP_SECRET=$(sudo -u crisis -i /srv/crisiscleanup/bin/rake secret) export CRISISCLEANUP_ADMIN_USER="Admin" export CRISISCLEANUP_ADMIN_EMAIL="admin@example.com" export CRISISCLEANUP_ADMIN_PWD=$(head -c 12 /dev/urandom | base64) -envsubst <${SOURCE_DIR}/srv/crisiscleanup/config/database.yml >/srv/crisiscleanup/config/database.yml -envsubst <${SOURCE_DIR}/srv/crisiscleanup/config/secrets.yml >/srv/crisiscleanup/config/secrets.yml -envsubst <${SOURCE_DIR}/srv/crisiscleanup/config/initializers/devise.rb >/srv/crisiscleanup/config/initializers/devise.rb -envsubst <${SOURCE_DIR}/srv/crisiscleanup/db/seeds.rb >/srv/crisiscleanup/db/seeds.rb -cp ${SOURCE_DIR}/srv/crisiscleanup/config/environments/production.rb /srv/crisiscleanup/config/environments/production.rb -cp ${SOURCE_DIR}/srv/crisiscleanup/.env /srv/crisiscleanup/.env -rm /srv/crisiscleanup/.env.test -sudo -u crisis -i /srv/crisiscleanup/bin/rake assets:precompile RAILS_ENV=production +envsubst <${SOURCE_DIR}/srv/crisiscleanup/conf/database.yml >/srv/crisiscleanup/conf/database.yml +cp ${SOURCE_DIR}/srv/crisiscleanup/conf/boot.rb /srv/crisiscleanup/conf/boot.rb +cp ${SOURCE_DIR}/srv/crisiscleanup/conf/initializers/devise.rb /srv/crisiscleanup/conf/initializers/devise.rb +cp ${SOURCE_DIR}/srv/crisiscleanup/conf/environments/production.rb /srv/crisiscleanup/conf/environments/production.rb # Populate database -sudo -u crisis -i /srv/crisiscleanup/bin/rake db:schema:load RAILS_ENV=production -sudo -u crisis -i /srv/crisiscleanup/bin/rake db:seed RAILS_ENV=production +envsubst <${SOURCE_DIR}/srv/crisiscleanup/db/seeds.rb >/tmp/seeds.rb +docker run --rm --link=postgres -v /srv/crisiscleanup/conf:/srv/crisiscleanup/config crisiscleanup rake db:schema:load +docker run --rm --link=postgres -v /srv/crisiscleanup/conf:/srv/crisiscleanup/config -v /tmp/seeds.rb:/srv/crisiscleanup/db/seeds.rb crisiscleanup rake db:seed +rm /tmp/seeds.rb # Create Rails service -cp ${SOURCE_DIR}/lib/systemd/system/crisiscleanup.service /lib/systemd/system/crisiscleanup.service -systemctl daemon-reload +cp ${SOURCE_DIR}/etc/init.d/crisiscleanup /etc/init.d/crisiscleanup +rc-update add crisiscleanup boot +service crisiscleanup start -# Create nginx site definition -cp ${SOURCE_DIR}/etc/nginx/sites-available/crisiscleanup /etc/nginx/sites-available/crisiscleanup -ln -s /etc/nginx/sites-available/crisiscleanup /etc/nginx/sites-enabled/crisiscleanup - -# Restart services -systemctl start crisiscleanup -systemctl restart nginx +# Create nginx app definition +cp ${SOURCE_DIR}/etc/nginx/apps/crisiscleanup /etc/nginx/apps/crisiscleanup +cp ${SOURCE_DIR}/etc/nginx/conf.d/crisiscleanup.conf /etc/nginx/conf.d/crisiscleanup.conf +service nginx reload # Add portal application definition -portal-app-manager crisis-cleanup "https://{host}:8005/" "${CRISISCLEANUP_ADMIN_EMAIL}" "${CRISISCLEANUP_ADMIN_PWD}" +portal-app-manager crisis-cleanup "https://{host}:8405/" "${CRISISCLEANUP_ADMIN_EMAIL}" "${CRISISCLEANUP_ADMIN_PWD}" diff --git a/crisiscleanup/Dockerfile b/crisiscleanup/Dockerfile new file mode 100644 index 0000000..8310a81 --- /dev/null +++ b/crisiscleanup/Dockerfile @@ -0,0 +1,74 @@ +FROM alpine:3.7 +MAINTAINER Disassembler + +RUN \ + # Install Ruby runtime dependencies + apk --no-cache add gdbm libressl readline zlib \ + # Install Ruby build dependencies + && apk --no-cache add --virtual .deps build-base autoconf gdbm-dev libressl-dev linux-headers readline-dev zlib-dev \ + # Download and unpack Ruby + && wget http://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.6.tar.xz -O ruby.tar.xz \ + && mkdir -p /usr/src/ruby \ + && tar -xJf ruby.tar.xz -C /usr/src/ruby --strip-components=1 \ + && rm ruby.tar.xz \ + && cd /usr/src/ruby \ + # Hackfix to suppress "Insecure world writable dir" warning + && sed -ni 'p;13a #define ENABLE_PATH_CHECK 0' file.c \ + # Configure compilation + hackfix to detect isnan/isinf macros + && autoconf \ + && ac_cv_func_isnan=yes ac_cv_func_isinf=yes ./configure --build=x86_64-linux-musl --disable-install-doc --enable-shared \ + # Compile and install Ruby + && make -j $(nproc) \ + && make install \ + # Install RubyGems and Bundler + && mkdir -p /usr/local/etc \ + && echo -e 'install: --no-document\nupdate: --no-document' >/usr/local/etc/gemrc \ + && gem update --system \ + # Cleanup + && cd /tmp \ + && rm -r /usr/src/ruby \ + && apk --no-cache del .deps \ + && rm -rf /root \ + && mkdir /root + +ENV RAILS_ENV production + +RUN \ + # Install runtime dependencies + apk --no-cache add libpq libxml2 libxslt nodejs tzdata \ + # Install build dependencies + && apk --no-cache add --virtual .deps build-base git libxml2-dev libxslt-dev linux-headers paxctl postgresql-dev yarn zlib-dev \ + # Fix grsec attributes to loosen memory protection restrictions + && paxctl -cm /usr/bin/node \ + # Clone CrisisCleanup + && git clone --depth 1 https://github.com/CrisisCleanup/crisiscleanup /srv/crisiscleanup \ + # Hackfix ruby dependency versions + && sed -i 's/2\.2\.5/2.3.6/' /srv/crisiscleanup/Gemfile \ + && sed -i 's/rdoc (4\.2\.0)/rdoc (4.3.0)/' /srv/crisiscleanup/Gemfile.lock \ + # Install Ruby and NodeJS dependencies + && cd /srv/crisiscleanup \ + && bundle config build.nokogiri --use-system-libraries \ + && bundle install \ + && npm install \ + && yarn \ + # Create CrisisCleanup secret + && echo -e "production:\n secret_key_base: $(rake secret)" >/srv/crisiscleanup/config/secrets.yml \ + # Generate static resources + && rake assets:precompile \ + # Create OS user + && addgroup -S -g 8005 crisiscleanup \ + && adduser -S -u 8005 -h /srv/crisiscleanup -s /bin/false -g crisiscleanup -G crisiscleanup crisiscleanup \ + && chown -R crisiscleanup:crisiscleanup /srv/crisiscleanup \ + # Cleanup + && apk --no-cache del .deps \ + && rm -rf /srv/crisiscleanup/.git* \ + && rm -rf /usr/local/share/.cache \ + && rm -rf /root \ + && mkdir /root + +VOLUME ["/srv/crisiscleanup/config"] +EXPOSE 8005 + +USER crisiscleanup +WORKDIR /srv/crisiscleanup +CMD ["rails", "server"] diff --git a/crisiscleanup/tmp/crisiscleanup-createdb.sql b/crisiscleanup/createdb.sql similarity index 100% rename from crisiscleanup/tmp/crisiscleanup-createdb.sql rename to crisiscleanup/createdb.sql diff --git a/crisiscleanup/etc/init.d/crisiscleanup b/crisiscleanup/etc/init.d/crisiscleanup new file mode 100755 index 0000000..df78d02 --- /dev/null +++ b/crisiscleanup/etc/init.d/crisiscleanup @@ -0,0 +1,17 @@ +#!/sbin/openrc-run + +description="Crisis Cleanup docker container" + +depend() { + need docker net + use dns logger netmount + after postgres +} + +start() { + /usr/bin/docker run -d --rm --name crisiscleanup --link=postgres -p 127.0.0.1:9005:8005 -v /srv/crisiscleanup/conf:/srv/crisiscleanup/config crisiscleanup +} + +stop() { + /usr/bin/docker stop crisiscleanup +} diff --git a/crisiscleanup/etc/nginx/apps/crisiscleanup b/crisiscleanup/etc/nginx/apps/crisiscleanup new file mode 100644 index 0000000..be98cf4 --- /dev/null +++ b/crisiscleanup/etc/nginx/apps/crisiscleanup @@ -0,0 +1,9 @@ +access_log /var/log/nginx/crisiscleanup.access.log; +error_log /var/log/nginx/crisiscleanup.error.log; + +location / { + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Host $host:$server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://127.0.0.1:9005; +} diff --git a/crisiscleanup/etc/nginx/conf.d/crisiscleanup.conf b/crisiscleanup/etc/nginx/conf.d/crisiscleanup.conf new file mode 100644 index 0000000..8c9c034 --- /dev/null +++ b/crisiscleanup/etc/nginx/conf.d/crisiscleanup.conf @@ -0,0 +1,11 @@ +server { + listen 8005; + listen [::]:8005; + include apps/crisiscleanup; +} + +server { + listen 8405 ssl http2; + listen [::]:8405 ssl http2; + include apps/crisiscleanup; +} diff --git a/crisiscleanup/etc/nginx/sites-available/crisiscleanup b/crisiscleanup/etc/nginx/sites-available/crisiscleanup deleted file mode 100644 index 2aebf10..0000000 --- a/crisiscleanup/etc/nginx/sites-available/crisiscleanup +++ /dev/null @@ -1,20 +0,0 @@ -server { - listen 8005 ssl http2; - listen [::]:8005 ssl http2; - - access_log /var/log/nginx/crisiscleanup.access.log; - error_log /var/log/nginx/crisiscleanup.error.log; - - root /srv/crisiscleanup/public; - try_files $uri/index.html $uri @crisiscleanup; - - location @crisiscleanup { - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Host $host:$server_port; - proxy_set_header X-Forwarded-Proto https; - proxy_pass http://127.0.0.1:3000; - } - - error_page 404 /404.html; - error_page 500 502 503 504 /500.html; -} diff --git a/crisiscleanup/lib/systemd/system/crisiscleanup.service b/crisiscleanup/lib/systemd/system/crisiscleanup.service deleted file mode 100644 index faf50fd..0000000 --- a/crisiscleanup/lib/systemd/system/crisiscleanup.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Crisis Cleanup Ruby on Rails server -After=network.target - -[Service] -User=crisis -WorkingDirectory=/srv/crisiscleanup -ExecStart=/srv/crisiscleanup/bin/rails server -b 127.0.0.1 -p 3000 -e production - -[Install] -WantedBy=multi-user.target diff --git a/crisiscleanup/srv/crisiscleanup/.env b/crisiscleanup/srv/crisiscleanup/.env deleted file mode 100644 index 49ffb95..0000000 --- a/crisiscleanup/srv/crisiscleanup/.env +++ /dev/null @@ -1,2 +0,0 @@ -COMPOSE_PROJECT_NAME=crisiscleanup -GOOGLE_MAPS_API_KEY=AIzaSyBvIF3D550tlpL6o1xRrDurGo-81VhHlOw diff --git a/crisiscleanup/srv/crisiscleanup/conf/boot.rb b/crisiscleanup/srv/crisiscleanup/conf/boot.rb new file mode 100644 index 0000000..b73a01a --- /dev/null +++ b/crisiscleanup/srv/crisiscleanup/conf/boot.rb @@ -0,0 +1,15 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +ENV['GOOGLE_MAPS_API_KEY'] = 'AIzaSyBvIF3D550tlpL6o1xRrDurGo-81VhHlOw' + +require 'bundler/setup' # Set up gems listed in the Gemfile. + +require 'rails/commands/server' + +module Rails + class Server + alias :default_options_bk :default_options + def default_options + default_options_bk.merge!(Host: '0.0.0.0', Port: 8005) + end + end +end diff --git a/crisiscleanup/srv/crisiscleanup/conf/database.yml b/crisiscleanup/srv/crisiscleanup/conf/database.yml new file mode 100644 index 0000000..da8b643 --- /dev/null +++ b/crisiscleanup/srv/crisiscleanup/conf/database.yml @@ -0,0 +1,8 @@ +production: + adapter: postgresql + encoding: unicode + database: crisiscleanup + pool: 5 + host: postgres + username: crisiscleanup + password: ${CRISISCLEANUP_PWD} diff --git a/crisiscleanup/srv/crisiscleanup/config/environments/production.rb b/crisiscleanup/srv/crisiscleanup/conf/environments/production.rb similarity index 99% rename from crisiscleanup/srv/crisiscleanup/config/environments/production.rb rename to crisiscleanup/srv/crisiscleanup/conf/environments/production.rb index aaf4001..2153f30 100644 --- a/crisiscleanup/srv/crisiscleanup/config/environments/production.rb +++ b/crisiscleanup/srv/crisiscleanup/conf/environments/production.rb @@ -50,7 +50,7 @@ Rails.application.configure do config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - config.force_ssl = true + config.force_ssl = false # Use the lowest log level to ensure availability of diagnostic information # when problems arise. diff --git a/crisiscleanup/srv/crisiscleanup/config/initializers/devise.rb b/crisiscleanup/srv/crisiscleanup/conf/initializers/devise.rb similarity index 98% rename from crisiscleanup/srv/crisiscleanup/config/initializers/devise.rb rename to crisiscleanup/srv/crisiscleanup/conf/initializers/devise.rb index 96467ba..cd60e24 100644 --- a/crisiscleanup/srv/crisiscleanup/config/initializers/devise.rb +++ b/crisiscleanup/srv/crisiscleanup/conf/initializers/devise.rb @@ -4,7 +4,7 @@ Devise.setup do |config| # The secret key used by Devise. Devise uses this key to generate # random tokens. Changing this key will render invalid all existing # confirmation, reset password and unlock tokens in the database. - config.secret_key = '${CRISISCLEANUP_SECRET}' + # config.secret_key = 'bb59fcb0f0a1b46483a9f7f52259c70114db2dd164686243ee5fee9abb18e1f5088e651c8d627e505db5d60691af3586de4b9a6f49cdeaeca59e5946a7726e52' # ==> Mailer Configuration # Configure the e-mail address which will be shown in Devise::Mailer, diff --git a/crisiscleanup/srv/crisiscleanup/config/database.yml b/crisiscleanup/srv/crisiscleanup/config/database.yml deleted file mode 100644 index 2367549..0000000 --- a/crisiscleanup/srv/crisiscleanup/config/database.yml +++ /dev/null @@ -1,31 +0,0 @@ -development: &development - adapter: postgresql - encoding: unicode - database: crisiscleanup_development - pool: 5 - username: crisiscleanup - password: crisiscleanup - host: localhost - port: 5432 - -docker: - <<: *development - host: postgres - -test: - adapter: postgresql - encoding: unicode - database: crisiscleanup_test - pool: 5 - username: crisiscleanup - password: crisiscleanup - host: localhost - port: 5432 - -production: - adapter: postgresql - encoding: unicode - database: crisiscleanup - pool: 5 - username: crisiscleanup - password: ${CRISISCLEANUP_PWD} diff --git a/crisiscleanup/srv/crisiscleanup/config/secrets.yml b/crisiscleanup/srv/crisiscleanup/config/secrets.yml deleted file mode 100644 index 7d90048..0000000 --- a/crisiscleanup/srv/crisiscleanup/config/secrets.yml +++ /dev/null @@ -1,25 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Your secret key is used for verifying the integrity of signed cookies. -# If you change this key, all old signed cookies will become invalid! - -# Make sure the secret is at least 30 characters and all random, -# no regular words or you'll be exposed to dictionary attacks. -# You can use `rake secret` to generate a secure secret key. - -# Make sure the secrets in this file are kept private -# if you're sharing your code publicly. - -development: - secret_key_base: 57e62ae63822f461997fa79e75462ba7316c44e03c80d003ef9f68b572b8755cb5a7221e6c4b4958f995c2b03a82112941ee8951b9f393e5ca2449ab480b572b - -docker: - secret_key_base: a6a0606f1a506b1a9e0059a1db833f2ee3d07ce86e2f938af1f5c0c120b2f5af73965610357edfb82afc90b024ed2ac7f39f25b38d3570d9e3cb76d4d096861b - -test: - secret_key_base: 055f7b3ef374b2c1c7469fab62d9bc38a8dc510b9f314cc401b75e52974b1422f7796e27d8ff1594468c65ee04493c13a249bcaf77a18e75f96ddec5dbd36ce9 - -# Do not keep production secrets in the repository, -# instead read values from the environment. -production: - secret_key_base: ${CRISISCLEANUP_SECRET} diff --git a/crisiscleanup/srv/crisiscleanup/db/seeds.rb b/crisiscleanup/srv/crisiscleanup/db/seeds.rb index 90d7b82..6332e0b 100644 --- a/crisiscleanup/srv/crisiscleanup/db/seeds.rb +++ b/crisiscleanup/srv/crisiscleanup/db/seeds.rb @@ -19,6 +19,5 @@ Legacy::LegacySite.create!([ {address: "200 Epcot Center Drive", blurred_latitude: 28.3849506927356, blurred_longitude: -81.5443968549352, case_number: "A7", city: "Orlando", claimed_by: 2, legacy_event_id: 1, latitude: 28.383045, longitude: -81.5485919, name: "Timothy Schmidt", phone1: "1234567890", reported_by: 2, requested_at: nil, state: "Florida", status: "Open, unassigned", work_type: "Debris", data: {"email"=>"", "notes"=>"", "habitable"=>"n", "assigned_to"=>"", "electricity"=>"n", "prepared_by"=>"", "rent_or_own"=>"", "unsafe_roof"=>"n", "cross_street"=>"", "status_notes"=>"", "time_to_call"=>"", "older_than_60"=>"n", "other_hazards"=>"", "roof_collapse"=>"n", "special_needs"=>"", "num_trees_down"=>"0", "num_wide_trees"=>"0", "chainsaw_needed"=>"n", "first_responder"=>"n", "autofill_disable"=>"", "electrical_lines"=>"n", "total_volunteers"=>"", "destruction_level"=>"", "meal_location_poc"=>"", "do_not_work_before"=>"", "meal_serving_times"=>"", "structural_problems"=>"n", "required_daily_meals"=>"", "work_without_resident"=>"n", "interior_debris_removal"=>"n", "unsalvageable_structure"=>"n", "heavy_machinary_required"=>"n", "vegitative_debris_removal"=>"n", "hours_worked_per_volunteer"=>"", "initials_of_resident_present"=>"", "nonvegitative_debris_removal"=>"n", "member_of_assessing_organization"=>"n"}, request_date: "2017-09-18", appengine_key: nil, zip_code: "32821", county: "Orange County", phone2: "", work_requested: "", name_metaphone: "TM0 SXMTT", city_metaphone: "ORLNT", county_metaphone: "ORNJ KNT", address_metaphone: " EPKT SNTR TRF", user_id: 2} ]) User.create!([ - {email: "${CRISISCLEANUP_ADMIN_EMAIL}", password: "${CRISISCLEANUP_ADMIN_PWD}", name: "${CRISISCLEANUP_ADMIN_USER}", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 1, legacy_organization_id: 1, current_sign_in_at: "2016-07-15 03:45:59", last_sign_in_at: "2016-06-08 16:56:37", current_sign_in_ip: "1.1.1.1", last_sign_in_ip: "173.164.56.105", referring_user_id: nil, admin: true, role: nil, mobile: nil, accepted_terms: true, accepted_terms_timestamp: "2017-09-18 20:46:31", title: nil}, - {email: "demo@crisiscleanup.org", password: "password", name: "Demo User", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 2, legacy_organization_id: 2, current_sign_in_at: "2017-09-18 20:47:11", last_sign_in_at: "2017-09-18 14:22:32", current_sign_in_ip: "::1", last_sign_in_ip: "1.1.1.1", referring_user_id: 1, admin: false, role: "Primary Contact", mobile: "555-555-5555", accepted_terms: true, accepted_terms_timestamp: "2017-09-18 20:47:11", title: "Manager"} + {email: "${CRISISCLEANUP_ADMIN_EMAIL}", password: "${CRISISCLEANUP_ADMIN_PWD}", name: "${CRISISCLEANUP_ADMIN_USER}", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 1, legacy_organization_id: 1, current_sign_in_at: "2016-07-15 03:45:59", last_sign_in_at: "2016-06-08 16:56:37", current_sign_in_ip: "1.1.1.1", last_sign_in_ip: "173.164.56.105", referring_user_id: nil, admin: true, role: nil, mobile: nil, accepted_terms: true, accepted_terms_timestamp: "2017-09-18 20:46:31", title: nil} ])