diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2c87c37..e30a152 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,8 @@ "context": ".." }, "remoteEnv": { - "MIX_ENV": "dev" + "MIX_ENV": "dev", + "GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}" }, "customizations": { "vscode": { @@ -29,7 +30,7 @@ } }, "postCreateCommand": "mix deps.get", - "postStartCommand": "/bin/sh /workspace/.devcontainer/scripts/post_start.sh", + "postStartCommand": "mix post_start", "mounts": [ "source=${localWorkspaceFolder},target=/workspace,type=bind", "source=${localEnv:HOME}/.gitconfig,target=/root/.gitconfig,type=bind,consistency=cached", diff --git a/.devcontainer/scripts/post_start.sh b/.devcontainer/scripts/post_start.sh deleted file mode 100755 index 1480e8f..0000000 --- a/.devcontainer/scripts/post_start.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/bin/bash - -echo "Starting post-start configuration..." - -# 1. Load Environment Variables from `.env` -if [ -f .env ]; then - export $(grep -v '^#' .env | xargs) - echo "Environment variables loaded from .env" -else - echo "No .env file found. Falling back to defaults." -fi - -# 2. Configure Git User -echo "Applying user Git configuration..." - -# Load user.name from gitconfig, .env, or fallback to "Unknown User" -GIT_USER_NAME=$(git config --global user.name || echo "${GIT_USER_NAME:-Unknown User}") -git config --global user.name "$GIT_USER_NAME" - -# Load user.email from gitconfig, .env, or fallback to "unknown@example.com" -GIT_USER_EMAIL=$(git config --global user.email || echo "${GIT_USER_EMAIL:-unknown@example.com}") -git config --global user.email "$GIT_USER_EMAIL" - -# Output the final values -echo "Git user.name: $(git config --global user.name)" -echo "Git user.email: $(git config --global user.email)" - -# 3. Configure SSH -echo "Setting up SSH..." -eval "$(ssh-agent -s)" -if [ "$(ls -A ~/.ssh 2>/dev/null)" ]; then - chmod 600 ~/.ssh/* - ssh-add ~/.ssh/id_ed25519 2>/dev/null && echo "Added id_ed25519 to SSH agent." - ssh-add ~/.ssh/id_rsa 2>/dev/null && echo "Added id_rsa to SSH agent." -else - echo "No SSH key found. Please ensure keys are available in ~/.ssh." -fi - -# 4. Test SSH and Fallback to HTTPS -echo "Testing SSH connection to GitHub..." -if ssh -T git@github.com 2>/dev/null; then - echo "SSH connection successful." -else - echo "SSH connection failed. Falling back to HTTPS." - REMOTE_URL=$(git remote get-url origin 2>/dev/null || echo "") - if [[ "$REMOTE_URL" == git@* ]]; then - HTTPS_URL=$(echo "$REMOTE_URL" | sed -e 's/^git@github.com:/https:\/\/github.com\//') - git remote set-url origin "$HTTPS_URL" - echo "Updated remote URL to HTTPS: $HTTPS_URL" - fi -fi - -# 5. Authenticate with GitHub CLI (if available) -if command -v gh &> /dev/null; then - echo "Authenticating with GitHub CLI..." - if [ -z "$GITHUB_TOKEN" ]; then - echo "No GITHUB_TOKEN found in the environment. Please provide one in the .env file." - exit 1 - fi - echo "$GITHUB_TOKEN" | gh auth login --with-token || { - echo "GitHub CLI authentication failed." - exit 1 - } - echo "Authenticated with GitHub CLI." -fi - -# 6. Add Git Credentials for HTTPS -if [ -n "$GITHUB_TOKEN" ]; then - echo "Configuring Git credentials for HTTPS..." - git config --global credential.helper store - echo "https://$GITHUB_TOKEN@github.com" > ~/.git-credentials -fi - -echo "Post-start configuration complete!" diff --git a/lib/mix/tasks/post_start.ex b/lib/mix/tasks/post_start.ex new file mode 100644 index 0000000..0495408 --- /dev/null +++ b/lib/mix/tasks/post_start.ex @@ -0,0 +1,182 @@ +defmodule Mix.Tasks.PostStart do + @shortdoc "Run post-start configuration" + + @moduledoc false + use Mix.Task + + require Logger + + def run(_args) do + Logger.info("Starting post-start configuration...") + + log_step("Loading environment variables", &load_environment_variables/0) + log_step("Validating Git configuration", &validate_git_configuration/0) + log_step("Configuring SSH", &configure_ssh/0) + log_step("Setting up GitHub authentication", &setup_github_authentication/0) + + Logger.info("Post-start configuration complete!") + end + + defp log_step(step_description, func) do + Logger.info("Step: #{step_description}") + + try do + func.() + rescue + e -> Logger.error("#{step_description} failed: #{inspect(e)}") + end + end + + defp load_environment_variables do + if File.exists?(".env") do + ".env" + |> File.read!() + |> String.split("\n") + |> Enum.reject(&String.starts_with?(&1, "#")) + |> Enum.each(&load_env_var/1) + + Logger.info("Environment variables loaded from .env") + else + Logger.info("No .env file found. Falling back to defaults.") + end + rescue + e -> Logger.error("Failed to load environment variables: #{inspect(e)}") + end + + defp load_env_var(line) do + trimmed_line = String.trim(line) + + if trimmed_line == "" or String.starts_with?(trimmed_line, "#") do + :ok + else + case String.split(trimmed_line, "=", parts: 2) do + [key, value] -> System.put_env(key, value) + _ -> Logger.error("Invalid .env line: #{line}") + end + end + rescue + e -> Logger.error("Error processing .env line: #{inspect(e)}") + end + + defp validate_git_configuration do + Logger.info("Validating Git configuration...") + + user_name = get_git_config("user.name") || "Unknown User" + Logger.info("Git user.name: #{user_name}") + + user_email = get_git_config("user.email") || "unknown@example.com" + Logger.info("Git user.email: #{user_email}") + rescue + e -> Logger.error("Error validating Git configuration: #{inspect(e)}") + end + + defp get_git_config(key) do + case System.cmd("git", ["config", "--global", key]) do + {result, 0} -> String.trim(result) + _ -> nil + end + rescue + e -> + Logger.error("Failed to retrieve Git config for #{key}: #{inspect(e)}") + nil + end + + defp configure_ssh do + Logger.info("Setting up SSH...") + + System.cmd("ssh-agent", ["-s"]) + ssh_dir = Path.expand("~/.ssh") + + if File.dir?(ssh_dir) do + ssh_dir + |> File.ls!() + |> Enum.each(&set_ssh_key_permissions(&1, ssh_dir)) + + ssh_add("id_ed25519") + ssh_add("id_rsa") + else + Logger.info("No SSH key found. Please ensure keys are available in ~/.ssh.") + end + + test_ssh_connection() + rescue + e -> Logger.error("Error setting up SSH: #{inspect(e)}") + end + + defp set_ssh_key_permissions(file, ssh_dir) do + File.chmod!(Path.join(ssh_dir, file), 0o600) + rescue + e -> Logger.error("Error setting permissions for SSH key #{file}: #{inspect(e)}") + end + + defp ssh_add(key) do + key_path = Path.expand("~/.ssh/#{key}") + + if File.exists?(key_path) do + System.cmd("ssh-add", [key_path]) + Logger.info("Added #{key} to SSH agent.") + end + rescue + e -> Logger.error("Error adding SSH key #{key}: #{inspect(e)}") + end + + defp test_ssh_connection do + Logger.info("Testing SSH connection to GitHub...") + + case System.cmd("ssh", ["-T", "git@github.com"], stderr_to_stdout: true) do + {output, 0} -> + if String.contains?(output, "successfully authenticated") do + Logger.info("#{output}") + else + Logger.error("SSH connection failed: #{output}") + fallback_to_https() + end + + {error_output, _} -> + Logger.error("SSH connection failed: #{error_output}") + end + rescue + e -> Logger.error("Error testing SSH connection: #{inspect(e)}") + end + + defp fallback_to_https do + case System.cmd("git", ["remote", "get-url", "origin"]) do + {remote_url, 0} -> + if String.starts_with?(remote_url, "git@github.com") do + https_url = String.replace(remote_url, ~r/^git@github\.com:/, "https://github.com/") + System.cmd("git", ["remote", "set-url", "origin", String.trim(https_url)]) + Logger.info("Updated remote URL to HTTPS: #{https_url}") + else + Logger.info("Remote URL does not use SSH. No changes made.") + end + + _ -> + Logger.info("No remote URL to update.") + end + end + + defp setup_github_authentication do + Logger.info("Setting up GitHub authentication...") + + if System.find_executable("gh") do + github_token = System.get_env("GITHUB_TOKEN") + + if github_token do + authenticate_github_cli(github_token) + else + Logger.error("No GITHUB_TOKEN found in the environment. Please provide one in the .env file.") + end + else + Logger.info("GitHub CLI not installed. Skipping authentication.") + end + end + + defp authenticate_github_cli(github_token) do + case System.cmd("gh", ["auth", "login", "--with-token"], env: [{"GITHUB_TOKEN", github_token}]) do + {_result, 0} -> Logger.info("Authenticated with GitHub CLI.") + _ -> Logger.error("GitHub CLI authentication failed.") + end + rescue + e -> Logger.error("Error during GitHub CLI authentication: #{inspect(e)}") + end +end diff --git a/mix.exs b/mix.exs index 8c7f5cf..72a5cef 100644 --- a/mix.exs +++ b/mix.exs @@ -12,6 +12,9 @@ defmodule ElixirKickoff.MixProject do docs: [ main: "readme", extras: ["README.md"] + ], + dialyzer: [ + plt_add_apps: [:mix] ] ] end