Create a file deploy.sh
on linuxremote under ~/workspace
with the following content.
module load elixir erlang || exit 1
cd ~/workspace/csci379 || exit 1
echo "Pulling the latest code..."
git pull || { echo "git pull failed"; exit 1; }
echo "Loading dependencies..."
mix deps.get --only prod || { echo "mix deps.get failed"; exit 1; }
echo "Compiling Code"
MIX_ENV=prod mix compile || { echo "mix compile failed"; exit 1; }
echo "Migrating Database"
MIX_ENV=prod mix ecto.migrate || { echo "mix ecto.migrate failed"; exit 1; }
echo "Deploying assets..."
MIX_ENV=prod mix assets.deploy || { echo "mix assets.deploy failed"; exit 1; }
echo "(Re)starting server in tmux..."
tmux kill-session -t LETTER 2>/dev/null || true
tmux new -d -s LETTER "MIX_ENV=prod mix phx.server" || { echo "tmux failed"; exit 1; }
echo "Deployment complete."
Don't forget to change letter in the script and make this file executable:
chmod 700 ~/workspace/deploy.sh
While we are on bucknell's linux system we should set up our secrets.
Modify ~/.bashrc
to include the production databases's credentials and a secret:
export DATABASE_URL_LETTER="ecto://csci379_25s_LETTER:PASSWORD@eg-postgresql.bucknell.edu/csci379_25s_LETTER"
export SECRET_KEY_BASE="Iee/+2HRTmYA03aa1xvY0umwT1GBM2VPrdMpTpay0NPwacef4L+Wa1bntBQiU6Nk"
Obviously replace LETTER with your course letter (twice, see above) and PASSWORD with your database password that you can find on the course website in your user-menu (when logged in). Also fill in your unique secret key base which you can generate with:
mix phx.gen.secret
Back in our local project workspace.
Update config/prod.exs
:
config :app, AppWeb.Endpoint,
cache_static_manifest: "priv/static/cache_manifest.json",
check_origin: ["https://eg.bucknell.edu"] # <-- update to this
Also update config/runtime.exs
:
# ...
System.get_env("DATABASE_URL_LETTER") || # <-- replace letter with your letter
raise """
environment variable DATABASE_URL_LETTER is missing.
For example: ecto://USER:PASS@HOST/DATABASE
"""
# ...
config :app, AppWeb.Endpoint,
url: [
host: "www.eg.bucknell.edu", # <-- update to this domain
path: "/csci379-25s-LETTER", # <-- update path with your correct letter
port: 443,
scheme: "https"
],
http: [
# ...
port: 4000 # <-- this needs to match your port number!
],
secret_key_base: secret_key_base
# ...
Since we serve our website locally directly on localhost
but our production environment is setup under a subdomain eg.bucknell.edu/csci379-s25-LETTER
we also need to tweak our websocket on the frontend to prepend the folder if we are in production enviroment:
assets/js/app.js
:
let liveSocketPath = process.env.NODE_ENV === "production" ? "/csci379-s25-LETTER/live" : "/live";
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket(liveSocketPath, Socket, {
longPollFallbackMs: 2500,
params: { _csrf_token: csrfToken }
})
Don't forget to replace LETTER with your letter again, this time, lower-case.
Now your production environment is set up and should be able to connect to the database later.
mix ecto.create
and mix ecto.drop
will not work as commands as we don't have postgres superadmin rights. Thus we will need to add a custom script that simulates the same behavior.
Add lib/mix/tasks/reset_tables.ex
(create folder path as it doesn't exist yet):
In your local computer's project workspace you will need to add two misk tasks:
lib/mix/tasks/deploy.ex
:
defmodule Mix.Tasks.Deploy do
use Mix.Task
@shortdoc "SSH into the server, update code, run tests, and restart the Phoenix server"
def run(_) do
ssh_command = """
ssh linuxremote3 << 'EOF'
~/workspace/deploy.sh
EOF
"""
{result, _exit_code} = System.cmd("bash", ["-c", ssh_command], stderr_to_stdout: true)
IO.puts(result)
end
end
This will allow you to simply run mix deploy
from your local computer and automatically ssh into Bucknell's linuxremote3 server in order to update the live website.
lib/mix/tasks/deploy.ex
:
defmodule Mix.Tasks.ResetTables do
use Mix.Task
@shortdoc "Drops, recreates and migrates database."
def run(_) do
# Start the application to ensure Repo is loaded
Mix.Task.run("app.start")
alias App.Repo
alias Ecto.Adapters.SQL
Mix.shell().info("Resetting tables...")
# Drop all tables
drop_all_tables()
# Run migrations
Mix.Task.run("ecto.migrate")
# Seed the database
# Mix.Task.run("run", ["priv/repo/seeds.exs"])
end
defp drop_all_tables do
alias Ecto.Adapters.SQL
alias App.Repo
query = """
DO $$ DECLARE
r RECORD;
BEGIN
FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP
EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE';
END LOOP;
END $$;
"""
SQL.query!(Repo, query, [])
Mix.shell().info("Dropped all tables.")
end
end
Since we can't delete and recreate the database directly (on bucknell's server) this workaround will essential do the same steps as mix reset
. (Delete, Recreate, Migrate)
If we ever mess up our migrations/database we can now run MIX_ENV=prod mix reset_tables
(in linuxremote) to reset the live database.