diff --git a/.devcontainer/bitwarden_common/docker-compose.yml b/.devcontainer/bitwarden_common/docker-compose.yml index 2f3a62877e..fbfca10d21 100644 --- a/.devcontainer/bitwarden_common/docker-compose.yml +++ b/.devcontainer/bitwarden_common/docker-compose.yml @@ -3,6 +3,9 @@ services: image: mcr.microsoft.com/devcontainers/dotnet:8.0 volumes: - ../../:/workspace:cached + env_file: + - path: ../../dev/.env + required: false # Overrides default command so things don't shut down after the process ends. command: sleep infinity diff --git a/.devcontainer/internal_dev/postCreateCommand.sh b/.devcontainer/internal_dev/postCreateCommand.sh index 071ffc0b29..cd8a007bcf 100755 --- a/.devcontainer/internal_dev/postCreateCommand.sh +++ b/.devcontainer/internal_dev/postCreateCommand.sh @@ -1,17 +1,42 @@ #!/usr/bin/env bash -export DEV_DIR=/workspace/dev +export REPO_ROOT="$(git rev-parse --show-toplevel)" export CONTAINER_CONFIG=/workspace/.devcontainer/internal_dev + git config --global --add safe.directory /workspace -get_installation_id_and_key() { - pushd ./dev >/dev/null || exit - echo "Please enter your installation id and key from https://bitwarden.com/host:" - read -r -p "Installation id: " INSTALLATION_ID - read -r -p "Installation key: " INSTALLATION_KEY - jq ".globalSettings.installation.id = \"$INSTALLATION_ID\" | - .globalSettings.installation.key = \"$INSTALLATION_KEY\"" \ - secrets.json.example >secrets.json # create/overwrite secrets.json - popd >/dev/null || exit +if [[ -z "${CODESPACES}" ]]; then + allow_interactive=1 +else + echo "Doing non-interactive setup" + allow_interactive=0 +fi + +get_option() { + # Helper function for reading the value of an environment variable + # primarily but then falling back to an interactive question if allowed + # and lastly falling back to a default value input when either other + # option is available. + name_of_var="$1" + question_text="$2" + default_value="$3" + is_secret="$4" + + if [[ -n "${!name_of_var}" ]]; then + # If the env variable they gave us has a value, then use that value + echo "${!name_of_var}" + elif [[ "$allow_interactive" == 1 ]]; then + # If we can be interactive, then use the text they gave us to request input + if [[ "$is_secret" == 1 ]]; then + read -r -s -p "$question_text" response + echo "$response" + else + read -r -p "$question_text" response + echo "$response" + fi + else + # If no environment variable and not interactive, then just give back default value + echo "$default_value" + fi } remove_comments() { @@ -26,51 +51,70 @@ remove_comments() { configure_other_vars() { pushd ./dev >/dev/null || exit - cp secrets.json .secrets.json.tmp + cp "$REPO_ROOT/dev/secrets.json" "$REPO_ROOT/dev/.secrets.json.tmp" # set DB_PASSWORD equal to .services.mssql.environment.MSSQL_SA_PASSWORD, accounting for quotes - DB_PASSWORD="$(grep -oP 'MSSQL_SA_PASSWORD=["'"'"']?\K[^"'"'"'\s]+' $DEV_DIR/.env)" + DB_PASSWORD="$(grep -oP 'MSSQL_SA_PASSWORD=["'"'"']?\K[^"'"'"'\s]+' $REPO_ROOT/dev/.env)" SQL_CONNECTION_STRING="Server=localhost;Database=vault_dev;User Id=SA;Password=$DB_PASSWORD;Encrypt=True;TrustServerCertificate=True" jq \ ".globalSettings.sqlServer.connectionString = \"$SQL_CONNECTION_STRING\" | .globalSettings.postgreSql.connectionString = \"Host=localhost;Username=postgres;Password=$DB_PASSWORD;Database=vault_dev;Include Error Detail=true\" | .globalSettings.mySql.connectionString = \"server=localhost;uid=root;pwd=$DB_PASSWORD;database=vault_dev\"" \ .secrets.json.tmp >secrets.json - rm .secrets.json.tmp + rm "$REPO_ROOT/dev/.secrets.json.tmp" popd >/dev/null || exit } one_time_setup() { - read -r -p \ - "Would you like to configure your secrets and certificates for the first time? -WARNING: This will overwrite any existing secrets.json and certificate files. -Proceed? [y/N] " response - if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then - echo "Running one-time setup script..." - sleep 1 - read -r -p \ - "Place the secrets.json and dev.pfx files from our shared Collection in the ./dev directory. + if [[ ! -f "$REPO_ROOT/dev/dev.pfx" ]]; then + # We do not have the cert file + if [[ ! -z "${DEV_CERT_CONTENTS}" ]]; then + # Make file for them + echo "Making $REPO_ROOT/dev/dev.pfx file for you based on DEV_CERT_CONTENTS environment variable." + # Assume content is base64 encoded + cat "$DEV_CERT_CONTENTS" | base64 -d > "$REPO_ROOT/dev/dev.pfx" + else + if [[ $allow_interactive -eq 1 ]]; then + read -r -p \ + "Place the dev.pfx files from our shared Collection in the $REPO_ROOT/dev directory. Press to continue." - remove_comments ./dev/secrets.json + fi + fi + fi + + if [[ -f "$REPO_ROOT/dev/dev.pfx" ]]; then + dotnet tool install dotnet-certificate-tool -g >/dev/null + cert_password="$(get_option "DEV_CERT_PASSWORD" "Paste the \"Licensing Certificate - Dev\" password: " "" 1)" + certificate-tool add --file "$REPO_ROOT/dev/dev.pfx" --password "$cert_password" + else + echo "You don't have a $REPO_ROOT/dev/dev.pfx file setup." >/dev/stderr + fi + + do_secrets_json_setup="$(get_option "SETUP_SECRETS_JSON" "Would you like us to setup your secrets.json file for you? [y/N] " "n")" + if [[ "$do_secrets_json_setup" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + remove_comments "$REPO_ROOT/dev/secrets.json" configure_other_vars + # setup_secrets needs to be ran from the dev folder + pushd "$REPO_ROOT/dev" >/dev/null || exit + echo "Injecting dotnet secrets..." + pwsh "$REPO_ROOT/dev/setup_secrets.ps1" || true + popd >/dev/null || exit + fi + + do_azurite_setup="$(get_option "SETUP_AZURITE" "Would you like us to setup your azurite environment? [y/N] " "n")" + if [[ "$do_azurite_setup" =~ ^([yY][eE][sS]|[yY])+$ ]]; then echo "Installing Az module. This will take ~a minute..." pwsh -Command "Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force" - pwsh ./dev/setup_azurite.ps1 - - dotnet tool install dotnet-certificate-tool -g >/dev/null - - read -r -s -p "Paste the \"Licensing Certificate - Dev\" password: " CERT_PASSWORD - echo - pushd ./dev >/dev/null || exit - certificate-tool add --file ./dev.pfx --password "$CERT_PASSWORD" - echo "Injecting dotnet secrets..." - pwsh ./setup_secrets.ps1 || true - popd >/dev/null || exit + pwsh "$REPO_ROOT/dev/setup_azurite.ps1" + fi + run_mssql_migrations="$(get_option "RUN_MSSQL_MIGRATIONS" "Would you like us to run MSSQL Migrations for you? [y/N] " "n")" + if [[ "$do_azurite_setup" =~ ^([yY][eE][sS]|[yY])+$ ]]; then echo "Running migrations..." sleep 5 # wait for DB container to start - dotnet run --project ./util/MsSqlMigratorUtility "$SQL_CONNECTION_STRING" + dotnet run --project "$REPO_ROOT/util/MsSqlMigratorUtility" "$SQL_CONNECTION_STRING" fi - read -r -p "Would you like to install the Stripe CLI? [y/N] " stripe_response + + stripe_response="$(get_option "INSTALL_STRIPE_CLI" "Would you like to install the Stripe CLI? [y/N] " "n")" if [[ "$stripe_response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then install_stripe_cli fi @@ -88,11 +132,4 @@ install_stripe_cli() { sudo apt install -y stripe } -# main -if [[ -z "${CODESPACES}" ]]; then - one_time_setup -else - # Ignore interactive elements when running in codespaces since they are not supported there - # TODO Write codespaces specific instructions and link here - echo "Running in codespaces, follow instructions here: https://contributing.bitwarden.com/getting-started/server/guide/ to continue the setup" -fi \ No newline at end of file +one_time_setup diff --git a/dev/.env.example b/dev/.env.example index 7f049728d7..f31b5b9eeb 100644 --- a/dev/.env.example +++ b/dev/.env.example @@ -26,3 +26,12 @@ IDENTITY_PROXY_PORT=33756 # Optional RabbitMQ configuration RABBITMQ_DEFAULT_USER=bitwarden RABBITMQ_DEFAULT_PASS=SET_A_PASSWORD_HERE_123 + +# Environment variables that help customize dev container start +# Without these the dev container will ask these questions in an interactive manner +# when possible (excluding running in GitHub Codespaces) +# SETUP_SECRETS_JSON=yes +# SETUP_AZURITE=yes +# RUN_MSSQL_MIGRATIONS=yes +# DEV_CERT_PASSWORD=dev_cert_password_here +# INSTALL_STRIPE_CLI=no