defmodule Mix.Tasks.Deploy.Nginx do use Mix.Task @shortdoc "Update NGINX configuration on server" @moduledoc """ Update NGINX configuration for the deployed Phoenix application. ## Usage mix deploy.nginx # Update nginx config for production mix deploy.nginx staging # Update nginx config for staging This task will: 1. Upload the nginx configuration from deploy/nginx.conf 2. Replace template variables with actual values 3. Test the configuration 4. Reload nginx if the test passes """ require Logger @impl Mix.Task def run(args) do # Load deployment config Mix.Task.run("loadconfig", ["config/deploy.exs"]) env = case args do [] -> :production [env] -> String.to_atom(env) _ -> Logger.error("Too many arguments. Usage: mix deploy.nginx [environment]") exit(1) end config = get_deploy_config(env) Logger.info("Updating NGINX configuration for #{env} environment...") with :ok <- check_requirements(config), :ok <- upload_nginx_config(config), :ok <- test_nginx_config(config), :ok <- reload_nginx(config) do Logger.info("✅ NGINX configuration updated successfully!") else {:error, reason} -> Logger.error("❌ Failed to update NGINX configuration: #{reason}") exit(1) end end defp get_deploy_config(env), do: Deploy.SSH.get_deploy_config(env) defp check_requirements(config) do required_keys = [:user, :domain, :url, :app_port, :app_name] missing = Enum.filter(required_keys, &(not Map.has_key?(config, &1))) if Enum.empty?(missing) do :ok else {:error, "Missing required config keys: #{inspect(missing)}"} end end defp upload_nginx_config(config) do # Check for nginx.conf in deploy directory nginx_files = Path.wildcard("deploy/nginx*.conf") nginx_file = cond do "deploy/nginx.conf" in nginx_files -> "deploy/nginx.conf" length(nginx_files) > 0 -> hd(nginx_files) true -> nil end if nginx_file do # Extract server name from URL - used as nginx config name for uniqueness server_name = URI.parse(config.url).host || "localhost" # SSL domain - could be different from server name for wildcard certs ssl_domain = Map.get(config, :ssl_domain, server_name) Logger.info("Uploading nginx configuration from #{nginx_file} for #{server_name}...") # Read template template = File.read!(nginx_file) # Create unique upstream name from server_name (replace dots/dashes with underscores) upstream_name = server_name |> String.replace(~r/[.-]/, "_") # Replace variables content = template |> String.replace("${APP_NAME}", config.app_name) |> String.replace("${APP_PORT}", to_string(config.app_port)) |> String.replace("${SERVER_NAME}", server_name) |> String.replace("${SSL_DOMAIN}", ssl_domain) |> String.replace("${UPSTREAM_NAME}", upstream_name) |> String.replace("${DEPLOY_TO}", Map.get(config, :deploy_to, "/var/www/#{config.app_name}")) # Create temp file - use server_name for unique filenames per environment temp_file = Path.join(System.tmp_dir!(), "nginx-#{server_name}.conf") File.write!(temp_file, content) # Upload to server remote_temp = "/tmp/nginx-#{server_name}.conf" case scp_upload(config, temp_file, remote_temp) do {_, 0} -> # Move to sites-available - use server_name as config name move_cmd = """ sudo cp #{remote_temp} /etc/nginx/sites-available/#{server_name} && \ sudo ln -sf /etc/nginx/sites-available/#{server_name} /etc/nginx/sites-enabled/#{server_name} """ case ssh_exec(config, move_cmd) do {_, 0} -> File.rm(temp_file) :ok {output, _} -> File.rm(temp_file) {:error, "Failed to install nginx config: #{output}"} end {output, _} -> File.rm(temp_file) {:error, "Failed to upload nginx config: #{output}"} end else {:error, "No nginx configuration file found in deploy/"} end end defp test_nginx_config(config) do Logger.info("Testing nginx configuration...") case ssh_exec(config, "sudo nginx -t") do {output, 0} -> Logger.info(output) :ok {output, _} -> {:error, "Nginx configuration test failed: #{output}"} end end defp reload_nginx(config) do Logger.info("Reloading nginx...") case ssh_exec(config, "sudo systemctl reload nginx") do {_, 0} -> :ok {output, _} -> {:error, "Failed to reload nginx: #{output}"} end end defp ssh_exec(config, command), do: Deploy.SSH.ssh_exec(config, command) defp scp_upload(config, local_file, remote_file), do: Deploy.SSH.scp_upload(config, local_file, remote_file) end