From ffb16cc9843971dfecb29e3f36f3f4f86fb061cf Mon Sep 17 00:00:00 2001 From: Nuru Date: Fri, 31 Jan 2025 12:38:04 -0800 Subject: [PATCH] Geodesic v4 (#961) * Breaking changes * Major new features --- .coderabbit.yaml | 111 ++++ .editorconfig | 8 +- Dockerfile.custom | 18 +- Dockerfile.options | 94 +-- Makefile | 13 +- README.md | 47 +- README.yaml | 45 +- ReleaseNotes-v4.md | 685 ++++++++++++++++++++ docs/ReleaseNotes-v4.md | 1 + docs/about.md | 9 +- docs/chudo.md | 1 - docs/chundo.md | 1 - docs/customization.md | 224 ++++--- docs/environment.md | 119 ++++ docs/envrc.md | 42 -- docs/kops.md | 559 ---------------- docs/kudo.md | 23 - docs/kundo.md | 1 - docs/wrapper.md | 30 + os/alpine/Dockerfile.alpine | 2 +- os/debian/Dockerfile.debian | 22 +- os/debian/packages-debian.txt | 3 +- rootfs/conf/.emacs | 1 - rootfs/conf/.inputrc | 1 - rootfs/conf/.kube/config | 2 - rootfs/etc/init.d/atlantis.sh | 2 +- rootfs/etc/kubeconfig | 3 + rootfs/etc/motd | 11 - rootfs/etc/motd.sh | 15 + rootfs/etc/profile.d/_01-launch-warning.sh | 63 ++ rootfs/etc/profile.d/_07-term-mode.sh | 85 ++- rootfs/etc/profile.d/_10-colors.sh | 72 +- rootfs/etc/profile.d/_20-localhost.sh | 31 - rootfs/etc/profile.d/_20-mounts.sh | 206 ++++++ rootfs/etc/profile.d/_30-geodesic-config.sh | 8 +- rootfs/etc/profile.d/_40-preferences.sh | 83 +-- rootfs/etc/profile.d/_50-workdir.sh | 73 --- rootfs/etc/profile.d/_50-workspace.sh | 30 + rootfs/etc/profile.d/atmos.sh | 18 +- rootfs/etc/profile.d/aws.sh | 80 ++- rootfs/etc/profile.d/banner.sh | 19 +- rootfs/etc/profile.d/motd.sh | 6 +- rootfs/etc/profile.d/oathtool.sh | 11 - rootfs/etc/profile.d/ssh-agent.sh | 31 - rootfs/etc/profile.d/terraform.sh | 2 +- rootfs/templates/bootstrap | 2 +- rootfs/templates/wrapper | 360 ---------- rootfs/templates/wrapper-body.sh | 594 +++++++++++++++++ rootfs/templates/wrapper-header.sh.tmpl | 24 + rootfs/usr/local/bin/boot | 22 +- rootfs/usr/local/bin/conf-directory | 20 - rootfs/usr/local/bin/wrapper | 3 +- rootfs/usr/local/sbin/shell-monitor | 48 ++ 53 files changed, 2454 insertions(+), 1530 deletions(-) create mode 100644 .coderabbit.yaml create mode 100644 ReleaseNotes-v4.md create mode 100644 docs/ReleaseNotes-v4.md delete mode 120000 docs/chudo.md delete mode 120000 docs/chundo.md create mode 100644 docs/environment.md delete mode 100644 docs/envrc.md delete mode 100644 docs/kops.md delete mode 100644 docs/kudo.md delete mode 120000 docs/kundo.md create mode 100644 docs/wrapper.md delete mode 120000 rootfs/conf/.emacs delete mode 120000 rootfs/conf/.inputrc delete mode 100644 rootfs/conf/.kube/config create mode 100644 rootfs/etc/kubeconfig delete mode 100644 rootfs/etc/motd create mode 100644 rootfs/etc/motd.sh create mode 100644 rootfs/etc/profile.d/_01-launch-warning.sh delete mode 100644 rootfs/etc/profile.d/_20-localhost.sh create mode 100644 rootfs/etc/profile.d/_20-mounts.sh delete mode 100644 rootfs/etc/profile.d/_50-workdir.sh create mode 100644 rootfs/etc/profile.d/_50-workspace.sh delete mode 100755 rootfs/etc/profile.d/oathtool.sh delete mode 100755 rootfs/etc/profile.d/ssh-agent.sh delete mode 100755 rootfs/templates/wrapper create mode 100755 rootfs/templates/wrapper-body.sh create mode 100644 rootfs/templates/wrapper-header.sh.tmpl delete mode 100755 rootfs/usr/local/bin/conf-directory create mode 100755 rootfs/usr/local/sbin/shell-monitor diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 000000000..48b806773 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,111 @@ +language: en-US +tone_instructions: '' +early_access: true +enable_free_tier: true +reviews: + profile: chill + request_changes_workflow: false + high_level_summary: true + high_level_summary_placeholder: '@coderabbitai summary' + high_level_summary_in_walkthrough: false + auto_title_placeholder: '@coderabbitai' + auto_title_instructions: '' + review_status: true + commit_status: true + fail_commit_status: false + collapse_walkthrough: true + changed_files_summary: true + sequence_diagrams: false + assess_linked_issues: true + related_issues: true + related_prs: true + suggested_labels: false + auto_apply_labels: false + suggested_reviewers: false + poem: false + labeling_instructions: [] + path_filters: [] + path_instructions: [] + abort_on_close: true + auto_review: + enabled: true + auto_incremental_review: true + ignore_title_keywords: [] + labels: [] + drafts: false + base_branches: [] + finishing_touches: + docstrings: + enabled: true + tools: + shellcheck: + enabled: true + ruff: # for Python + enabled: false + markdownlint: + enabled: true + github-checks: + enabled: true + timeout_ms: 90000 + languagetool: + enabled: true + enabled_only: false + level: default + biome: # For JavaScript/TypeScript + enabled: false + hadolint: + enabled: true + swiftlint: # For Swift + enabled: false + phpstan: # For PHP + enabled: false + level: default + golangci-lint: # For Go + enabled: false + yamllint: + enabled: true + gitleaks: + enabled: true + checkov: + enabled: true + detekt: # For Kotlin + enabled: false + eslint: # For JavaScript/TypeScript + enabled: false + rubocop: # For Ruby + enabled: false + buf: # For Protobuf + enabled: false + regal: # For Rego + enabled: false + actionlint: + enabled: true + pmd: # For Java + enabled: false + cppcheck: # For C/C++ + enabled: false + semgrep: # Static analysis. CodeRabbit recommends disabling this tool unless you configure specific rules for it. + enabled: false + circleci: # For CircleCI + enabled: false +chat: + auto_reply: true + integrations: + jira: + usage: disabled + linear: + usage: disabled +knowledge_base: + opt_out: false + learnings: + scope: auto + issues: + scope: auto + jira: + usage: disabled + project_keys: [] + linear: + usage: disabled + team_keys: [] + pull_requests: + scope: auto diff --git a/.editorconfig b/.editorconfig index 02ba238bc..43c3ab6fc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,12 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file [*] +end_of_line = lf insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 # Override for Makefile [{Makefile, makefile, GNUmakefile}] @@ -13,7 +20,6 @@ indent_size = 4 [*.yaml] indent_style = space indent_size = 2 -trim_trailing_whitespace = true [*.sh] indent_style = tab diff --git a/Dockerfile.custom b/Dockerfile.custom index 9b1ffe97a..1ed32d1d5 100644 --- a/Dockerfile.custom +++ b/Dockerfile.custom @@ -17,7 +17,7 @@ # We always recommend pinning versions where changes are likely to break things. # We put the versions up top here so they are easy to find and update. # Find the latest version at https://github.com/cloudposse/geodesic/releases -ARG VERSION=2.11.0 +ARG VERSION=4.0.0 # Changing base OS for Geodesic is possible by changing this arg, but # unfortunately, the package managers are different, so it is not that simple. ARG OS=debian @@ -45,25 +45,13 @@ ENV AWS_REGION_ABBREVIATION_TYPE=fixed ENV AWS_DEFAULT_REGION=us-west-2 ENV AWS_DEFAULT_SHORT_REGION=uw2 -# Install specific versions of Terraform. -# We patch specific patch versions because Terraform will not operate -# on Terraform "states" that have been touched by later versions. -ARG TF_014_VERSION=0.14.10 -ARG TF_015_VERSION=0.15.4 -ARG TF_1_VERSION=1.0.4 -RUN apt-get update && apt-get install -y -u \ - terraform-0.14="${TF_014_VERSION}-*" terraform-0.15="${TF_015_VERSION}-*" \ - terraform-1="${TF_1_VERSION}-*" -# Set Terraform 0.14.x as the default `terraform`. You can still use -# version 0.15.x by calling `terraform-0.15` or version 1.x as terraform-1 -RUN update-alternatives --set terraform /usr/share/terraform/0.14/bin/terraform # Pin kubectl minor version (must be within 1 minor version of cluster version) # Note, however, that due to Docker layer caching and the structure of this # particular Dockerfile, the patch version will not automatically update # until you change the minor version or change the base Geodesic version. # If you want, you can pin the patch level so you can update it when desired. -ARG KUBECTL_VERSION=1.20 +ARG KUBECTL_VERSION=1.30 RUN apt-get update && apt-get install kubectl-${KUBECTL_VERSION} # Install Atmos CLI (https://github.com/cloudposse/atmos) @@ -71,4 +59,4 @@ RUN apt-get install atmos COPY rootfs/ / -WORKDIR / +WORKDIR /workspace diff --git a/Dockerfile.options b/Dockerfile.options index a838d6bb6..2249f7c6c 100644 --- a/Dockerfile.options +++ b/Dockerfile.options @@ -31,7 +31,7 @@ ENV MAKE_INCLUDES="Makefile Makefile.*" # This is separate so that updating it does not invalidate the Docker cache layer with all the packages installed above # https://cloud.google.com/sdk/docs/release-notes ARG GOOGLE_CLOUD_CLI_VERSION -ENV CLOUDSDK_CONFIG=/localhost/.config/gcloud/ +ENV CLOUDSDK_CONFIG=/etc/xdg_config_home/.config/gcloud/ RUN apt-get update && apt-get install -y google-cloud-cli=${GOOGLE_CLOUD_CLI_VERSION}-\* @@ -43,95 +43,3 @@ RUN { gcloud config set core/disable_usage_reporting true --installation && \ -#################################################################################### -# kops support -# If you are using Cloud Posse's kops reference architecture, you con configure it -# in your Dockerfile like this (edit as desired) - -ENV KOPS_CLUSTER_NAME=example.foo.bar - -ENV KOPS_MANIFEST=/conf/kops/manifest.yaml -ENV KOPS_TEMPLATE=/templates/kops/default.yaml -ENV KOPS_STATE_STORE s3://undefined -ENV KOPS_STATE_STORE_REGION us-east-1 -ENV KOPS_FEATURE_FLAGS=+DrainAndValidateRollingUpdate - -ENV KOPS_BASTION_PUBLIC_NAME="bastion" - -ENV KUBECONFIG=/dev/shm/kubecfg -ENV KUBECONFIG_TEMPLATE=/templates/kops/kubecfg.yaml - -RUN /usr/bin/kops completion bash > /etc/bash_completion.d/kops.sh - -# Instance sizes for kops bastion, master nodes, and worker nodes -ENV BASTION_MACHINE_TYPE "t3.small" -ENV MASTER_MACHINE_TYPE "t3.medium" -ENV NODE_MACHINE_TYPE "t3.medium" - -# Min/Max number of nodes (aka workers) per region -ENV NODE_MAX_SIZE 2 -ENV NODE_MIN_SIZE 2 - -# end of kops support section -#################################################################################### - -#### ALPINE ONLY #### -# Alpine does not include the very common `glibc` GNU C Standard Library, which -# causes compatibility problems. Among other things, AWS CLI v2 does not work -# out of the box with Alpine. The following recipe installs `glibc` , and has to be run -# before installing other packages, particularly `libc6-compat`, and then, -# because it conflicts, you have to tweak a bit and then install `libc6-compat`. -# So put this in Dockerfile.alpine after setting up the package repositories -# but before installing any packages https://github.com/cloudposse/geodesic/blob/91336bf56fb7ff0d9812e01ceacc40ca59a17cce/os/alpine/Dockerfile.alpine#L81 -# (Not verified) - -# Install glibc and glibc-bin and the C.UTF-8 locale -ENV LANG=C.UTF-8 -ARG ALPINE_GLIBC_PACKAGE_VERSION=2.33-r0 -RUN apk update && apk add -u curl && \ - ALPINE_GLIBC_PACKAGE_VERSION="${ALPINE_GLIBC_PACKAGE_VERSION}" && \ - curl -sSLO https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${ALPINE_GLIBC_PACKAGE_VERSION}/glibc-${ALPINE_GLIBC_PACKAGE_VERSION}.apk && - apk add --allow-untrusted glibc-${ALPINE_GLIBC_PACKAGE_VERSION}.apk && rm glibc-${ALPINE_GLIBC_PACKAGE_VERSION}.apk && \ - curl -sSLO https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${ALPINE_GLIBC_PACKAGE_VERSION}/glibc-bin-${ALPINE_GLIBC_PACKAGE_VERSION}.apk && - apk add --allow-untrusted glibc-bin-${ALPINE_GLIBC_PACKAGE_VERSION}.apk && rm glibc-bin-${ALPINE_GLIBC_PACKAGE_VERSION}.apk && \ - curl -sSLO https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${ALPINE_GLIBC_PACKAGE_VERSION}/glibc-i18n-${ALPINE_GLIBC_PACKAGE_VERSION}.apk && - apk add --allow-untrusted glibc-i18n-${ALPINE_GLIBC_PACKAGE_VERSION}.apk && rm glibc-i18n-${ALPINE_GLIBC_PACKAGE_VERSION}.apk && \ - /usr/glibc-compat/bin/localedef --force --inputfile POSIX --charmap UTF-8 "$LANG" || true && \ - printf "export LANG=%s\n" "$LANG" > /etc/profile.d/locale.sh && \ - apk del glibc-i18n && \ - rm -f /usr/glibc-compat/lib/ld-linux-x86-64.so.2 && \ - /usr/glibc-compat/sbin/ldconfig - - -# Remove conflicting link, install libc6-compat, restore link to glibc -RUN mv /lib64/ld-linux-x86-64.so.2 /lib64/glibc-ld-linux-x86-64.so.2 && \ - apk add --force-overwrite libc6-compat && \ - rm -f /lib64/ld-linux-x86-64.so.2 && \ - mv /lib64/glibc-ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2 && \ - /usr/glibc-compat/sbin/ldconfig - - -# Now you can install packages -# https://github.com/cloudposse/geodesic/blob/91336bf56fb7ff0d9812e01ceacc40ca59a17cce/os/alpine/Dockerfile.alpine#L81-L88 - - -# Now you can move AWS CLI v1 aside, keep it as an alternative, and install AWS CLI v2 - -# Move AWS CLI v1 to aws1 and set up alternatives -RUN mv /usr/bin/aws /usr/local/bin/aws1 && \ - update-alternatives --install /usr/local/bin/aws aws /usr/local/bin/aws1 1 - - -# Install AWS CLI 2 -# Get version from https://github.com/aws/aws-cli/blob/v2/CHANGELOG.rst -# We cannot automatically track the release versions, so we just install the latest -# ARG AWS_CLI_VERSION=2.1.34 -RUN AWSTMPDIR=$(mktemp -d -t aws-inst-XXXXXXXXXX) && \ - curl -sSsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64${AWS_CLI_VERSION:+-${AWS_CLI_VERSION}}.zip" -o "$AWSTMPDIR/awscliv2.zip" && \ - cd $AWSTMPDIR && \ - unzip -qq awscliv2.zip && \ - ./aws/install -i /usr/share/aws/v2 -b /usr/share/aws/v2/bin && \ - update-alternatives --install /usr/local/bin/aws aws /usr/share/aws/v2/bin/aws 2 && \ - rm -rf $AWSTMPDIR - - diff --git a/Makefile b/Makefile index 654c777cb..7f6716d0d 100644 --- a/Makefile +++ b/Makefile @@ -47,17 +47,22 @@ build: $(DOCKER_BASE_OS).build install: $(DOCKER_BASE_OS).install run: - @geodesic + @$(APP_NAME) + +run/solo: + @$(APP_NAME) --solo %.run: %.build %.install - @geodesic + @$(APP_NAME) run/check: @if [[ -n "$$(docker ps --format '{{ .Names }}' --filter name="^/$(APP_NAME)\$$")" ]]; then \ printf "**************************************************************************\n" ; \ printf "Not launching new container because old container is still running.\n"; \ - printf "Exit all running container shells gracefully or kill the container with\n\n"; \ - printf " docker kill %s\n\n" "$(APP_NAME)" ; \ + printf "Exit all running container shells gracefully or quit the container with\n\n"; \ + printf " %s stop\n\n" "$(APP_NAME)" ; \ + printf "Then, all new shells will be running in the same new container.\n\n" ; \ + printf "Alternately, run \`make run/solo\` or \`$(APP_NAME) --solo\` to start a new container.\n" ; \ printf "**************************************************************************\n" ; \ exit 9 ; \ fi diff --git a/README.md b/README.md index c04540bd1..7638866b8 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,20 @@ We recommend starting by using `geodesic` as a Docker base image (e.g. `FROM clo > Starting with Geodesic 2.0, we distributed Geodesic as a multi-platform (`linux/amd64`, `linux/arm64`) Debian-based Docker image and a single-platform (`linux/amd64`) Alpine-based image. > We moved the `cloudposse/geodesic:latest` Docker image tag from the Alpine version to the Debian version at that time. + +### What’s Changed in Geodesic 4.0 + +Geodesic 4.0 is a major release that brings many new features and improvements. The most notable changes are: + +- The first launched shell is no longer special. All shells are now equal, and you can quit them in any order. + The geodesic container remains running until the last shell exits. +- The `geodesic` command now has a `--solo` option that allows you to launch a new Geodesic container for just that one shell. +- Geodesic no longer mounts the host user's entire home directory into the container. Instead, it mounts only selected directories. +- The `geodesic stop` command has been enhanced to shut down the Geodesic container gracefully, rather than forcefully, allowing, + among other things, shell scripts to run their exit handlers. + +See extensive release notes for Geodesic 4.0 [here](ReleaseNotes-v4.md). + ### What’s Changed in Geodesic 3.0 Rather than bringing new features, Geodesic 3.0 is focused on slimming down the Docker image and removing outdated tools. @@ -136,9 +150,9 @@ The `latest` tag points to the latest Debian-based image, although we recommend ### Quickstart -#### docker run +#### Installing Geodesic -Launching Gedoesic is a bit complex, so we recommend you install a launch script by running +Launching Geodesic is a bit complex, so we recommend you install a launch script by running ``` docker run --rm cloudposse/geodesic:latest-debian init | bash ``` @@ -147,6 +161,20 @@ After that, you should be able to launch Geodesic just by typing geodesic ``` +Alternately, customize the Makefile as described below and use `make install` to build your custom image +and install the launch script. + +#### Running Geodesic + +Geodesic has only a few commands and command-line options. The most important command is `geodesic`, which launches the Geodesic shell. +The only other command you might normally use is `geodesic stop`, which stops the Geodesic container, but +Geodesic automatically quits (and removes the Docker container) when you exit the last shell, so you should rarely need to use `geodesic stop`. + +Run `geodesic help` for a list of command-line options. + +See [customization](/docs/customization.md) documentation for information on how to customize your Geodesic environment. +Geodesic has many customization options, but they are most commonly set in configuration files, not on the command line. + ### Customizing your Docker image In general we recommend creating a customized version of Geodesic by creating your own `Dockerfile` starting with @@ -154,7 +182,7 @@ In general we recommend creating a customized version of Geodesic by creating yo # We always recommend pinning versions to avoid surprises and breaking changes. # We put the version up top here so it is easy to find and update. # Find the latest version at https://github.com/cloudposse/geodesic/releases -ARG VERSION=3.0.0 +ARG VERSION=4.0.0 # If you don't want to bothered with updating the version, you can use `latest` instead, # but keep in mind that as long as you have a local image with the `latest` tag, # it will not be updated by `docker run`. You will have to explicitly pull the latest image. @@ -170,6 +198,17 @@ ENV BANNER="my-custom-geodesic" You can see some example configuration options to include in [Dockerfile.options](./Dockerfile.options). +#### Makefile customizations + +We also recommend creating a `Makefile` to simplify building and running your custom image. +You can use the [Makefile](/Makefile) in this repository with minimal modifications. + +- Update `DOCKER_ORG` and `DOCKER_IMAGE` to match your Docker Hub username and the name of your custom image. +- Update `DOCKER_FILE` to match the path to your custom `Dockerfile`. +- Update `APP_NAME` to give the command to launch your custom image a custom name. + +Then you can build your custom image with `make build` and run it with `make run`. + #### Multi-platform gotchas Although the Geodesic base image is provided in 2 architectures, when you do a local build @@ -324,7 +363,7 @@ All other trademarks referenced herein are the property of their respective owne --- -Copyright © 2017-2024 [Cloud Posse, LLC](https://cpco.io/copyright) +Copyright © 2017-2025 [Cloud Posse, LLC](https://cpco.io/copyright) README footer diff --git a/README.yaml b/README.yaml index eab3d8d31..a0aa8bb61 100644 --- a/README.yaml +++ b/README.yaml @@ -80,6 +80,20 @@ introduction: |- > Starting with Geodesic 2.0, we distributed Geodesic as a multi-platform (`linux/amd64`, `linux/arm64`) Debian-based Docker image and a single-platform (`linux/amd64`) Alpine-based image. > We moved the `cloudposse/geodesic:latest` Docker image tag from the Alpine version to the Debian version at that time. + + ### What’s Changed in Geodesic 4.0 + + Geodesic 4.0 is a major release that brings many new features and improvements. The most notable changes are: + + - The first launched shell is no longer special. All shells are now equal, and you can quit them in any order. + The geodesic container remains running until the last shell exits. + - The `geodesic` command now has a `--solo` option that allows you to launch a new Geodesic container for just that one shell. + - Geodesic no longer mounts the host user's entire home directory into the container. Instead, it mounts only selected directories. + - The `geodesic stop` command has been enhanced to shut down the Geodesic container gracefully, rather than forcefully, allowing, + among other things, shell scripts to run their exit handlers. + + See extensive release notes for Geodesic 4.0 [here](ReleaseNotes-v4.md). + ### What’s Changed in Geodesic 3.0 Rather than bringing new features, Geodesic 3.0 is focused on slimming down the Docker image and removing outdated tools. @@ -137,9 +151,9 @@ introduction: |- usage: |- ### Quickstart - #### docker run + #### Installing Geodesic - Launching Gedoesic is a bit complex, so we recommend you install a launch script by running + Launching Geodesic is a bit complex, so we recommend you install a launch script by running ``` docker run --rm cloudposse/geodesic:latest-debian init | bash ``` @@ -148,6 +162,20 @@ usage: |- geodesic ``` + Alternately, customize the Makefile as described below and use `make install` to build your custom image + and install the launch script. + + #### Running Geodesic + + Geodesic has only a few commands and command-line options. The most important command is `geodesic`, which launches the Geodesic shell. + The only other command you might normally use is `geodesic stop`, which stops the Geodesic container, but + Geodesic automatically quits (and removes the Docker container) when you exit the last shell, so you should rarely need to use `geodesic stop`. + + Run `geodesic help` for a list of command-line options. + + See [customization](/docs/customization.md) documentation for information on how to customize your Geodesic environment. + Geodesic has many customization options, but they are most commonly set in configuration files, not on the command line. + ### Customizing your Docker image In general we recommend creating a customized version of Geodesic by creating your own `Dockerfile` starting with @@ -155,7 +183,7 @@ usage: |- # We always recommend pinning versions to avoid surprises and breaking changes. # We put the version up top here so it is easy to find and update. # Find the latest version at https://github.com/cloudposse/geodesic/releases - ARG VERSION=3.0.0 + ARG VERSION=4.0.0 # If you don't want to bothered with updating the version, you can use `latest` instead, # but keep in mind that as long as you have a local image with the `latest` tag, # it will not be updated by `docker run`. You will have to explicitly pull the latest image. @@ -171,6 +199,17 @@ usage: |- You can see some example configuration options to include in [Dockerfile.options](./Dockerfile.options). + #### Makefile customizations + + We also recommend creating a `Makefile` to simplify building and running your custom image. + You can use the [Makefile](/Makefile) in this repository with minimal modifications. + + - Update `DOCKER_ORG` and `DOCKER_IMAGE` to match your Docker Hub username and the name of your custom image. + - Update `DOCKER_FILE` to match the path to your custom `Dockerfile`. + - Update `APP_NAME` to give the command to launch your custom image a custom name. + + Then you can build your custom image with `make build` and run it with `make run`. + #### Multi-platform gotchas Although the Geodesic base image is provided in 2 architectures, when you do a local build diff --git a/ReleaseNotes-v4.md b/ReleaseNotes-v4.md new file mode 100644 index 000000000..b504606d6 --- /dev/null +++ b/ReleaseNotes-v4.md @@ -0,0 +1,685 @@ +# Geodesic v4.0.0 Release Notes + +### Highlights + +#### Better Shell Management + +##### Equal Treatment for Multiple Shells in a Single Container + +A much requested feature, Geodesic no longer exits the container when the first shell exits. +Instead, the container runs until all shells have exited. This means you can now run multiple shells +inside the container and exit them in any order; you no longer have to keep track of which +shell was the first one launched. Unfortunately, this also means that you can no longer +detach and reattach to a shell. + +A side benefit of this is that previously, if you had something like `trap handler EXIT` in your +top-level shell, there was a good chance the handler would not run because the shell will +be killed (SIGKILL, `kill -9`) rather than shut down cleanly. Now, there is a much greater +likelihood that the shells will shut down in an orderly manner and run their exit hooks. + +##### New Capability for Multiple Shells with One Container per Shell + +However, Geodesic now supports another much-requested feature: launching a new container +each time you run Geodesic. This is done by setting the `ONE_SHELL` environment variable to "true" +or passing `--solo` on the command line. This allows you to run multiple versions of Geodesic, +and also allows you to detach from a shell and reattach to it later. + +##### External Command to Stop Geodesic + +Not a new feature, but one that many people were not aware of: you can kill the running +Geodesic container with the command `geodesic stop`. This will stop the container, and it +will be automatically removed (assuming you started it with `geodesic`). Now, however, +there is the possibility that you will have several running containers. If this is the case, +`geodesic stop` will list the running containers by name. You can then pass the +name as an argument to `geodesic stop` and it will stop that one. + +##### Cleanup Commands on Shell Exit and Container Exit + +Another old feature few people knew about: you can have Geodesic automatically +run a command when a shell exits. This was done by creating an executable command named +`geodesic_on_exit` and putting it in your `$PATH`. This feature has been enhanced +in two ways: + +1. Now you can set the name of the command to run when the shell exits via `ON_SHELL_EXIT` + (defaults to `geodesic_on_exit`). Also new: the `ON_SHELL_EXIT` command will have available + to it the short ID and name of the container in which it was running, via the + environment variables `GEODESIC_EXITING_CONTAINER_ID` and `GEODESIC_EXITING_CONTAINER_NAME`, + respectively. +2. You can use the new environment variable `ON_CONTAINER_EXIT` to configure a different + command to run only when the container exits. It will also have the container ID and name + available to it via the same environment variables. + +Be aware that the commands are called on a best-effort basis when the Geodesic +launch wrapper exits. If you detach from a shell, the wrapper will run then and +call `ON_SHELL_EXIT`. If you reattach to the shell, the wrapper is not involved, +so quitting the shell or container will not run the cleanup command. + +Alternately, if you quit two shells at nearly the same time, for example by +running `geodesic stop`, the `ON_CONTAINER_EXIT` command may be called twice. +This is because the wrapper calls the command when the container has stopped +before shell exit processing has finished, and both shells fit the criterion. + +Now that shells normally exit cleanly (provided you do not +run `docker kill geodesic`), you may find that you get more reliable behavior +out of: + +```bash +trap exit_handler EXIT +``` + +to run on each shell completion. + +#### Better Configuration Management + +Geodesic now supports configuration files for customizing the launch of the Geodesic container. +Although Geodesic has for a while been [customizable](https://github.com/cloudposse/geodesic/blob/main/docs/customization.md), +the customization you could configure via files were limited to customizations of the +running Docker container. Previously, customizations regarding how Geodesic is launched were difficult to manage. +Now, you can create a `launch-options.sh` file in the Geodesic configuration directories +to customize the launch of the Geodesic container. The directory search path and the +priority of the files are the same as for the other Geodesic customization files. + +Note that most of the launch options configure the launching of the Docker container, +and therefore have no effect when you run `geodesic` to start a new shell +inside an already running container. However, you can use the `--solo` option to +force Geodesic to start a new container to pick up the new launch options. You +can also add `ONE_SHELL=true` to the `launch-options.sh` file to force Geodesic to +start a new container each time you run it. + +#### Better File System Layout + +Geodesic no longer mounts the entire host user's home directory into the container. +This had been a performance problem and was explicitly discouraged by Docker. Now, +Geodesic mounts only specific directories from the host to the container, and you +have full control over which directories are mounted where (with sensible defaults). + +Inspired by the [Dev Container](https://containers.dev/), the `/localhost` directory +has been removed, and the host's git root directory is mounted to `/workspace` in the container. +This is all configurable, and some configuration for each project will make it easier to +use multiple projects with Geodesic. + +Among other things, this means that your project source directory no longer has to be +under your home directory. You are free to locate it on another drive if you like. + +For reasons lost to history, Geodesic set the container user's home directory to `/conf`. +This caused some problems for people who wanted to run Geodesic as a non-root user. +The `/conf` directory has been removed, and the container user's home directory +(as specified in `/etc/passwd`) is now honored. By default, Geodesic launches as the +`root` user, so the default `$HOME` is `/root`. + +As before, the host user's home directory path is available in the container as +`$LOCAL_HOME`, and mounted files and directories are available at the same paths +in the container as on the host. + +## Breaking Changes + +- Previously, `$HOME` was set to `/conf` in the container. This is no longer the case. + `$HOME` is now set to the shell user's home directory. By default, this is `/root`. + If you launch a shell in Geodesic as a non-root user, `$HOME` will be set to that user's home directory, + provided you have properly created the user with `adduser`. **By default, the + container user will share configuration with the host user by mounting selected host user's + configuration directories into the container user's home directory, allowing + bidirectional updates.** + +> [!WARNING] +> It is important to be aware that any files from the host directory that are mounted into the container +> will be modifiable by the container user, and those modifications will propagate to the host. +> This includes any files that are implicitly modified by programs running in the container. +> If you need separate configurations for the host and container, you should not use +> `HOMEDIR_MOUNTS` or `HOMEDIR_ADDITIONAL_MOUNTS` to directly mount them into the container's +> home directory. Instead, you use `HOST_MOUNTS` to mount them elsewhere in the container, +> and then use a preferences or override script to copy the files to the appropriate location +> and then modify them if needed. See [docs/customization.md](docs/customization.md) and +> [docs/environment.md](docs/environment.md) for more details. + +- Global Git configuration can be in either of 2 places: + - `~/.config/git/config` - Starting with `git` v2.41 or so, it is the newly preferred location for the global Git configuration. + - `~/.gitconfig` - This is the old location for the global Git configuration. + + If you have a `~/.gitconfig` file, it will not be accessible to the container by default. + This may be advantageous if you want to keep your global Git configuration separate from the container. + If you have a `~/.config/git/config` file, it will be accessible to the container by default, + and it will be modified if you run `git config --global` from within Geodesic, + provided you are using a recent enough version of `git`. The version of `git` + that ships with Debian 12 and Geodesic 4.0.0 is 2.39.5 and does not yet + appear to support `~/.config/git/config`, but this could change without notice. + +- The `/conf` directory no longer exists. Generally, what used to be in `/conf` + is now in `/root` if it was created in the Geodesic Dockerfile.debian, or in + `$HOME` (also `/root` by default) if it was created in the Geodesic startup scripts. +- The `conf-directory` command has been removed. It was part of the old `helmfile` support + but likely had been broken for some time. If you were using it, you will need to + copy the old file and update it to reflect that `/conf/` no longer exists. +- Support for [Atlantis](https://github.com/runatlantis/atlantis) has not been removed, but + it is also not being actively maintained or tested, so this release may have issues with Atlantis. + Among other things, the Atlantis configuration directory previously defaulted to `/conf/atlantis/`, which no longer exists. + The default has been changed to `/home/atlantis`, but that directory also does not exist by default. + You should create it along with creating the `atlantis` user in your Dockerfile. +- `/conf/.kube/config` has been moved to `/etc/kubeconfig`. It is installed as + `/root/.kube/config`, but this is now expected to be hidden by mounting the + host user's `$HOME/.kube` directory over `/root/.kube`. + +
Special notes for Spacelift Users (click to reveal) + +If you are using Geodesic with Spacelift, you may need to make some changes to your Dockerfile. +The `spacelift` user is required by Spacelift if you want to run this image as a Spacelift runner image. +If you previously were installing `/rootfs/conf/.kube/config` somewhere like `$HOME/.kube/`, +you should now be installing `/rootfs/etc/kubeconfig` instead. + +```dockerfile +# `spacelift` user with uid=1983 is required by Spacelift if we want to run this image as Spacelift runner image +# https://docs.spacelift.io/concepts/worker-pools#custom-runner-images +# https://docs.spacelift.io/integrations/docker#customizing-the-runner-image +RUN adduser --disabled-password --home /home/spacelift --no-create-home --uid 1983 --gecos "" spacelift +# Spacelift running under EKS needs to be able to dynamically update its AWS configuration. +# This includes creating temp files under /etc/aws-config/ and modifying aws-config-spacelift. +# We create /home/spacelift separately rather than via adduser to avoid getting default files like .bashrc installed. +# NOTE: The following assumes that Spacelift is getting its AWS configuration from /etc/aws-config/aws-config-spacelift +# which must exist or this line will fail. If you are storing its configuration elsewhere, you will need to adjust this line. +RUN mkdir /home/spacelift && \ + chown spacelift:spacelift /etc/aws-config/aws-config-spacelift /home/spacelift && \ + chgrp spacelift /etc/aws-config/ && \ + chmod 775 /etc/aws-config/ + + # Give Spacelift an empty KUBECONFIG file to suppress some errors and warnings + RUN if [ -d /conf/.kube ]; then cp -a /conf/.kube /home/spacelift/.kube; \ + else mkdir -p /home/spacelift/.kube && cp /etc/kubeconfig /home/spacelift/.kube/config; fi && \ + chown -R spacelift:spacelift /home/spacelift/.kube && \ + chmod 700 /home/spacelift/.kube && \ + chmod 600 /home/spacelift/.kube/config +``` + +You may also have had a script under `rootfs/etc/profile.d` that set up `$HOME` like this: + +```bash +{ [[ -z $HOME ]] || [[ $HOME == "/root" ]]; } && (( $(id -u) == 0 )) && export HOME=/conf +``` + +You can delete that line, as `$HOME` is now set to the shell user's home directory by default, +or you can replace it with the following, which works with both Geodesic v3 and v4: + +```bash +[[ -z $HOME ]] && HOME="$(getent passwd $(id -un) | cut -d: -f6)" +[[ $HOME == "/root" ]] && (( $(id -u) == 0 )) && [[ -d /conf ]] && export HOME=/conf +``` + +
+ +- Previously, if you exited the shell that launched Geodesic, the container would exit, + killing any other running shells. Now, the container will not exit until all shells have exited. + As a side effect, you can no longer reattach to a shell that you have detached from. + You can get something closer to the old behavior by setting `ONE_SHELL=true`. + See [New Default Behavior for Multiple Shells](#new-default-behavior-for-multiple-shells) below + for more details. + +- Previously, the entire host user's home directory was mounted into the container under `/localhost`, + making everything in the host user's home directory available to the container. + Now, only specific directories are mounted, and they are mounted in the container user's + `$HOME` directory. The default directories are `.aws`, `.config`, `.emacs.d`, + `.geodesic`, `.kube`, `.ssh`, and `.terraform.d`. You can add additional directories by setting + the `HOMEDIR_ADDITIONAL_MOUNTS` environment variable. See [The Home Directory](#the-home-directory) below + for more details. + +- Previously, preferences and overrides files could be placed directly in the `$GEODESIC_CONFIG_HOME` + directory. Now they must be placed in `$GEODESIC_CONFIG_HOME/defaults` or a + Docker image-specific subdirectory. Only the `history` file can be placed directly in the + `$GEODESIC_CONFIG_HOME` directory. + +- Previously, environment variables inside the container could be set in the `~/.geodesic/env` file, + which was passed to Docker via `--env-file`. This file is now ignored. Instead, you should + set environment variables in the customization preferences and overrides. + +- The `/localhost` directory no longer exists. This used to be the single mount point + for the host filesystem, and the host user's entire `$HOME` directory was mounted there. + Now, we no longer mount the entire `$HOME` directory tree into the container. Instead, + we mount specific directories from the host to the container. + - Configuration directories directly under the host user's `$HOME` directory + (such as `.aws` or `.config`) are mounted to the container user's `$HOME` + directory. + - The git repository root directory for the project is mounted to the container's `/workspace` directory. + - Additional directories can be mounted from the host to the container by setting + the `HOST_MOUNTS` environment variable. + + If you were relying on the `/localhost` directory, it would be best to update your scripts to use + either `$HOME`, `$WORKSPACE_MOUNT`, or `$WORKSPACE_FOLDER` as appropriate. As a temporary workaround, + you can run `ln -s "$LOCAL_HOME" /localhost` in your customizations. + +- Previously, you could have Geodesic perform file ownership mapping between host and container + by setting `GEODESIC_HOST_BINDFS_ENABLED=true`; this variable is now deprecated. + Use `MAP_FILE_OWNERSHIP=true` instead. This feature is disabled by default and can + cause issues if enabled unnecessarily, but it is useful if you are having file ownership issues. + See [Files Written to Mounted Host Home Directory Owned by Root User](https://github.com/cloudposse/geodesic/issues/594) + for more details. + +### Obsolete and Deprecated Features + +#### Custom SSH Support Removed + +When Geodesic was first created, there was no way to share the SSH agent socket between the host and the container. +As a result, Geodesic provided custom SSH support, launching an SSH agent and reading configuration and keys from the host. +Now that Docker supports sharing the SSH agent socket, this custom SSH support is no longer necessary. +If the `SSH_AUTH_SOCK` environment variable is set on the host, it will be used by Docker, and the +Docker container will have access to the host SSH agent. The host `$HOME/.ssh` directory will also +be mounted automatically (unless you suppress it), so the container will have access to the host's SSH keys +and configuration, giving you a choice of how to manage your SSH keys. + +We recommend you use the host's security mechanisms to secure your SSH keys, and add them to the host's +SSH agent to make them accessible to the container. + +#### Automatic MFA Support Removed + +The `mfa` command and `oathtool` were removed. The `mfa` command was a wrapper around `oathtool` +to generate TOTP codes. It was removed because: + +- We did not have a secure place to store the TOTP key. + - It was being stored in a plaintext file + - It was being stored in `${AWS_DATA_PATH}/${profile}.mfa` which is wrong on several levels: + 1. `$AWS_DATA_PATH` is a `PATH`-like list of directories, not a single directory + 2. `$AWS_DATA_PATH` is meant to direct the AWS SDK to [directories from which to load Python models](https://github.com/boto/botocore/blob/cac78632cabddbc7b64f63d99d419fe16917e09b/botocore/loaders.py#L33), not for storing user data + 3. Actually storing the key in `${AWS_DATA_PATH}/${profile}.mfa` can cause problems for the AWS SDK +- We believe there are better ways to manage MFA, such as 1Password. +- If you still want to use `oathtool`, you can install it yourself. It is very easy to use. + +### Internal changes less likely to affect users + +- Previously, Geodesic attempted to duplicate host file paths inside the container + using symbolic links. Now Geodesic uses bind mounts instead. This should not affect + the user, but it does require the `SYS_ADMIN` capability. + Geodesic has always run with the `--privileged` flag, which includes `SYS_ADMIN`, so + this only affects people who had removed the `--privileged` flag somehow. + +## New Container File System Layout + +Geodesic v4.0.0 introduces a new file system layout for the Geodesic container, +inspired by the [Dev Container](https://containers.dev/) standard. + +### The Old Layout + +Previously, the host user's entire home directory was mounted into the container +under `/localhost`. This was done to allow the container to access the host user's +configuration files, such as `.aws` and `.ssh`. However, this had some major drawbacks, +the main one being that Docker had to map all of the user's files and directories into the +container, including, on macOS, Docker's own virtual disk and other dynamic files. +This caused major performance problems in some cases. + +Previously the home directory for the container user was forced to be `/conf`, and files +and directories were linked from `/localhost` to `/conf`. This was done to allow for a single +host mount, back when host mounts were expensive. This was also problematic, as `/conf` was +owned by `root`, and if you wanted to run the Geodesic image as a non-root user, you +had to take extra steps to manage the permissions of `/conf` and its contents. + +### The New Layout + +#### The Home Directory + +A set of directories are mounted from the user's home directory on the host to the container user's +home directory. These are meant to be directories that contain configuration files that the container's +users will need to access. Project source directories and other directories that are not meant to be +used as configuration directories should not be mounted this way. Mount the project source directories +into the container's workspace instead, and mount other directories via the `HOST_MOUNTS` environment variable, +both of which are described after this section. + +These directories are specified as a comma-separated list of directories (or files) relative to the host user's home directory. +If items in the list are not present on the host, they will be silently ignored. + +- `HOMEDIR_MOUNTS` is a list of directories to mount. It is set by default to `".aws,.config,.emacs.d,.geodesic,.kube,.ssh,.terraform.d"`. + If you set it to something else, it will replace the default list. Ensure that your Geodesic configuration directory + (default is `$HOME/.config/geodesic`) is mounted. +- `HOMEDIR_ADDITIONAL_MOUNTS` is a list of additional directories to mount. It is appended to the + `HOMEDIR_MOUNTS` list of directories to mount. This allows you to add to the defaults without overriding them. + +Note that you can mount files this way, but there are issues with that, especially when mapping file ownership. + +Many files that used to be placed directly in the `/conf` directory can now be placed in subdirectories. +Many applications now support the `XDG Base Directory Specification`, which specifies that configuration +files should be placed in `$XDG_CONFIG_HOME` (defaults to `~/.config/`). This directory is mounted by default. + +- `~/.gitconfig` can be moved to `~/.config/git/config`. If you mount `~/.gitconfig` directly, and have file ownership + mapping enabled, `git config` will not be able to modify the file. Instead, you should mount `~/.config/` and the + `git/config` inside will work as expected. +- `~/.bash_profile` can be moved to `~/.bash_profile.d/` and sourced from there. however, we do not recommend this, and we do not + mount `~/.bash_profile.d` by default. Instead, we recommend you put scripts you want to run inside Geodesic in + `~/.config/geodesic/defaults/preferences.d/` where they will be sourced automatically. If you want to share + files between the host and Geodesic, you can use symbolic links, but keep in mind that they must resolve properly in + the container, and the target files must be in a directory that is mounted into the container. You can mount + `~/.bash_profile.d` into the container by setting `HOMEDIR_ADDITIONAL_MOUNTS=".bash_profile.d"`. +- `~/.bashrc` can be moved to `~/.bashrc.d/` and sourced from there. The same caveats apply as for `~/.bash_profile`. +- `~/.emacs` can be moved into its current preferred location, `~/.emacs.d/init.el`. + +#### The Host Mounts + +You can mount any additional directories from the host to the container by setting the `HOST_MOUNTS` environment variable. +This is a comma-separated list of directories to mount, in the format `absolute_host_path[:container_path]`. If the container path is not specified, +it will be the same as the host path. The host path name must be absolute, and `~` is not acceptable. +If you want to place directories under the container user's home directory, use `HOMEDIR_ADDITIONAL_MOUNTS` +as described above. + +Unfortunately, since the colon (`:`) is meaningful to Docker, you cannot mount directories with colons in their names, +and you cannot separate directories with colons. This list must be separated with commas. + +#### The Workspace + +The workspace is where the code on the host lives, and is mounted into the container. +This is controlled by several environment variables, all of which have defaults +settings that can be overridden. + +As always, you can configure the environment variables on the command line with `--var=value`, +but as a convenience, you can also override `WORKSPACE_FOLDER_HOST_DIR` with `--workspace=dir`. + +tl;dr: Either launch Geodesic from the root of your project, or set `WORKSPACE_FOLDER_HOST_DIR` in your `launch-options.sh` file +to the root of your project. (See [Launch Options Files](#launch-options-files) below for details.) +If you do this, you can launch Geodesic from any directory and have the correct directory be the workspace. + +| Variable | Description | +|-----------------------------|-----------------------------------------------------------------------------------------------------------| +| `WORKSPACE_FOLDER_HOST_DIR` | The directory on the host that is the root of the project. | +| `WORKSPACE_MOUNT_HOST_DIR` | The directory on the host that is mounted into the container to make the source code accessible. | +| `WORKSPACE_MOUNT` | The directory in the container where the `WORKSPACE_MOUNT_HOST_DIR` is mounted. Defaults to `/workspace`. | +| `WORKSPACE_FOLDER` | The directory in the container that is considered the root of the project. | + +The variables are set as follows: + +- If you set `WORKSPACE_FOLDER_HOST_DIR` in the environment, that directory will be used as the working directory. It must be an + absolute path: `$HOME/path` is acceptable, `~/path` is not. You can set this in the `launch-options.sh` file for + each image you use, and then you can launch Geodesic from any directory and have the correct directory be the workspace. + If not set, `WORKSPACE_FOLDER_HOST_DIR` defaults to the current working directory, from where Geodesic was launched. + +- If you set `WORKSPACE_MOUNT_HOST_DIR` in the environment, it must be either the same as `WORKSPACE_FOLDER_HOST_DIR` or + a parent of that directory. This directory will be mounted into the container as `WORKSPACE_MOUNT`. If not set: + + - If `WORKSPACE_FOLDER_HOST_DIR` is inside a Git repository, `WORKSPACE_MOUNT_HOST_DIR` will be set to the root of that repository + - If `WORKSPACE_FOLDER_HOST_DIR` is not inside a Git repository, `WORKSPACE_MOUNT_HOST_DIR` will be set to `WORKSPACE_FOLDER_HOST_DIR` + +- Unless explicitly set (not recommended), `WORKSPACE_FOLDER_HOST_DIR`, relative to the parent of `WORKSPACE_MOUNT_HOST_DIR`, will be communicated + to the container as `WORKSPACE_FOLDER` and considered the working directory for the container. +- A symbolic link will be created in the container, so that the host value of `WORKSPACE_FOLDER_HOST_DIR` will + reference the `WORKSPACE_FOLDER`. + +### Fixing File Ownership Issues + +Depending on the way you installed Docker, you may have file ownership issues with the files created +from within the container on the host. The default Geodesic user is `root` and if Docker is not translating +file ownership properly, the files will be owned by `root` on the host. This can be fixed by running Docker +in "rootless" mode, but that is not always practical, so Geodesic has special support to handle this case. + +This support used to be enabled by setting `GEODESIC_HOST_BINDFS_ENABLED=true`, but this is now deprecated. +Instead, enable it by setting `MAP_FILE_OWNERSHIP=true`. This will cause Geodesic to use `bindfs` to map the +file ownership between the host and the container. Please note, however, that if Docker is properly translating +file ownership, this setting will cause, rather than fix, file ownership problems, so only use it if needed. +It is disabled by default, because current macOS and best practice Linux Docker installations do not need it. + +## New Features + +### Command-Line Options + +Geodesic documentation has shown (and for the moment, continues to show) +Geodesic options as settings of shell environment variables. This is because Geodesic +is launched by a `bash` script, and then runs a `bash` shell inside the container. +In this document, we take care to differentiate between options that apply to the +launch script (sometimes referred to as the "wrapper") and options that apply to the +shell inside the container. + +What has always been true, but never clearly spelled out, is that the options that +apply to the launch script can also be set as command-line options. Convert the +environment variable to lower case, optionally replace the `_` with `-`, and prefix +it with `--` and you have the command line option. For example, `ONE_SHELL=false` becomes +`--solo=false`. For boolean options, you can leave out the value, so `ONE_SHELL=true` +becomes `--solo`. + +To avoid tedious redundancy, we will not usually repeat the command-line options in the +documentation. Instead, we will refer to the environment variable, and you can +convert it to a command-line option as described above. Just remember that they +only apply to launch options, not to configuration of the shell inside the container. + +### New Default Behavior for Multiple Shells + +Previously, when you launched Geodesic, it would launch a new shell as PID 1. +If you tried to launch Geodesic again, it would not start a new container, but would +instead exec into the container, launching a new shell. This was done to avoid the +overhead of starting a new container each time you wanted a new shell, and has some +advantages and disadvantages with all the shells sharing the same container. +One disadvantage was that if you exited the first shell, the container would exit, +killing any other shells running inside the container. Another disadvantage, +or at least odd behavior, is that if you detached from the first shell, you could +reattach to it later, but if you detached from a shell launched by exec, you could +not reattach to it. Attempting to reattach to it would attach to the first shell, +and you would have 2 terminals sharing the same shell, while the detached shell +would remain abandoned. + +Now, by default, when you launch Geodesic, it launches a tiny init process as PID 1. This init process +monitors the shells running inside the container, and does not exit until all the shells exit. +So now if you quit the first shell while other shells are running, the container will not exit. + +One consequence of this change is that if you detach from any shell, even the first one, you will not be able to +reattach to it. `docker attach` will connect you to the init process, not the shell. So we semi-disable detaching from +the shell by setting an unusual string for the `detachKeys`. + +### New Option for One Container Per Shell + +An alternative to this new default behavior is to launch a new container each time you run Geodesic. +This is done by setting the `ONE_SHELL` environment variable to "true" in your +`launch-options.sh` file, or using `--solo` on the command line. This will cause the wrapper +to launch a new container each time you run it. + +The 2 main advantages of this are: + +1. You can run multiple versions of Geodesic at the same time. This is useful for testing new versions. +2. You can detach from a shell and reattach to it later. + +### New Options for Cleanup Scripts + +Previously, when the wrapper that launches Geodesic exited, it would run a cleanup script +named `geodesic_on_exit` if it existed. This name was hard coded and not configurable. + +Now, the name of the cleanup script is configurable, and the script makes a distinction between +two events: + +1. ON_SHELL_EXIT: When a shell exits but the container is still running. Defaults to no script. +2. ON_CONTAINER_EXIT: When the container exits. Defaults to `geodesic_on_exit`. + +The caveat here is that these scripts are run when the wrapper exits, not necessarily +when the shell or container exits. This means that if you detach from a shell, the wrapper +will run `$ON_SHELL_EXIT`. If you reattach to the shell, the wrapper is not involved, +so quitting the shell or container will not run the cleanup script. + +### New Location for Geodesic Configuration Files + +Previously, all Geodesic configuration was stored in the `~/.geodesic` directory. +This has been changed to `$XDG_CONFIG_HOME/geodesic` which defaults to `~/.config/geodesic`. +This change was made to follow the +[XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html). + +If the `$XDG_CONFIG_HOME/geodesic` directory does not exist, Geodesic will +continue to use the `~/.geodesic` directory. If the `$XDG_CONFIG_HOME/geodesic` directory does exist, +the `~/.geodesic` directory will be ignored. + +Previously, environment variables inside the container could be set in the `~/.geodesic/env` file, +which was passed to Docker via `--env-file`. This file is now ignored. Instead, you should +set environment variables in the customization preferences and overrides. + +### New Customization Options + +As explained in the [Customizing Geodesic](/docs/customization.md) documentation, +there are several ways to customize Geodesic. However, until now, most of these customizations +only applied to customizing the shell inside the Geodesic container. Customizing the +launch of the Geodesic container itself was more difficult. + +#### Launch Options Files + +Geodesic now supports launch options files that customize the launch of the Geodesic container. +Geodesic is launched by a `bash` script and can be customized by setting environment variables. +Using the same directory structure as the Geodesic configuration files, you can create a file +`launch-options.sh` that will be sourced by the script after the defaults are configured but before +they are used. The searched directories depend on the name of the Docker image being launched. +All `launch-options.sh` files are sourced in the order they are found, meaning later ones override earlier ones. +With the configuration directory `$GEODESIC_CONFIG_HOME` (defaults to `$XDG_CONFIG_HOME/geodesic`) and +an image named `ghcr.io/cloudposse/geodesic:4.0.0-debian`, the directories searched, in order, are: + +1. `$GEODESIC_CONFIG_HOME/defaults/` +2. `$GEODESIC_CONFIG_HOME/cloudposse/` +3. `$GEODESIC_CONFIG_HOME/geodesic/` +4. `$GEODESIC_CONFIG_HOME/cloudposse/geodesic/` + +The registry (`ghcr.io/` in the example) is ignored when searching for the `launch-options.sh` file. + +If the `$GEODESIC_CONFIG_HOME/launch-options.sh` file directly changes the `DOCKER_IMAGE` variable, it will change the +directories being searched in steps 2-4. Later changes, or setting `GEODESIC_IMAGE`, will not change +the directories being searched. + +#### New Customization Command-Line Options + +3 command line options regarding customization have been added: + +1. `--no-custom` (or `--no-customization`, or `--geodesic-customization-disabled`) will disable all user-specific + customizations. This is equivalent to setting `GEODESIC_CUSTOMIZATION_DISABLED=true`. This is useful for + "works in my environment" testing, where you want to disable all customizations to see if the problem is in the + customizations or in the base image. Note that this does not disable changes made by `launch-options.sh`. +2. `--trace` will enable tracing the Geodesic script as it performs customizations. Equivalent to `--trace=custom`. +3. `--trace="custom terminal hist` will enable tracing of the customizations, terminal configuration (mainly with respect + to light and dark mode support), and determining which Bash history file to use, respectively. You can use these options + in any combination, for example, `--trace="hist"`. + +## Dark mode support + +Geodesic's limited color handling had initially assumed terminals are in light mode. +Support for terminals being in dark mode was introduced in Geodesic v2.10.0, +but was not previously well documented. There have also been some enhancements +since then. The following describes the state of support as of v4.0.0. + +### Switching between light and dark mode + +Geodesic provides basic support for terminal dark and light modes. +Primarily, this is used to ensure Geodesic's colored output is readable in both modes, +for example, black in light mode and white in dark mode. + +There is no standard way to be notified of a terminal's color mode change. Geodesic +listens for SIGWINCH and updates the color mode when receiving it. Some terminals +send this when the color mode changes, but not all do. (For example, macOS Terminal does not.) + +There can be issues with the signal handler. For example, if your computer is +waking from sleep, the signal handler may be called multiple times, but +the terminal may take several seconds to respond to the query about its color mode. +This can result in long delays while Geodesic waits for the terminal to respond, +and if it times out, the response may eventually be written to the terminal +command line, looking something like `10;rgb:0000/0000/000011;rgb:ffff/ffff/ffff`. +This area of Geodesic is still new and under development, so there are likely to be subtle bugs. +If you want to disable this feature, you can set `GEODESIC_TERM_COLOR_AUTO=false`. +If Geodesic detects a problem with the terminal color mode, it will disable this feature +by setting `GEODESIC_TERM_COLOR_AUTO=disabled`. + +You can report issues with this, or any Geodesic feature, via the `#geodesic` +channel in the [Cloud Posse Slack workspace](https://cpco.io/slack?utm_source=github&utm_medium=release_notes&utm_campaign=cloudposse/geodesic&utm_content=slack). + +Geodesic provides a shell function called `update-terminal-color-mode` that can be used to manually +update the terminal mode. This function is called automatically when Geodesic starts, but +if you change the terminal color mode while Geodesic is running, you can call this function +to update the color mode. If your terminal supports calling a function when the color mode changes, +you can call this function from there. Alternately, you can trigger the function call +by resizing the terminal window, which triggers the SIGWINCH signal handler. + +The `update-terminal-color-mode` function takes one argument, which is the terminal color mode, +either `light` or `dark`. If you do not provide an argument, it will attempt to determine +the terminal color mode itself. + +You can query Geodesic for its cached color mode setting by running `get-terminal-color-mode`. + +Changing Geodesic's color mode does not change anything already on the screen. It only affects +future output. + +#### Named text color helpers + +To help you take advantage of the color mode, Geodesic provides a set of named text color helpers. +They are defined as functions that output all their arguments in the named mode. +The named colors are + +- red +- green +- yellow +- cyan + +Note: yellow is problematic. To begin with, "yellow" is not necessarily yellow, +it varies with the terminal theme, and would be better named "caution" or "info". +In addition, it is too light to be used in light mode, so we substitute magenta instead. + +Each of these colors has 4 variations. Using "red" as an example, they would be: + +- red +- bold-red +- red-n +- bold-red-n + +The "bold-color" version outputs text in the bold (or "emphasis") version of the color. + +The "-n" means no newline is output after the text. These versions also include non-printing delimiters around the +non-printing text, making them suitable for use in PS1 prompts. + +Note that the newline in the plain versions is stripped if run via command substitution, so + +```bash +echo "$(red "Hello") World" +``` + +will not have a newline between "Hello" and "World". + +The remaining ANSI colors, black, white, blue, and magenta, are not directly provided as named helpers to +discourage their use. They are available via the `_geodesic_color` function, which takes +the same kind of color name as the named helpers as its first argument, and then outputs +the rest of its arguments in that color. For example, + +```bash +_geodesic_color bold-magenta Hello, World +``` + +These colors are not provided as named helpers because they are problematic, and +we want to discourage their use. Nevertheless, you may prefer to use the +`_geodesic_color` function to color text in these colors, because of the +dark mode support. + +- In light mode, yellow is too light to be used, so it is replaced with magenta. + We therefore discourage using magenta as it will not be distinguished from yellow in light mode. +- In dark mode, blue is problematic, so it is replaced with cyan. Also, white and black are swapped. + +## Updated Documentation + +The [customization](/docs/customization.md) documentation has been updated to reflect the new +features and changes in Geodesic v4.0.0. + +The [environment variables](/docs/environment.md) documentation has been added to document the +shell environment variables Geodesic uses for customization and operation. + +The [wrapper](/docs/wrapper.md) documentation has been added to explain what is meant +when other documentation refers to "the wrapper". + +### Environment Variable Changes + +For full documentation of environment variables, see the [Environment Variables](/docs/environment.md) document. + +V4 changes: + +- `GEODESIC_LOCALHOST` prefixed variables have been removed. +- `HOME` has changed from `/conf` to the container user's home directory as configured in `/etc/passwd`. For the + default user of `root`, this is `/root`. +- Variables that had defaults referencing `/localhost` now generally reference `$HOME` instead. + +- `HOMEDIR_MOUNTS` and `HOMEDIR_ADDITIONAL_MOUNTS` are comma-separated lists of directories relative to the host user's home directory + to mount into the container under the container user's home directory. `HOMEDIR_MOUNTS` has a default list of + directories to mount. `HOMEDIR_ADDITIONAL_MOUNTS` is appended to the `HOMEDIR_MOUNTS` list so you can easily + add to the defaults without overriding them. +- `HOST_MOUNTS` is a list of mounts from the host to the container. It has the format + `host_path[:container_path]`. If the container_path is not specified, it is assumed to be the same as the host_path. + This list excludes the home directory, which is handled separately. + +- `WORKSPACE_MOUNT_HOST_DIR` is the host directory where the project will be mounted. Analogous to the source of Dev Container's + `workspaceMount`. Typically, this is a Git repository root. +- `WORKSPACE_MOUNT` is the container path where `WORKSPACE_MOUNT_HOST_DIR` will be mounted. Analogous to the target of Dev Container's + `workspaceMount`. Defaults to `/workspace`, which is the default for a Dev Container, but you may want to set it to + something like your project name or git repository name for visibility in the container. +- `WORKSPACE_FOLDER_HOST_DIR` is the base directory of the project on the host. Analogous to the target of Dev Container's + `workspaceFolder`. Typically, this is the same as `WORKSPACE_MOUNT`, but may be a subdirectory if the Git repository is + a "monorepo" containing multiple projects. It must be an absolute path either equal to or a subdirectory of `WORKSPACE_MOUNT_HOST_DIR`. + - Setting `WORKSPACE_FOLDER_HOST_DIR` in your Docker-image-specific `launch-options.sh` will allow you to launch your project's + Geodesic app from any working directory and have the correct configuration inside Geodesic. +- `WORKSPACE_FOLDER` is the base directory of the project inside the container. Analogous to the target of Dev Container's + `workspaceFolder`. Typically, this is the same as `WORKSPACE_MOUNT`, but may be a subdirectory if, for example, the Git repository is + a "monorepo" containing multiple projects. It must be an absolute path either equal to or a subdirectory of `WORKSPACE_FOLDER_HOST_DIR`. +- `GEODESIC_TERM_COLOR_AUTO` is normally unset. Set it to "false" to disable attempts at automatic terminal light/dark mode detection. diff --git a/docs/ReleaseNotes-v4.md b/docs/ReleaseNotes-v4.md new file mode 100644 index 000000000..0311fb514 --- /dev/null +++ b/docs/ReleaseNotes-v4.md @@ -0,0 +1 @@ +Moved to [/ReleaseNotes-v4.md](../ReleaseNotes-v4.md) diff --git a/docs/about.md b/docs/about.md index 0ade9225c..327f3f250 100644 --- a/docs/about.md +++ b/docs/about.md @@ -20,19 +20,16 @@ about - About the Geodesic Cloud Automation Shell At its core, Geodesic is a framework for provisioning cloud infrastructure and the applications that sit on top of it. We leverage as many existing tools as possible to facilitate cloud fabrication and administration. We're like the connective tissue that sits between all of the components of a modern cloud. -* [`aws-cli`](https://github.com/aws/aws-cli/) for interacting directly with the AWS APIs +* [`atmos`](https://atmos.tools) for managing configuration of deployments across multiple environments +* [`aws` CLI](https://github.com/aws/aws-cli/) for interacting directly with the AWS APIs * [`chamber`](https://github.com/segmentio/chamber) for managing secrets with AWS SSM+KMS and exposing them as environment variables * [`helm`](https://github.com/kubernetes/helm/) for installing packages like Varnish or Apache on the Kubernetes cluster * [`helmfile`](https://github.com/roboll/helmfile) for 12-factorizing chart values and installing chart collections * [`kubectl`](https://kubernetes.io/docs/user-guide/kubectl-overview/) for controlling kubernetes resources like deployments or load balancers -* [`gcloud`, `gsutil`](https://cloud.google.com/sdk/) for integration with Google Cloud (e.g. GKE, GCE, Google Storage) * [`gomplate`](https://github.com/hairyhenderson/gomplate/) for template rendering configuration files using the GoLang template engine. Supports lots of local and remote datasources -* [`goofys`](https://github.com/kahing/goofys/) a high-performance Amazon S3 file system for mounting encrypted S3 buckets that store cluster configurations and secrets * [`terraform`](https://github.com/hashicorp/terraform/) for provisioning miscellaneous resources on pretty much any cloud * [`tmate`](https://tmate.io) for remote terminal sharing with other engineers (pairing) and collaborative debugging -[](https://media.giphy.com/media/26FmS6BRnPVPo2FDq/source.gif) - ## SEE MORE -Extensive documentation is provided on our [Documentation Hub](https://docs.cloudposse.com/geodesic). +Extensive documentation is provided on our [Documentation Hub](https://docs.cloudposse.com/resources/legacy/fundamentals/geodesic/). diff --git a/docs/chudo.md b/docs/chudo.md deleted file mode 120000 index 652c48a14..000000000 --- a/docs/chudo.md +++ /dev/null @@ -1 +0,0 @@ -kudo.md \ No newline at end of file diff --git a/docs/chundo.md b/docs/chundo.md deleted file mode 120000 index 652c48a14..000000000 --- a/docs/chundo.md +++ /dev/null @@ -1 +0,0 @@ -kudo.md \ No newline at end of file diff --git a/docs/customization.md b/docs/customization.md index 0f7048d88..1ec2fbad8 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -2,7 +2,7 @@ title: Customization(5) | Geodesic author: - Nuru -date: March 2019 +date: January 2025 --- ## NAME @@ -10,7 +10,7 @@ date: March 2019 Customization - How to customize Geodesic at launch time ## SYNOPSIS - + Several features of Geodesic can be customized at launch time (rather than during the build of the Docker image) so that people can share an image yet still have things set up the way they like. This document describes @@ -18,27 +18,25 @@ how to configure the customization. ## DESCRIPTION -A great deal of customization can be implemented by placing shell scripts -on the host computer, to be read either at the start of `bash` profile +A great deal of customization can be implemented by placing shell scripts +on the host computer, to be read either at the start of `bash` profile script processing or at the end of it. These shell scripts can set up environment variables, command -aliases, shell functions, etc. and through setting environment variables, can cause Geodesic to +aliases, shell functions, etc. and through setting environment variables, can cause Geodesic to enable or disable certain features. -Due to technical limitations, there are a small number of options -that can only be controlled via command line options or corresponding (exported) -shell environment variables set in the user's shell before launching -Geodesic via the wrapper script (which is the default and recommended way to launch Geodesic). - -Users can also choose whether to have a single `bash` history file -for all containers or to have separate history files. +### Configuration via Shell Environment Variables and Command Line Options -### Command line options (also environment variables) +Nearly all configuration of Geodesic is ultimately done via shell environment variables. +See [environment(5)](https://github.com/cloudposse/geodesic/blob/main/docs/environment.md) +for a list of all the environment variables that Geodesic uses. Nearly all of them +have corresponding command-line options. For the most part, this document will only refer to the environment +variables, and only mention the command-line options when they are different from or have +no corresponding environment variable. Here we explain how to derive the command-line +options from the environment variables. -Geodesic accepts a small number of command line options, which can also be -set by corresponding (exported) shell environment variables. In all cases, -the command line options must be introduced with double dashes and be -set via `=`. Boolean options can be set `=true` with no argument on the command line. -Command line options are lower case with words separated by dashes, +In all cases, the command line options relating to environment variables +must be introduced with double dashes and be set via `=`. Boolean options can be +set `=true` with no argument on the command line. Command line options are lower case with words separated by dashes, while environment variables are upper case with words separated by underscores. Valid: @@ -50,117 +48,149 @@ geodesic --geodesic-host-bindfs-enabled Not valid: ```bash -geodesic --env-file ~/.geodesic/extra-envs -env-file=~/.geodesic/extra-envs -ENV-FILE=~/.geodesic/extra-envs -GEODESIC_HOST_BINDFS_ENABLED= geodesic +geodesic --env-file ~/.geodesic/extra-envs # would set ENV_FILE to "true" and pass the next argument to Geodesic to run +env-file=~/.geodesic/extra-envs # not a valid environment variable name +ENV-FILE=~/.geodesic/extra-envs # not a valid environment variable name ``` At the moment, underscores are accepted instead of dashes in command line arguments, but this behavior should not be relied on: Works, but not officially supported: + ```bash -geodesic --env-file=~/.geodesic/extra_envs +geodesic --env_file=~/.geodesic/extra_envs ``` -TODO: Document all the command line options in [geodesic(1)](geodesic.md) +### Phases of Configuration + +Geodesic configuration is done in two phases: + +1. In phase 1, an installed `bash` script, referred to as the [wrapper(7)](wrapper.md), + configures the `docker run` command (extensively) and starts the Geodesic container. + Command line options, exported shell environment variables, and `launch-options.sh` files + configure the wrapper and the launching of the Geodesic Docker container. +2. Once the container is running, Geodesic uses some of the same environment variables, + plus many others, and reads a series of configuration files to set up the shell environment. + +Geodesic's default configuration is fine for casual users, but most power users will +want to store their preferences in configuration files on the host machine. + +#### Configuration File Locations + +Configuration files can be in several places, with different levels of specificity +and priority. However, the first order of business is determining the host machine +root directory for configuration files. + +##### Root directory for configuration files -### Root directory for configuration +All configuration files (and shell history files generated by Geodesic shells) +are stored under the root directory designated by `$GEODESIC_CONFIG_HOME`. +Prior to Geodesic version 4, the default was `$HOME/.geodesic`. +Starting with Geodesic version 4, the default directory is +`$XDG_CONFIG_HOME/geodesic`, which defaults to `$HOME/.config/geodesic`, per the +[XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/latest/) +If that default directory does not exist, Geodesic will fall back to `$HOME/.geodesic`. -All configuration files are stored under `$GEODESIC_CONFIG_HOME`, which defaults to `/localhost/.geodesic`. -At this time, `/localhost` is mapped to the host `$HOME` directory and this cannot be configured yet, -so all configuration files must be under `$HOME`, but within that limitation they can be placed anywhere. -So if you set `$GEODESIC_CONFIG_HOME` to `/localhost/work/config/geodesic`, -then files would go in `~/work/config/geodesic/` and below on your Docker host machine. -### Resources +#### Configuration File Types -There are currently 5 Resources used for configuration: -- Preferences, which are shell scripts loaded very early in the launch of the Geodesic shell. -- Overrides, which are shell scripts loaded very late in the launch of the Geodesic shell. -- `bash` history files, which store `bash` command line history. -- A file of environment variable settings passed to `docker --env-file` -- Command-line options (which can also be set by correspondingly named shell environment variables) +There are currently 4 file types used for configuration: -Additionally, when Geodesic exits normally, it will run the host command `geodesic_on_exit` -if it is available. This is intended to be a script that you write and install -anywhere on your PATH to do whatever cleanup you want. For example, change the window title. +1. `launch-options.sh` which configure the launching of the Geodesic Docker container. These + are read by the Geodesic wrapper script and thus are the first set to be read. +2. Preferences, which are shell scripts sourced very early in the launch of the Geodesic shell. + This is where you configure preferences that affect the behavior of the Geodesic shell. +3. Overrides, which are shell scripts sourced very late in the launch of the Geodesic shell. + This is where you can override settings made by Geodesic or by the preferences files that + you were not able to influence earlier or that require the results of the initialization. +4. `bash` history files, which store `bash` command line history so that it persists between + shell sessions. Only the last one found is used. If no `history` file is found, + Geodesic will create and use `${GEODESIC_CONFIG_HOME}/history`. -#### Preferences and Overrides +For preferences and overrides, you can either provide a file with that name +or a directory with that name plus `.d`, e.g. `preferences.d`. If a directory, the visible files in the directory +will be sourced, unless they match the `GEODESIC_AUTO_LOAD_EXCLUSIONS` regex, which defaults to +`(~|.bak|.log|.old|.orig|.txt|.md|.disabled|#)$`. This regex is `bash` Extended Regular Expressions syntax. -Both preferences and overrides can be either a single file, named `preferences` and `overrides` respectively, -or can be a collection of files in directories named `preferences.d` and `overrides.d`. -If they are directories, all the visible files in the directories will be sourced, -except for hidden files and files with names matching the `GEODESIC_AUTO_LOAD_EXCLUSIONS` regex, -which defaults to `(~|.bak|.log|.old|.orig|.disabled)$`. +You can also configure Geodesic via command line options. -#### History files +#### Configuration Directories File Loading Order -`bash` history is always stored in a single file named `history`, never a directory of files -nor files with any other name. If you want to use a separate history file for one -Geodesic-based Docker image not shared by other Geodesic-based Docker images, you -must create an empty `history` file in the image-specific configuration directory (see below). +Several directories are searched for configuration files, based on the Docker image name. +File loaded later override earlier files. Files within a directory are loaded +in lexical sort order. -#### Environment file +An image name like `ghcr.io/cloudposse/geodesic:latest` generates the following +parts (these are not the actual variable names used): -Geodesic passes a single argument to Docker's `--env-file` option when launching -via the installed wrapper script. By default, that file is `~/.geodesic/env` -but that file name can be set via the `--geodesic-default-env-file` command line -option or the `GEODESIC_DEFAULT_ENV_FILE` environment variable. +- `COMPANY=cloudposse` +- `STAGE=geodesic` +- `DOCKER_IMAGE=cloudposse/geodesic` -A second environment file can be specified in addition to the default via the -`--env-file` command line option or `ENV_FILE` +The directories are searched in the following order: -### Configuration by file placement -Resources can be in several places, and will be loaded from most general to most specific, according to the name of the docker container image. +1. `$GEODESIC_CONFIG_HOME/defaults` - the most general configuration files +2. `$GEODESIC_CONFIG_HOME/$COMPANY` - company-specific configuration files +3. `$GEODESIC_CONFIG_HOME/$STAGE` - stage-specific configuration files +4. `$GEODESIC_CONFIG_HOME/$DOCKER_IMAGE` - container-specific configuration files -- The most general resources are the ones directly in `$GEODESIC_CONFIG_HOME`. These are applied first. To keep the top-level directory less cluttered and to avoid name clashes, you can put them in a subdirectory named `defaults`. If that subdirectory exists, then `GEODESIC_CONFIG_HOME ` itself is not searched. -- The `DOCKER_IMAGE` name is then parsed. Everything before the final `/` is considered the "company" name and everything after is, following the Cloudposse reference architecture, referred to as the "stage" name. So for the `DOCKER_IMAGE` name `cloudposse/geodesic`, the company name is `cloudposse` and the stage name is `geodesic` -- The next place searched for resources is the directory with the same name as the "company". In our example, that would be `~/.geodesic/cloudposse`. Resources here would apply to all containers from the same company. -- The next place searched for resources is the directory with the same name as the "stage", which is generally the name of the project. In our example, that would be `~/.geodesic/geodesic`. Resources here would apply to all containers with the same base name, perhaps various forks of the same project. -- The final place searched is the directory with the full name of the Docker image: `$GEODESIC_CONFIG_HOME/$DOCKER_IMAGE`, -i.e. `~/.geodesic/cloudposse/geodesic`. Files here are the most specific to this container. +Prior to Geodesic version 4, `$GEODESIC_CONFIG_HOME/` itself would be searched +in step 1 if `$GEODESIC_CONFIG_HOME/defaults` did not exist. As of v4, this +is no longer the case, and those files (except for `history`) will be ignored. -By loading them in this order, you can put your defaults at one level and then override/customize them at another, minimizing the amount of duplication needed to customize a wide range of containers. +For history files only, if no `history` file is found in the above directories, +Geodesic will create and use `${GEODESIC_CONFIG_HOME}/history`. In order to +use a different history file, you must create an empty file with that name in the +appropriate directory. -### Usage details -Preferences and Overrides are loaded in the order specified above and all that are found are loaded. -For history files, only the last one found is used. To start keeping separate history, -just create an empty history file in the appropriate place. -While Preferences and Override files themselves must be `bash` scripts and will be directly loaded into -the top-level Geodesic shell, they can of course call other programs. -You can even use them to pull configurations out of other places. +##### Cleanup on Exit -Symbolic links must be relative if you want them to work both inside Geodesic and outside of it. -Symbolic links that reference directories that are not below `$HOME` on the host will not work. +Geodesic, by default, can run multiple shells per container. When a shell exits, +Geodesic will run the command specified by `ON_SHELL_EXIT`, which defaults to `geodesic_on_exit`. +(Prior to Geodesic version 4, the command name was hard-coded.) This is intended to be a +command that you write and install to do whatever cleanup you want. For example, change the window title. +Note that another way to accomplish nearly the same thing is to use the `trap` command in your +preferences file to run a command when the shell exits. -When possible, Geodesic mounts the host `$HOME` directory as `/localhost` and creates a symbolic link -from `$HOME` to `/localhost` so that files under `$HOME` on the host can be referenced by the -exact same absolute path both on the host computer and inside Geodesic. For example, if the -host `$HOME` is `/Users/fred`, then `/Users/fred/src/example.sh` will refer to the same file both -on the host and from inside the Geodesic shell. +Starting with Geodesic version 4, there is a similar command `ON_CONTAINER_EXIT`, that runs +when the Geodesic container exits. + +The caveat here is that these scripts are run when the wrapper exits, not necessarily +when the shell or container exits. This means that if you detach from a shell, the wrapper +will run `$ON_SHELL_EXIT`. If you reattach to the shell, the wrapper is not involved, +so quitting the shell or container will not run the cleanup script. + +For these reasons, you may prefer to install a command via your preferences file +and then set that command to run on shell exit via the `trap` command: + +```bash +trap 'geodesic_on_exit' EXIT +``` + +The caveat here is that if the shell is killed with `kill -9`, the `trap` command will not run. +That is less of a problem with Geodesic v4, which is generally able to terminate shells +politely (via SIGTERM), but will still be a problem if the container is killed with `docker kill`. -In general, you should put most of your customization in the Preferences files. -Geodesic (usually) takes care to respect and adapt to preferences set before it starts adding on top of them. -The primary use for overrides is if you need the results of the initialization process as inputs to your configuration, -or if you need to undo something Geodesic does not yet provide a configuration option for not doing in the first place. ## WARNING -One of the key benefits of Geodesic is that it provides a consistent environment for all users regardless of their -local machine. It eliminates the "it works on my machine" excuse. While these customization options can be great -productivity enhancements as well as provide the opportunity to install new features to try them out before committing -to installing them permanently, they can also create the kind of divergence in environments that brings back -the "it works on my machine" problem. -Therefore, we have included an option to disable the customization files: the preferences, the overrides, -and the docker environment files. Simply set and export the host environment variable `$GEODESIC_CUSTOMIZATION_DISABLED` -to any value other than "false" before launching Geodesic. +One of the key benefits of Geodesic is that it provides a consistent environment for all users regardless of their +local machine. It eliminates the "it works on my machine" excuse. While these customization options can be great +productivity enhancements as well as provide the opportunity to install new features to try them out before committing +to installing them permanently, they can also create the kind of divergence in environments that brings back +the "it works on my machine" problem. + +Therefore, we have included an option to disable the customization the preferences and the overrides. +You can use the command-line option `--no-custom` (or `--no-customization`) to disable them. +Alternately, you can set and export the host environment variable `$GEODESIC_CUSTOMIZATION_DISABLED` +to any value other than "false" before launching Geodesic. This can be set in `launch-options.sh` +as launch options themselves are not disabled either way. ## TROUBLESHOOTING -If customizations are not being found or are not working as expected, -you can set the host environment variable `$GEODESIC_TRACE` to "custom" before -launching Geodesic and a trace of the customization process will be output -to the console. + +If customizations are not being found or are not working as expected, you can use the command line option `--trace` +and a trace of the customization process will be output to the console. Alternatively, you can set the host environment +variable `$GEODESIC_TRACE` to "custom" before launching Geodesic. diff --git a/docs/environment.md b/docs/environment.md new file mode 100644 index 000000000..f7a2017d3 --- /dev/null +++ b/docs/environment.md @@ -0,0 +1,119 @@ +--- +title: environment(5) | Geodesic +author: +- Nuru +date: January 2025 +--- + +Geodesic makes extensive use of environment variables for a variety of purposes. +This document is a reference of what variables are in use and what their purpose is. +It may be incomplete. Please update it as missing variables are found. + +Geodesic version 4 additions and changes: + +- `GEODESIC_LOCALHOST` prefixed variables have been removed. +- `HOMEDIR_MOUNTS` and `HOMEDIR_ADDITIONAL_MOUNTS` are input lists of directories relative to the home directory on the host + to mount into the container under the container user's home directory. +- `GEODESIC_HOMEDIR_MOUNTS` is set inside the shell to be the union of `HOMEDIR_MOUNTS` and `HOMEDIR_ADDITIONAL_MOUNTS`. +- `HOST_MOUNTS` is a list of mounts from the host to the container. It has the format + `host_path[:container_path]`. If the container_path is not specified, it is assumed to be the same as the host_path. + This list excludes the home directory, which is handled separately. +- `GEODESIC_HOST_PATHS` is set inside the shell to an array of absolute file system paths that are mounted from the host. +- `WORKSPACE_FOLDER_HOST_DIR` is the base directory of the project on the host. Analogous to the target of Dev Container's + `workspaceFolder`. **Defaults to the working directory from where you launched Geodesic**. + Typically, this is the root of the Git repository holding your source code, but may be a subdirectory if the Git repository is + a monorepo containing multiple projects. It must be an absolute path either equal to or a subdirectory of `WORKSPACE_MOUNT_HOST_DIR`. + - Setting `WORKSPACE_FOLDER_HOST_DIR` in your Docker-image-specific `launch-options.sh` will allow you to launch your project's + Geodesic app from any working directory and have the correct configuration inside Geodesic. +- `WORKSPACE_FOLDER` is the base directory of the project inside the container. Analogous to the target of Dev Container's + `workspaceFolder`. Typically, this is the same as `WORKSPACE_MOUNT`, but may be a subdirectory if, for example, the Git repository is + a "monorepo" containing multiple projects. It must be an absolute path either equal to or a subdirectory of `WORKSPACE_MOUNT`. +- `WORKSPACE_MOUNT_HOST_DIR` is the host directory where the project will be mounted. Analogous to the source of Dev Container's + `workspaceMount`. Typically, this is a Git repository root. If `WORKSPACE_FOLDER_HOST_DIR` is in a subdirectory of the Git repository, + `WORKSPACE_MOUNT_HOST_DIR` defaults to the repository root. Otherwise, it defaults to `WORKSPACE_FOLDER_HOST_DIR`. +- `WORKSPACE_MOUNT` is the container path where `WORKSPACE_MOUNT_HOST_DIR` will be mounted. Analogous to the target of Dev Container's + `workspaceMount`. Defaults to `/workspace`, which is the default for a Dev Container, but you may want to set it to + something like your project name or git repository name for visibility in the container. +- `GEODESIC_TERM_COLOR_AUTO` is normally unset. Set it to "false" to disable attempts at automatic terminal light/dark mode detection. +- `GEODESIC_MOTD_ENABLED` can be set to "false" to disable printing the message of the day at shell startup. +- `MAP_FILE_OWNERSHIP` replaces `GEODESIC_HOST_BINDFS_ENABLED`. If set to true, Geodesic will use `bindfs` to map file ownership + between the host and container. This not normally needed, as it should be handled automatically by Docker. + +### Geodesic Version 3 Environment Variables + +Below is a list of environment variable that may be visible in the shell and were present in Geodesic v3. +Many of these variables are only recognized if you explicitly set or export them prior to running the script. +Others are set and read internally to control optional behaviors. + +They are sorted alphabetically, ignoring leading underscores for the purpose of sorting. Variables marked with an +asterisk (*) are either deprecated or removed in Geodesic v4 and should not be relied on. The description prefix +"Internal:" indicates that the variable is used by Geodesic itself and should not be set or relied on. Other +description prefixes such as "AWS SDK:" or "bash:" indicate that these variables are used by the prefixed component +and not Geodesic itself. + +| Variable | Description | +|-------------------------------------|----------------------------------------------------------------------------------------| +| `ASSUME_ROLE`* | Internal: Current AWS assume-role name (or profile) in use. | +| `ATMOS_BASE_PATH` | Base path for Atmos configuration (auto-derived if possible). | +| `AWS_CONFIG_FILE` | AWS SDK: Specifies a non-default location for the config file | +| `AWS_DEFAULT_REGION` | AWS SDK: Can override the region setting in the config file | +| `AWS_DEFAULT_SHORT_REGION` | Shortened form of the AWS region (e.g., usw2). | +| `AWS_MFA_PROFILE`* | Name of the AWS MFA profile for the mfa() function. | +| `AWS_REGION_ABBREVIATION_TYPE` | Determines how the AWS region name is shortened (e.g., "fixed"). | +| `AWS_SHARED_CREDENTIALS_FILE` | AWS SDK: Specifies a non-default location for the credentials file | +| `BANNER` | Custom banner text shown on shell startup. | +| `BANNER_COLOR` | ANSI color (escape code) for the banner text. Defaults to cyan. | +| `BANNER_COMMAND` | Command used to display the banner. Defaults to figurine). | +| `BANNER_FONT` | Font to use when BANNER_COMMAND=figurine. | +| `DOCKER_IMAGE` | Docker image name (repo) in use. Used for configuring customizations. | +| `FZF_COLORS` | `fzf`: Chooses the color scheme for the `fzf` interface. | +| `GEODESIC_AWS_HOME` | Docker ARG: Path to the .aws directory (credentials, config) Geodesic should use. | +| `GEODESIC_AWS_ROLE_CACHE` | Internal: Keeps a fingerprint of AWS credentials to update the prompt efficiently. | +| `GEODESIC_BINDFS_OPTIONS` | Extra options passed to bindfs for file ownership mapping. | +| `GEODESIC_CONFIG_HOME` | Base directory for user customizations. Before v4, defaults to /localhost/.geodesic. | +| `GEODESIC_CUSTOMIZATION_DISABLED` | If set to anything but "false", disables user customizations. | +| `GEODESIC_HOST_BINDFS_ENABLED`* | Deprecated (use `MAP_FILE_OWNERSHIP` instead): Enables file ownership mapping. | +| `GEODESIC_HOST_CWD`* | Host’s current working directory (used to set container’s initial cd). | +| `GEODESIC_HOST_GID` | Host group ID for file ownership user/group mapping. | +| `GEODESIC_HOST_UID` | Host user ID for file ownership user/group mapping. | +| `GEODESIC_LOCALHOST`* | Obsolete: Filesystem path to the host mount (/localhost). | +| `GEODESIC_LOCALHOST_DEVICE`* | Obsolete, Internal: Device info for /localhost (helps detect host vs container paths). | +| `GEODESIC_LOCALHOST_MAPPED_DEVICE`* | Obsolete, Internal: Device info if /localhost is mounted via bindfs. | +| `GEODESIC_OS` | OS flavor used by Geodesic (e.g., debian or alpine). | +| `GEODESIC_PORT` | Port exposed to host, to be used by services like Teleport. | +| `GEODESIC_SHELL` | Indicates that this is a running Geodesic shell. | +| `GEODESIC_TF_CMD` | Terraform command name (e.g., terraform or tofu) for Geodesic to use. | +| `GEODESIC_TF_PROMPT_ACTIVE` | Internal: Whether the custom Terraform prompt is active. | +| `GEODESIC_TF_PROMPT_ENABLED` | Toggles the custom Terraform command-line prompt behavior on/off. | +| `GEODESIC_TF_PROMPT_LINE` | Internal: Holds the prompt string indicating the current Terraform workspace. | +| `GEODESIC_TF_PROMPT_TF_NEEDS_INIT` | Internal: Indicates if the current Terraform directory needs initialization. | +| `GEODESIC_TRACE` | Enables logging/tracing of terminal events (e.g., custom, hist). | +| `_GEODESIC_TRACE_CUSTOMIZATION` | Internal: Enables debug tracing of user customization scripts. | +| `GEODESIC_VERSION` | Current Geodesic version string. | +| `GEODESIC_WORKDIR`* | Obsolete: Initial working directory for the container (e.g., /conf or /stacks). | +| `HISTFILE` | `bash`: Path to the Bash command-history file. | +| `HISTFILESIZE` | `bash`: Maximum number of lines to keep in the history file. | +| `HOME` | POSIX: Home directory inside the container (mapped if /localhost exists). | +| `KUBE_PS1_CLUSTER_FUNCTION` | `kube-ps1`: Custom function for cluster-name display in the `kube-ps1` prompt. | +| `KUBE_PS1_PREFIX` | `kube-ps1`: String displayed to the left of the cluster name in `kube-ps1`. | +| `KUBECONFIG` | `kubectl`: Path to the active Kubernetes config file. | +| `LANG` | POSIX: Locale for messages and collation. | +| `LC_ALL` | POSIX: Forces a single locale category (C.UTF-8) for everything. | +| `LOCAL_HOME` | Host user’s $HOME, if mapped into the container. | +| `MOTD_URL` | URL for fetching a “message of the day” at shell startup. | +| `NAMESPACE` | Namespace/environment name shown in the prompt/banner. | +| `PROMPT_COMMAND` | `bash`/Internal: Bash hook that runs before each prompt; extended by Geodesic. | +| `PROMPT_HOOKS` | Internal: Array of functions to call in PROMPT_COMMAND for dynamic prompts. | +| `PROMPT_STYLE` | Style of the prompt: plain, fancy, or unicode. | +| `PS1` | `bash`: Primary prompt string (the final assembled Bash prompt). | +| `SCREEN_SIZE` | Internal: Tracks the current terminal screen size as LINES x COLUMNS. | +| `SHLVL` | `bash`1: Shell nesting level (1 for the main shell, higher for subshells). | +| `SSH_AGENT_CONFIG`* | Obsolete: Path to the file storing SSH agent environment variables. | +| `SSH_AUTH_SOCK` | `ssh`: Socket path for the running SSH agent. | +| `SSH_KEY`* | Path to private SSH key file to automatically add to the SSH agent. | +| `STAGE` | Identifies the environment stage (e.g., dev, prod). | +| `TELEPORT_LOGIN` | `teleport`: Username for Teleport-based SSH sessions. | +| `TELEPORT_LOGIN_BIND_ADDR` | `teleport`: Local bind address for Teleport SAML-based login callbacks. | +| `TELEPORT_PROXY` | `teleport`: Teleport proxy host (defaults to tele.). | + + diff --git a/docs/envrc.md b/docs/envrc.md deleted file mode 100644 index 1a2013c7c..000000000 --- a/docs/envrc.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: envrc (1) | Geodesic -author: -- Erik Osterman -date: May 2019 ---- - -## NAME - -`envrc` - `direnv` configuration file for environment variables - -## SYNOPSIS - -Every time the prompt is displayed, `direnv` checks for the existence of an `.envrc` file in the current and parent directories. If the file exists (and is authorized), it is evaluated in a bash sub-shell where all exported variables are captured and then exported to the current shell. - -## HELPERS - -Geodesic provides a number of helpers. - -### Terraform - -By adding `use terraform` to your `.envrc`, it will set the environment suitable for use with terraform. - -It also supports a version argument to indicate which version of terraform you wish to use in the current project. - -For example, `use terraform 0.11` will set the current path to search `/usr/local/terraform/0.11/bin`. - -The following alpine packages are provided and can be installed by running: - -Install Terraform 0.11 by running: - - `apk add --update terraform_0.11@cloudposse` - -Install Terraform 0.12 by - - `apk add --update terraform_0.12@cloudposse` - - - -## SOURCE - -These commands are defined in `/etc/direnv/rc.d/terraform` diff --git a/docs/kops.md b/docs/kops.md deleted file mode 100644 index becbdb414..000000000 --- a/docs/kops.md +++ /dev/null @@ -1,559 +0,0 @@ ---- -title: KOPS(1) | Geodesic -author: -- Erik Osterman -date: May 2019 ---- - -## NAME - -kops - (Deprecated) Kubernetes Operations (kops) - -## SYNOPSIS - -***Deprecation notice*** CloudPosse no longer supports `kops` for managing Kubernetes clusters. -This document is retained for historical reference, but should not be used for new clusters. -Some binaries, such as `kops` itself, used to be shipped pre-installed in Geodesic, and -some, such as `direnv` and `tfenv` had special support as well. Some of these are no -longer installed, and some may not work as advertised due to bit rot. - -***Historical synopsis*** -Kops is one of the easiest ways to get a production grade Kubernetes cluster up and running. The `kops` command line tool (cli) is like `kubectl` for clusters. It handles all the standard CRUD operations necessary to manage the complete life cycle of a cluster. - -It is possible to run any number of [kops clusters](http://github.com/kubernetes/kops) within an account. Our -current practice is to define one cluster per Geodesic contianer, with all cluster configuration -in the `/conf` folder. We are planning to publish guidelines for how to manage multiple clusters per container, -but that is still a work in progress that will introduce some new patterns. - -## DESCRIPTION - -- This document describes how to set up a single `kops` cluster in a single Geodesic container. -- The described usage pattern corresponds to [Geodesic](https://github.com/cloudposse/geodesic) version 0.95.1, -[Reference Architecture](https://github.com/cloudposse/reference-architectures) version 0.7.0, and -[terraform-root-modules](https://github.com/cloudposse/terraform-root-modules) version 0.71.0 -- Check the versions! Geodesic, `kops`, Kubernetes, and other tools referenced in this documentation are -constantly evolving, and it is likely this documentation will be at least slightly out-of-date within days -of its publication. Also, because it is only updated manually (rather than generated automatically), there will likely -be times when one part of this document intends to reference one version of a resource while another part references -a different version. Please keep these facts in mind when you are using this document to help you set up your own cluster. - -## FEATURES - -- **Automated Provisioning** of Kubernetes clusters in [AWS](https://github.com/kubernetes/kops/blob/master/docs/aws.md) and [GCE](https://github.com/kubernetes/kops/blob/master/docs/tutorial/gce.md) -- **Highly Available (HA)** Kubernetes masters and nodes by using auto-scaling groups -- **Dry-runs & Idempotency** ensure predictable cluster operations -- **Kubernetes Addons** extend the default functionality [add-ons](https://github.com/kubernetes/kops/blob/master/docs/addons.md) -- **Command line Tool** supports all CRUD operations and has [autocompletion](https://github.com/kubernetes/kops/blob/master/docs/cli/kops_completion.md) -- **Declarative Manifests (YAML)** make GitOps style [Configuration](https://github.com/kubernetes/kops/blob/master/docs/manifests_and_customizing_via_api.md) easier -- [Templating](https://github.com/kubernetes/kops/blob/master/docs/cluster_template.md) and dry-run modes for creating manifests -- **Supports Multiple CNIs** providers [out of the box](https://github.com/kubernetes/kops/blob/master/docs/networking.md). -- **Lifecycle Hooks** make it easy to add containers and files to nodes via a [cluster manifest](https://github.com/kubernetes/kops/blob/master/docs/cluster_spec.md) - -## OVERVIEW - -The process of provisioning a new `kops` cluster takes (3) steps. Here's what it looks like: - -1. **Configure the parameter settings** - - Create a new project (e.g. `/conf/kops`). - - Configure parameters in multiple places. - - Rebuild the `geodesic` image. Then restart the shell. -2. **Provision the `kops` dependencies using the [`kops`](https://github.com/cloudposse/terraform-root-modules/tree/master/aws/kops) terraform root module** - - State backend (S3 bucket) that will store the YAML state. - - Cluster DNS zone that will be used by kops for service discovery. - - SSH key-pair to access the Kubernetes masters and nodes. - - Write settings to SSM Parameter Store. -3. **Execute the `kops create` on the manifest file to create the `kops` cluster** - - Build the manifest. - - Validate the cluster is healthy. - - -### Configuration Settings Overview - -We use 2 different mechanisms for storing configuration parameters. - -1. Parameters can be stored in files. This is best for parameters that are not secret, should be under source code -control, and do not need to be shared among "projects." Most parameters should be stored in files. -1. Parameters can be saved in and read from AWS SSM Parameter Store using [Chamber](https://github.com/segmentio/chamber) - or the [Terraform](https://www.terraform.io/) `aws_ssm_parameter` resource. This is best for secrets, as they are protected via AWS IAM, and do - not risk being checked into source code control. We also use this for parameters that are automatically generated - or are used by more than - one project, such as `KOPS_NETOWRK_CIDR`. However access to SSM is severely rate limited, so parameters that - are accessed frequently should be stored somewhere else. For the sake of brevity, we sometimes refer to the AWS SSM Parameter Store - as just "SSM". - -WARNING: Do not define the same parameter in _both_ SSM and `.envrc` files, as this can lead to confusing behavior as -the two definitions compete for precedence. - -SSM parameters are grouped by "prefix" and access to them can be controlled with IAM policies on a per-prefix basis. -`chamber` calls the prefix a "service". For `kops` configuration we use the prefix/service "kops" for -parameters stored in SSM. This means an environment variable named `FOO` (all uppercase) would be set via `chamber write kops foo ` -or `aws ssm put-parameter --name '/kops/foo --value ` (SSM parameter keys used by the tools are all lowercase). - -To facilitate setting configurations via files, and to keep configurations segregated, we create (inside the `/conf` -folder) a folder per "project". Roughly speaking, a "project" is a Terraform module or other group of DevOps scripts -and configurations that form a self-contained unit (though possibly with dependencies on other projects). - - -#### `direnv` - -We use [direnv](https://direnv.net/) to automatically load configurations into enviornment variables -when you `cd` into a directory. Alternatively, they can be executed explicitly by running -`direnv exec $directory $command`. This is useful when running commands as part of a CI/CD GitOps-style pipeline. - -The way `direnv` works is that when you `cd` into a directory, it looks for a file named `.envrc` and if it -finds it, reads and executes the contents of the file. It generally expects to find `bash` style environment -variable assignments of the form -```bash -export NAME=vaule -``` -Any environment variables set in the `.envrc` file are exported into the current environment, and, critically, -removed from the environment when you `cd` out of the directory. - -Normally `direnv` only reads the `.envrc` file, but CloudPosse adds a `use envrc` command to the file, which -causes `direenv` to read all the files in the directory that have `.envrc` extensions. This allows parameters -to be set automatically by `direnv` without requiring that the settings be put in a hidden file. - -##### `/conf/.envrc` - -In the `/conf` directory itself, we put a file named `.envrc` which contains just this one line: -``` -export MAKE_INCLUDES="Makefile Makefile.*" -``` -That supports our general pattern of installing a `Makefile.tasks` file in each Terraform project directory, which -in turn downloads a `Makefile` specific to that Terraform module. - -#### `/conf/kops/` - -We create a "project" folder for the `kops` configuration. Inside this goes a few different configurations files. - -##### `.envrc` - -Every project files gets a `.envrc` file that is automatically loaded by `direnv`. Because this is a hidden -file, we only put in this file the `direnv` commands needed to load and process other configuration files. -Currently, our `.envrc` file contains these commands: -- [`use envrc`](https://github.com/cloudposse/geodesic/blob/0.95.1/rootfs/etc/direnv/rc.d/envrc) tells direnv to load all the files in the directory that have names ending with .envrc -- [`use terraform`](https://github.com/cloudposse/geodesic/blob/0.95.1/rootfs/etc/direnv/rc.d/terraform) maps certain -environment variables to ones that will get passed to Terraform -- [`use atlantis`](https://github.com/cloudposse/geodesic/blob/0.95.1/rootfs/etc/direnv/rc.d/atlantis) maps certain -`atlantis` [environment variables](https://www.runatlantis.io/docs/atlantis-yaml-reference.html#reference) to ones -that will get passed to Terraform -- [`use tfenv`](https://github.com/cloudposse/geodesic/blob/0.95.1/rootfs/etc/direnv/rc.d/tfenv) maps environment -variables to `terraform` command-line flags. - -##### `kops.envrc`, `terraform.envrc` - -We finish configuring the environment by placing commands like `export ENVVAR=value` in files whose names -end with `.envrc`. We name the files and group the commands by component. So in `/conf/kops` we place -`kops.envrc` which gets environment variables needed by `kops` directly (and not stored in SSM) and -`terraform.envrc` which gets environment variables needed by `terraform`. - -##### `terraform.tfvars` - -We also create a `terraform.tfvars` file which holds `terraform` configuration data in the form of assignments to -`terraform` variables. For historical reasons, some `kops` parameters are actually configured in `terraform.tfvars`, -then `terraform` uses this information to create more parameters, which it then stores in SSM. - -### Terraform and Chamber - -We use Terraform to provision resources such as domain names and AWS S3 buckets. For `kops` we also use -it to generate several configuration parameters which Terraform then stores in SSM. However, most tools -that we use cannot read parameters out of SSM, so we use Chamber to read _all_ the parameters out of SSM -and place them in environment variables, which all our tools can use. - -### Where Did That Value Come From? - -Because of the interactions between Docker, Chamber, `direnv`, and Terraform, it can be difficult to determine -where the final value of a parameter originated. This document focuses on the recommended place to set -parameters, but you can dig into some details of alternate possiblities here: -
-Show details -To illustrate the situation, here is an example tracing how -the environment variable `KOPS_PRIVATE_SUBNETS` is set and used: - -1. `KOPS_PRIVATE_SUBNETS` could first be set in Geodesic's Dockerfile -1. We recommend that you do not run Geodesic directly, but rather create a customized Docker container based -on a specific version of Geodesic. So `KOPS_PRIVATE_SUBNETS` could be set in your customized Dockerfile. While -we do not recommend setting `KOPS_PRIVATE_SUBNETS` there, we do recommend setting other parameters there. -1. One of the first things you do inside Geodesic is `assume-role`. While this is only concerned with -AWS credentials, it does set `AWS_REGION` and `AWS_DEFAULT_REGION`, and also sets `TF_VAR_aws_assume_role_arn` -which is used by Terraform as the default value for the Terraform variable `aws_assume_role_arn` -1. Then you `cd /conf/kops` and `direnv` loads all the `.envrc` files and populates the environment, so if you -have `KOPS_PRIVATE_SUBNETS` set in `kops.envrc` (which, because we later set it in SSM, you should not), -then that value is now what is set. -1. Note that `direnv`, through our custom additions of `use terraform` and `use tfenv`, will pass [some of the -environment variables](../rootfs/etc/direnv/rc.d/terraform) to `terraform` as command line arguments and will pass all -of the environment variables as default values for `terraform` variables. -1. Then you `make deps` and `terraform apply`. The Terraform module reads the value of `network_cidr` from -`terraform.tfvars`, computes subnet CIDRs bocks, and stores the value in SSM with the key `/kops/kops_private_subnets`. -1. You run `make kops/shell` to load all the values stored in SSM under the `/kops` prefix into your local environment -by executing `chamber exec kops -- bash -l`. At this point, -the value that Terraform just generated overwrites `KOPS_PRIVATE_SUBNETS` and this is the value going forward. -1. Except, as noted above, if you have also set `KOPS_PRIVATE_SUBNETS` in `kops.envrc`, then if you `cd /conf`, -`direnv` will set `KOPS_PRIVATE_SUBNETS` to the value it had before you did `cd /conf/kops` in step 4, which is the value -from one of the Dockerfiles. And then if you `cd /conf/kops` again, `direnv` will overwrite the value from -SSM with the value from `kops.envrc`. This is why you should not set a parameter in both a file and SSM. - -
- -### Create the cluster - -The following describes how to create a cluster, treating the setting of parameters in various places as -steps in the creation process. - -- Note: Our standard configuration of `kops` isolates the cluster in private subnets (not directly accessible from the -public internet) of a VPC (a configuration commonly called a "Private Cluster"), with the VPC created and managed by -`kops`, and this is what is described here. We have recently added support for deploying a cluster inside a VPC that -is not created or managed by `kops` but created some other way, which we call a "shared VPC". The adjustments needed -to operate in a shared VPC are described below under [Shared VPC](#shared-vpc). - -### Setting the Parameters - -#### Dockerfile - -The Geodesic Dockerfile provides some default values for enviornment variables, but for most of them -it is no longer recommended to rely on them and they may be removed in future versions. (The few we recommend you -continue to get from Geodesic are indicated in the table below with a "Suggested Value" of ``.) -However, we consider it reasonable, if not necessarily -"best", practice for you to define these environment variables in the Dockerfile you use to build your custom -Geodesic container. Note that if you generated your Dockerfile using our [Reference Architecture tools](https://github.com/cloudposse/reference-architectures#get-started), then -these will already be set for you. - -
-Used by `kops` and other tools - -| Environment Variable | Description of the Parameter | Suggested Value | -| -------------------------------------------------- | -------------------------------------------|------------------| -| NAMESPACE | A short string that distinguishes your organization for others, such as "cpco" | _(must be custom)_ | -| STAGE | A short string that indicates an environment with a organization | One of: audit, corp, data, dev, prod, root, staging | -| AWS_DEFAULT_PROFILE | The name of the profile in your `~/.aws/config` to use when executing AWS commands | `"${NAMESPACE}-${STAGE}-admin"` | -| AWS_REGION | The AWS Region to use for AWS commands. Use a region geographically close to you. | `us-west-2` | -
-
-Specific to `kops` - -| Environment Variable | Description of the Parameter | Suggested Value | -| -------------------------------------------------- | -------------------------------------------|------------------| -| KOPS_MANIFEST | Location of the (generated) `kops` manifest file | `` | -| KUBECONFIG | Where to store/find "kubeconfig" file used by `kubectl` | `` -| KUBECONFIG_TEMPLATE | Template file for `build-kubeconfig` to use to create a "kubeconfig" file | `` | -| KOPS_BASTION_PUBLIC_NAME | The hostname part of the Bastion server's domain name | bastion | -
- - -#### `/conf/kops/terraform.envrc`, `/conf/kops/terraform.tfvars` - -Geodesic uses Terraform to set up networking and DNS, computing values that will be used later. - -The `/conf/kops/terraform.envrc` file normally only contains 2 parameters: - -| `terraform.envrc` Variable | Description of the Parameter | Suggested Value | -|--------------------|----------------------------------------------|-----------------| -| TF_CLI_INIT_FROM_MODULE | The URL for the `kops` "Terraform root module" | _see below_ | -| TF_CLI_PLAN_PARALLELISM | The maximum number of concurrent operations as Terraform walks the graph. | 2 | - -- `TF_CLI_INIT_FROM_MODULE` should be a version-pinned reference to the `kops` module of the -CloudPosse [Terraform Root Modules repo](https://github.com/cloudposse/terraform-root-modules). For this -version of the documentation, it should be `git::https://github.com/cloudposse/terraform-root-modules.git//aws/kops?ref=tags/0.71.0`, -which should match the "terraform-root-modules" version stated above under [Usage](#usage). - - -The remaining -parameters should be configured in `/conf/kops/terraform.tfvars`. If you generated your Dockerfile using our -[Reference Architecture tools](https://github.com/cloudposse/reference-architectures#get-started), then -all these values will already be set for you. - -
-Terraform Variables - -| Terraform Variable | Description of the Parameter | Suggested Value | -|--------------------|----------------------------------------------|-----------------| -| network_cidr | The CIDR network block to use for the cluster | _see below_ | -| kops_non_masquerade_cidr | The CIDR network block to use for communication inside the cluster | `100.64.0.0/10` | -| zone_name | The DNS name of the DNS Zone in which to place the cluster | _see below_ | -| region | The AWS region where the cluster should be created. | Should be the same as `$AWS_REGION` | - -##### Notes: -- Our reference architecture expects there to be a "parent domain", typically a second-level domain name such as `cpco.io`, - that all services for the entire organization are under. The `zone_name` is the domain for all services in this - specific account, and we recommend `${STAGE}.`. -- The `kops` cluster will be named `${AWS_REGION}.${ZONE_NAME}`. Unfortunately, CloudPosse documentation and tools - are inconsistent in their use of `zone_name` and `dns_zone` because their usage has evolved over the years, and - some tools enforce the convention that the cluster - name is `${AWS_REGION}.${ZONE_NAME}`, while others have a variable called `dns_zone` but in fact require it to be - set to the full cluster name. Please use extra care when you encounter these variable names elsewhere. -- If you are using a [Shared VPC](#shared-vpc), you should leave `network_cidr` unset and add `create_vpc = "false"` -- There are additional parameters that can be set, but usually the defaults are good. For full details, - see the [source files](https://github.com/cloudposse/terraform-root-modules/blob/master/aws/kops/variables.tf). -
- -#### `/conf/kops/kops.envrc` - -We create a [`kops`](https://github.com/kubernetes/kops) cluster from a manifest. We create the manifest -by combining environment variables with a template, which is itself selected by an environment variable. -The following environment variables can be set in `/conf/kops/kops.envrc`. None are required, but for stability -you should at a minimum set the following variables. - - -| Critical Environment Variables | Description of the Parameter | Recommended value | -| ------------------------------- | ---------------------------------------------------------------------------------------------- |-------------------| -| KOPS_TEMPLATE | Location of kops manifest go-template (gomplate) that describes the cluster, may be a URL | _see below_ | -| KUBERNETES_VERSION | Version of Kubernetes to install | 1.11.9 | -| KOPS_BASE_IMAGE | The AWS [AMI to use](https://github.com/kubernetes/kops/blob/master/docs/images.md) when creating EC2 instances. | _see below_ | -| KOPS_AUTHORIZATION_RBAC_ENABLED | Set to "true" to enable [Kubernetes RBAC](https://kubernetes.io/blog/2017/10/using-rbac-generally-available-18/) (strongly recommended) | true | -| BASTION_MACHINE_TYPE | AWS Instance type for the Bastion server | t3.small | -| MASTER_MACHINE_TYPE | AWS Instance type for the Kubernetes master nodes | t3.medium | -| NODE_MACHINE_TYPE | AWS Instance type for the Kubernetes worker nodes | t3.medium | -| NODE_MAX_SIZE | Maximum number of EC2 instances in the _default_ node pool | 2 | -| NODE_MIN_SIZE | Minimum number of EC2 instances in the _default_ node pool | 2 | - -- Notes: -1. `KOPS_TEMPLATE` used to point to a file in the local file system by default, but we now recommend using a URL -pointing to a specific version of the template published in our [Reference Architecture GitHub](https://github.com/cloudposse/reference-architectures). -Be sure to use the "raw text" URL from GitHub, not the HTML URL. At the time of this writing, the recommended value for `KOPS_TEMPLATE` is - `https://raw.githubusercontent.com/cloudposse/reference-architectures/0.7.0/templates/kops/kops-private-topology.yaml.gotmpl` -1. `KOPS_BASE_IMAGE` refers to one of the official AWS AMI's provided by `kops`. -For more details, refer to the [official documentation](https://github.com/kubernetes/kops/blob/master/docs/images.md). -Additionally, the [latest stable images](https://github.com/kubernetes/kops/blob/master/channels/stable) are published -on their GitHub. At the time of this writing, the recommended value of `KOPS_BASE_IMAGE` is -`kope.io/k8s-1.11-debian-jessie-amd64-hvm-ebs-2018-08-17` - -There are some other environment variables you can set in `/conf/kops/kops.envrc` but the defaults are usually sufficient. -
-Other Environment Variables - -| Environment Variable | Description of the setting | -| -------------------------------------------------- | ------------------------------------------------------------------- | -| KOPS_API_LOAD_BALANCER_IDLE_TIMEOUT_SECONDS | AWS ELB idle connection timeout for the API load balancer | -| KOPS_AWS_IAM_AUTHENTICATOR_ENABLED | Toggle IAM Authenticator support | -| KOPS_BASTION_PUBLIC_NAME | Hostname that will be used for the bastion instance | -| KOPS_CLOUDWATCH_DETAILED_MONITORING | Toggle detailed CloudWatch monitoring (increases operating costs) | -| KOPS_CLUSTER_AUTOSCALER_ENABLED | Toggle the Kubernetes node autoscaler capability | -| KOPS_FEATURE_FLAGS | Enable experimental features that are not available by default | -| KOPS_KUBE_API_SERVER_AUTHORIZATION_MODE | Ordered list of plug-ins to do authorization on secure port | - -**IMPORTANT:** - -1. `KOPS_KUBE_API_SERVER_AUTHORIZATION_MODE` is a comma-separated list (e.g.`AlwaysAllow,AlwaysDeny,ABAC,Webhook,RBAC,Node`) -4. `KOPS_FEATURE_FLAGS` are [published on their GitHub](https://github.com/kubernetes/kops/blob/master/docs/experimental.md) - -
- -#### Terraform generated configuration - -In addition to creating AWS resources, Terraform generates the following parameters that it stores in SSM (using -lowercase names) and which then can be exported into the shell environment as environment variables using Chamber. -It is best that you do NOT configure these manually, but if you do need to change the values, you need to -change them in SSM **after** using Terraform to generate your AWS resources, and be aware that if `terraform` -is run again, it will overwrite the values you set. - -
-Parameters Generated by Terraform - -| SSM/Environment Variable | Description of the Parameter | -| -------------------------------------------------- | -------------------------------------------| -| KOPS_CLUSTER_NAME | The cluster name used by `kops` | -| KOPS_STATE_STORE | The name of the S3 bucket where `kops` stores its state files | -| KOPS_STATE_STORE_REGION | The AWS region where `kops` stores its state files | -| KOPS_DNS_ZONE | the Route53 hosted zone in which `kops` will create DNS records | -| KOPS_NETWORK_CIDR | CIDR block of the kops virtual network | -| KOPS_PRIVATE_SUBNETS | CIDR blocks of the kops private subnets | -| KOPS_UTILITY_SUBNETS | CIDR block of the kops utility (public) subnets | -| KOPS_NON_MASQUERADE_CIDR | The CIDR block for pod IPs | -| KOPS_AVAILABILITY_ZONES | The AWS Availability Zones in which the cluster will be provisioned | -
- -### Shared VPC - -Normally, `kops` creates and manages its own VPC. However, you may want to create a shared VPC that `kops` will -not modify, in order to more easily add other services to it. We provide a [VPC](https://github.com/cloudposse/terraform-root-modules/tree/master/aws/vpc) -Terraform module that creates a VPC and stores in SSM the information `kops` needs in order to use it. Details -instructions on how to use that module are beyond the scope of this document, but briefly, you can just use -it following the same pattern used for other "Terraform root modules". The key configuration items are: -- Remove `network_cidr` from `/conf/kops/terraform.tfvars` and copy its value to `vpc_cidr_block` in -`/conf/vpc/terraform.tfvars` -- Set `create_vpc = "false"` in `/conf/kops/terraform.tfvars` - -Be sure to create the VPC first, before provisioning anything relating to `kops` - -### Provisioning Resources - -Now that you have all the configuration set, build your custom Geodesic container, start it, and go through the following -steps from within the container. - -- `assume-role` to assume an IAM role with appropriate permissions -- `cd /conf/kops` not only changes your working directory, but causes `direnv` to set up your environment variables -- `make deps` loads Terraform modules, configures Terraform state storage, and downloads a Makefile for the next steps -- `terraform apply` (type "yes" when prompted) creates S3 bucket, DNS zone, and SSH keypair for use by `kops` - -At this point, there are new settings (generated by Terraform), and you need to take steps to load them into your -shell environment. -- `make kops/shell` puts you into a subshell with the new settings loaded into your environment - -From here forward you need to make sure you are continuing to operate from within this subshell. When the Geodesic -command line prompt is 2 lines, a `+` at the end of the first line lets you know that the `kops` parameters are loaded. - -- `make kops/build-manifest` creates the `$KOPS_MANIFEST` file -- `make kops/create` loads the manifest into `kops` -- `make kops/create-secret-sshpublickey` loads the SSH key into `kops` (for communicating with the EC2 instances) -- `make kops/apply` actually creates the cluster - -The cluster make take 5-10 minutes to fully come on line. -- `kops validate cluster` or `make kops/validate` to check on the status of the cluster. Once it has validated, -your cluster is up and running and ready to use. - - -## Operating the Cluster - -Use the `kubectl` command to interact with the Kubernetes cluster. To use the `kubectl` command -(_e.g._ `kubectl get nodes`, `kubectl get pods`), you need to first export the `kubecfg` configuration settings -from the cluster. - -Run the following command to export `kubecfg` settings needed to connect to the cluster: - -```bash -kops export kubecfg -``` - -**IMPORTANT:** You need to run this command every time you start a new shell and before you interact with the cluster -(e.g. before running `kubectl`). By default, we set the `KUBECONFIG=/dev/shm/kubecfg` (shared memory based filesystem) -so that it never touches disk and is wiped out when the shell exits. Also, before you run `kops export kubecfg` you -need to have run `assume-role` and `make kops/shell`, in order to have the necessary credentials available for -`kops` to be able to generate the `kubecfg`. - -See the documentation for [`kubecfg` settings for `kubectl`](https://github.com/kubernetes/kops/blob/master/docs/kubectl.md) -for more details. - -
Show Example Output - -Below is an example of what it should _roughly_ look like (IPs and Availability Zones may differ). - -``` -⨠ kops validate cluster - -Validating cluster us-west-2.example.company.co - -INSTANCE GROUPS -NAME ROLE MACHINETYPE MIN MAX SUBNETS -bastions Bastion t2.medium 1 1 utility-us-west-2a,utility-us-west-2d,utility-us-west-2c -master-us-west-2a Master t2.medium 1 1 us-west-2a -master-us-west-2c Master t2.medium 1 1 us-west-2c -master-us-west-2d Master t2.medium 1 1 us-west-2d -nodes Node t2.medium 2 2 us-west-2a,us-west-2d,us-west-2c - -NODE STATUS -NAME ROLE READY -ip-172-20-108-58.us-west-2.compute.internal node True -ip-172-20-125-166.us-west-2.compute.internal master True -ip-172-20-62-206.us-west-2.compute.internal master True -ip-172-20-74-158.us-west-2.compute.internal master True -ip-172-20-88-143.us-west-2.compute.internal node True - -Your cluster us-west-2.example.company.co is ready -``` - -
-
- -Run the following command to list all nodes: - -```bash -kubectl get nodes -``` - -
Show Example Output - -Below is an example of what it should _roughly_ look like (IPs and Availability Zones may differ). - -``` -⨠ kubectl get nodes -NAME STATUS ROLES AGE VERSION -ip-172-20-108-58.us-west-2.compute.internal Ready node 15m v1.11.9 -ip-172-20-125-166.us-west-2.compute.internal Ready master 17m v1.11.9 -ip-172-20-62-206.us-west-2.compute.internal Ready master 18m v1.11.9 -ip-172-20-74-158.us-west-2.compute.internal Ready master 17m v1.11.9 -ip-172-20-88-143.us-west-2.compute.internal Ready node 16m v1.11.9 -``` - -
-
- -Run the following command to list all pods: - -```bash -kubectl get pods --all-namespaces -``` - -
Show Example Output - -Below is an example of what it should _roughly_ look like (IPs and Availability Zones may differ). - -``` -⨠ kubectl get pods --all-namespaces -NAMESPACE NAME READY STATUS RESTARTS AGE -kube-system calico-kube-controllers-69c6bdf999-7sfdg 1/1 Running 0 1h -kube-system calico-node-4qlj2 2/2 Running 0 1h -kube-system calico-node-668x9 2/2 Running 0 1h -kube-system calico-node-jddc9 2/2 Running 0 1h -kube-system calico-node-pszd8 2/2 Running 0 1h -kube-system calico-node-rqfbk 2/2 Running 0 1h -kube-system dns-controller-75b75f6f5d-tdg9s 1/1 Running 0 1h -kube-system etcd-server-events-ip-172-20-125-166.us-west-2.compute.internal 1/1 Running 0 1h -kube-system etcd-server-events-ip-172-20-62-206.us-west-2.compute.internal 1/1 Running 2 1h -kube-system etcd-server-events-ip-172-20-74-158.us-west-2.compute.internal 1/1 Running 0 1h -kube-system etcd-server-ip-172-20-125-166.us-west-2.compute.internal 1/1 Running 0 1h -kube-system etcd-server-ip-172-20-62-206.us-west-2.compute.internal 1/1 Running 2 1h -kube-system etcd-server-ip-172-20-74-158.us-west-2.compute.internal 1/1 Running 0 1h -kube-system kube-apiserver-ip-172-20-125-166.us-west-2.compute.internal 1/1 Running 0 1h -kube-system kube-apiserver-ip-172-20-62-206.us-west-2.compute.internal 1/1 Running 3 1h -kube-system kube-apiserver-ip-172-20-74-158.us-west-2.compute.internal 1/1 Running 0 1h -kube-system kube-controller-manager-ip-172-20-125-166.us-west-2.compute.internal 1/1 Running 0 1h -kube-system kube-controller-manager-ip-172-20-62-206.us-west-2.compute.internal 1/1 Running 0 1h -kube-system kube-controller-manager-ip-172-20-74-158.us-west-2.compute.internal 1/1 Running 0 1h -kube-system kube-dns-5fbcb4d67b-kp2pp 3/3 Running 0 1h -kube-system kube-dns-5fbcb4d67b-wg6gv 3/3 Running 0 1h -kube-system kube-dns-autoscaler-6874c546dd-tvbhq 1/1 Running 0 1h -kube-system kube-proxy-ip-172-20-108-58.us-west-2.compute.internal 1/1 Running 0 1h -kube-system kube-proxy-ip-172-20-125-166.us-west-2.compute.internal 1/1 Running 0 1h -kube-system kube-proxy-ip-172-20-62-206.us-west-2.compute.internal 1/1 Running 0 1h -kube-system kube-proxy-ip-172-20-74-158.us-west-2.compute.internal 1/1 Running 0 1h -kube-system kube-proxy-ip-172-20-88-143.us-west-2.compute.internal 1/1 Running 0 1h -kube-system kube-scheduler-ip-172-20-125-166.us-west-2.compute.internal 1/1 Running 0 1h -kube-system kube-scheduler-ip-172-20-62-206.us-west-2.compute.internal 1/1 Running 0 1h -kube-system kube-scheduler-ip-172-20-74-158.us-west-2.compute.internal 1/1 Running 0 1h -``` - -
-
-
- -### Tips & Tricks - -1. Use `kubens` to easily change your namespace context -2. Use `kubectx` to easily change between kubernetes cluster contexts -3. Use `kubeon` and `kubeoff` to enable the fancy kubernetes prompt - -### Upgrade a Cluster - -To upgrade the cluster or change settings (_e.g_. number of nodes, instance types, Kubernetes version, etc.): - -1. Update the settings in the `.envrc` files for the corresponding kops project -2. Rebuild Docker image (`make docker/build`) -3. Run `geodesic` shell (e.g. by running the wrapper script `example.company.co`) - - assume role (`assume-role`) - - change directory to the `/conf/kops` folder (or whichever project folder contains your kops configurations) - - `make kops/shell` -4. Run `kops export kubecfg` to get the cluster context -1. Run `make kops/build-manifest` to create a new `manifest.yaml` -5. Run `kops replace -f manifest.yaml` to replace the cluster resources (update state) -6. Run `kops update cluster` to view a plan of changes -7. Run `kops update cluster --yes` to apply pending changes -8. Run `kops rolling-update cluster` to view a plan of changes -9. Run `kops rolling-update cluster --yes --force` to force a rolling update (replace EC2 instances) - -## REFERENCES - -- https://github.com/kubernetes/kops/blob/master/docs/manifests_and_customizing_via_api.md - -## GETTING HELP - -Did you get stuck? Find us on [slack](https://slack.cloudposse.com) in the `#geodesic` channel. diff --git a/docs/kudo.md b/docs/kudo.md deleted file mode 100644 index 81c8afc18..000000000 --- a/docs/kudo.md +++ /dev/null @@ -1,23 +0,0 @@ -#### `chudo`/ `kudo` and `chundo`/`kundo` -Named in reference to the `sudo` command and "undo" operation, these commands replace 2 previous commands that create subshells with secrets that enable `kops` to control a Kubernetes cluster. - -Previously: -``` -cd /conf/kops -make deps -make kops/shell -``` -or -``` -chamber exec kops -- bash -l -``` -Now: `kudo` or `chdo kops` import those secrets into the current shell rather than creating a subshell. - -Previously: you removed the secrets from the shell by exiting the shell. -Now: `kundo` or `chundo kops` remove the secrets from the current shell. - -`kudo` is shorthand to just import the `chamber` secrets in the `kops` service. `chudo` is more generic, taking a list of services, e.g. `chudo kops datadog codefresh`. Likewise, `chundo` takes a list of services, and `kundo` is just an alias for `chundo kops`. - -#### Source - -These commands are defined in [aliases.sh](../rootfs/etc/profile.d/aliases.sh) diff --git a/docs/kundo.md b/docs/kundo.md deleted file mode 120000 index 652c48a14..000000000 --- a/docs/kundo.md +++ /dev/null @@ -1 +0,0 @@ -kudo.md \ No newline at end of file diff --git a/docs/wrapper.md b/docs/wrapper.md new file mode 100644 index 000000000..2f724ea1f --- /dev/null +++ b/docs/wrapper.md @@ -0,0 +1,30 @@ +--- +title: wrapper(7) | Geodesic +author: +- Nuru +date: January 2025 +--- + + +## Name + + Wrapper - Geodesic wrapper script + +## Synopsis + +The wrapper is an executable shell script installed to launch a Geodesic container. +It is responsible for setting up the relationship between the host and the container, +such as mounting directories, exposing a TCP port, and forwarding the SSH agent. + +## Description + +Geodesic comes in 2 parts. The bulk of it is a Docker container image that contains a collection of tools and utilities +you hopefully will find useful. The other part is a wrapper script that makes it easy to interact with the container. +This is referred to as "the wrapper" in many places in the documentation. + +When you "install" Geodesic, what you are actually installing is the wrapper script. The wrapper script is a shell script +pre-configured with the Docker image to use and other configuration. The wrapper name is configured +in the Makefile as `APP_NAME`, and for the basic Geodesic image is `geodesic`. Users are encouraged +to customize Geodesic and give it their own name. + +In the documentation, wherever you see `geodesic`, you can replace it with your own `APP_NAME`. diff --git a/os/alpine/Dockerfile.alpine b/os/alpine/Dockerfile.alpine index 9609784b3..2121db6c4 100644 --- a/os/alpine/Dockerfile.alpine +++ b/os/alpine/Dockerfile.alpine @@ -241,7 +241,7 @@ RUN helm3 plugin install https://github.com/databus23/helm-diff.git --version v$ # Configure host AWS configuration to be available from inside Docker image # # AWS_DATA_PATH is a PATH-like variable for configuring the AWS botocore library to -# load additional modules. Do not set it. ENV AWS_DATA_PATH=/localhost/.aws +# load additional modules. Do not set it. ARG GEODESIC_AWS_HOME=/localhost/.aws ENV AWS_CONFIG_FILE=${GEODESIC_AWS_HOME}/config ENV AWS_SHARED_CREDENTIALS_FILE=${GEODESIC_AWS_HOME}/credentials diff --git a/os/debian/Dockerfile.debian b/os/debian/Dockerfile.debian index 6b880724d..63dc04197 100644 --- a/os/debian/Dockerfile.debian +++ b/os/debian/Dockerfile.debian @@ -97,11 +97,10 @@ RUN for dir in $XDG_DATA_HOME $XDG_CONFIG_HOME $XDG_CACHE_HOME; do \ ENV BANNER "geodesic" -ENV MOTD_URL=http://geodesic.sh/motd -ENV HOME=/conf - # Install all packages as root USER root +# We used to override user home directory to /conf, but we no longer do that. +ENV HOME=/root # Keep dpkg quiet about running non-interactively RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections @@ -134,8 +133,7 @@ RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages. # Install packages (but only explicitly listed ones) RUN apt-get update && apt-get install -y --no-install-recommends \ $(grep -h -v '^#' /etc/apt/packages.txt /etc/apt/packages-debian.txt | sed -E 's/@(cloudposse|community|testing)//g' ) && \ - mkdir -p /etc/bash_completion.d/ /etc/profile.d/ /conf && \ - touch /conf/.gitconfig + mkdir -p /etc/bash_completion.d/ /etc/profile.d/ # Install `tofu` as an alternative to `terraform`, if it is available. # Set priority to 5, which is lower than any other Cloud Posse Terraform package, @@ -163,17 +161,15 @@ WORKDIR /tmp COPY --from=python /usr/local/ /usr/local/ # Explicitly set KUBECONFIG to enable kube_ps1 prompt -ENV KUBECONFIG=/conf/.kube/config +ENV KUBECONFIG="${HOME}/.kube/config" # Install an empty kubeconfig to suppress some warnings -COPY rootfs/conf/.kube/config /conf/.kube/config +COPY rootfs/etc/kubeconfig "${KUBECONFIG}" # Set mode on kubeconfig to suppress some warnings while installing tools RUN chmod 600 $KUBECONFIG # # Install kubectl # -# Set KUBERNETES_VERSION and KOPS_BASE_IMAGE in /conf/kops/kops.envrc -# RUN kubectl completion bash > /etc/bash_completion.d/kubectl.sh # https://github.com/ahmetb/kubectx/releases @@ -230,8 +226,8 @@ RUN helm3 plugin install https://github.com/databus23/helm-diff.git --version v$ # Configure host AWS configuration to be available from inside Docker image # # AWS_DATA_PATH is a PATH-like variable for configuring the AWS botocore library to -# load additional modules. Do not set it. ENV AWS_DATA_PATH=/localhost/.aws -ARG GEODESIC_AWS_HOME=/localhost/.aws +# load additional modules. Do not set it. +ARG GEODESIC_AWS_HOME=${HOME}/.aws ENV AWS_CONFIG_FILE=${GEODESIC_AWS_HOME}/config ENV AWS_SHARED_CREDENTIALS_FILE=${GEODESIC_AWS_HOME}/credentials # Region abbreviation types are "fixed" (always 3 chars), "short" (4-5 chars), or "long" (the full AWS string) @@ -330,7 +326,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends pandoc && \ RUN for dir in $XDG_DATA_HOME $XDG_CONFIG_HOME $XDG_CACHE_HOME; do \ chmod -R a+rwX $dir; done -WORKDIR /conf +RUN mkdir /workspace +WORKDIR /workspace ENTRYPOINT ["/bin/bash"] CMD ["-c", "boot"] @@ -338,3 +335,4 @@ CMD ["-c", "boot"] ARG DEV_VERSION ENV GEODESIC_DEV_VERSION=$DEV_VERSION ENV GEODESIC_VERSION="${GEODESIC_VERSION}${GEODESIC_DEV_VERSION:+ (${GEODESIC_DEV_VERSION})}" + diff --git a/os/debian/packages-debian.txt b/os/debian/packages-debian.txt index 774613c4d..a46d8993a 100644 --- a/os/debian/packages-debian.txt +++ b/os/debian/packages-debian.txt @@ -13,8 +13,9 @@ ldnsutils # locales net-tools netcat-openbsd -oathtool +psmisc procps +tmate wget # Not installing keybase diff --git a/rootfs/conf/.emacs b/rootfs/conf/.emacs deleted file mode 120000 index 441c4b4b9..000000000 --- a/rootfs/conf/.emacs +++ /dev/null @@ -1 +0,0 @@ -/localhost/.emacs \ No newline at end of file diff --git a/rootfs/conf/.inputrc b/rootfs/conf/.inputrc deleted file mode 120000 index 4671859b3..000000000 --- a/rootfs/conf/.inputrc +++ /dev/null @@ -1 +0,0 @@ -/localhost/.inputrc \ No newline at end of file diff --git a/rootfs/conf/.kube/config b/rootfs/conf/.kube/config deleted file mode 100644 index 7eb3890ab..000000000 --- a/rootfs/conf/.kube/config +++ /dev/null @@ -1,2 +0,0 @@ -# This is a placeholder Kuberentes configuration file ("kubeconfig") to suppress some warnings. -# We recommend placing real kubeconfig files under /dev/shm to reduce the chance of accidental exposure. \ No newline at end of file diff --git a/rootfs/etc/init.d/atlantis.sh b/rootfs/etc/init.d/atlantis.sh index 50f9796c4..10fbfcd36 100755 --- a/rootfs/etc/init.d/atlantis.sh +++ b/rootfs/etc/init.d/atlantis.sh @@ -52,7 +52,7 @@ if [ "${ATLANTIS_ENABLED}" == "true" ]; then # Set some defaults if none provided export ATLANTIS_USER=${ATLANTIS_USER:-atlantis} export ATLANTIS_GROUP=${ATLANTIS_GROUP:-atlantis} - export ATLANTIS_HOME=${ATLANTIS_HOME:-/conf/atlantis} + export ATLANTIS_HOME=${ATLANTIS_HOME:-/home/atlantis} # create atlantis user & group (getent group ${ATLANTIS_GROUP} || addgroup ${ATLANTIS_GROUP}) >/dev/null diff --git a/rootfs/etc/kubeconfig b/rootfs/etc/kubeconfig new file mode 100644 index 000000000..138f05221 --- /dev/null +++ b/rootfs/etc/kubeconfig @@ -0,0 +1,3 @@ +# This is a placeholder Kubernetes configuration file ("kubeconfig") to suppress some warnings. +# We recommend placing kubeconfig files that contain client keys under /dev/shm to reduce the chance of accidental exposure. +# Alternatively, you can use your own configuration from the host's $HOME/.kube/config. diff --git a/rootfs/etc/motd b/rootfs/etc/motd deleted file mode 100644 index 8b0a55488..000000000 --- a/rootfs/etc/motd +++ /dev/null @@ -1,11 +0,0 @@ - -IMPORTANT: -# Unless there were errors reported above, -# * Your host $HOME directory should be available under `/localhost` -# * Your host AWS configuration and credentials should be available -# * Use Leapp on your host computer to manage your credentials -# * Leapp is free, open source, and available from https://leapp.cloud -# * Use AWS_PROFILE environment variable to manage your AWS IAM role -# * You can interactively select AWS profiles via the `assume-role` command - - diff --git a/rootfs/etc/motd.sh b/rootfs/etc/motd.sh new file mode 100644 index 000000000..da37996c5 --- /dev/null +++ b/rootfs/etc/motd.sh @@ -0,0 +1,15 @@ +# This is the local message of the day (motd) that is displayed when you log into the container. +# It is sourced from /etc/profile.d/motd.sh as a bash script so that it can interpolate variables. + +cat << EOF +IMPORTANT: +# Unless there were errors reported above, +# * Configuration from your host \$HOME directory should be available +# * under both \`${LOCAL_HOME}\` and \`${HOME}\`. +# * Your AWS configuration should be available at \`${AWS_CONFIG_FILE}\`. +# * Your host AWS credentials should be available. +# * Use the AWS_PROFILE environment variable to manage your AWS IAM role, or +# * you can interactively select AWS profiles via the \`assume-role\` command, +# * which will launch a subshell with your selected profile set. + +EOF diff --git a/rootfs/etc/profile.d/_01-launch-warning.sh b/rootfs/etc/profile.d/_01-launch-warning.sh new file mode 100644 index 000000000..66b60b29b --- /dev/null +++ b/rootfs/etc/profile.d/_01-launch-warning.sh @@ -0,0 +1,63 @@ +# Files in the profile.d directory are executed by the lexicographical order of their file names. +# This file is named _01-launch-warning.sh. The leading underscore is needed to ensure this file executes before +# other files with alphabetical names. The number portion is to ensure proper ordering among +# the high-priority scripts. +# +# This file has no dependencies and does not strictly need to come first, +# but it is nice to have the warnings come before other output. + +# In case this output is being piped into a shell, print a warning message + +# Specifically, this guards against: +# docker run -it cloudposse/geodesic:latest-debian | bash + +# If the warning message is longer than the terminal width, +# on some terminals the message may be truncated. In that case, +# all the erasure characters will not be printed, and the message will not be erased. +# So we take pains to make the printed message appear short on the terminal. +# We do that in part by using backspaces to erase the message one character at a time, +# so the cursor never advances. Then we eval the message with backspaces removed. +# We have to then add some extra characters to erase the eval command. +function warn_if_piped() { + local saved_stty + saved_stty=$(stty -g) + trap 'stty "$saved_stty"' EXIT + + stty -echo -opost + + local mesg cmd xb m fx bx feval beval + mesg='printf "\\n\\rIf piping Geodesic output into a shell, do not attach a terminal (-t flag)\\n\\n\\r" >&2; exit 8; ' + cmd="printf \"m='\$m'; eval \\\${m//\$'\\b'/}\"" + + xb=0 + m="" + for i in $(seq 1 ${#mesg}); do + m+="${mesg:$i-1:1}" + [[ "${mesg:$i-1:1}" == '\' ]] && xb=$((xb + 1)) || m+=$'\b' + done + + fx="" + bx="" + for i in $(seq 1 $xb); do + bx+=$'\b' + fx+=" " + done + m+=$bx + m+=$fx + m+=$bx + + # Cover the eval command itself. Tough to compute the exact number of backspaces needed, + # due to escapes and non-printing characters. So we just add a few extra, because + # extra backspaces are ignored. + feval=$(printf "%*s" 18) + beval="${feval// /$'\b'}" + + eval "$cmd" + echo -n "$beval$feval$beval" + + stty "$saved_stty" + trap - EXIT +} + +warn_if_piped +unset -f warn_if_piped diff --git a/rootfs/etc/profile.d/_07-term-mode.sh b/rootfs/etc/profile.d/_07-term-mode.sh index 924c3ff32..dc0e36233 100644 --- a/rootfs/etc/profile.d/_07-term-mode.sh +++ b/rootfs/etc/profile.d/_07-term-mode.sh @@ -14,13 +14,53 @@ # # At some point we may introduce other methods to determine the terminal's color scheme. +# First, at startup, let's try an OSC query. If we get no response, we will assume light mode +# and disable further queries. + +function _verify_terminal_queries_are_supported() { + if tty -s && [[ -n "$(tput setaf 1 2>/dev/null)" ]]; then + local saved_state x fg_rgb bg_rgb exit_code + saved_state=$(stty -g) + trap 'stty "$saved_state"' EXIT + [[ $GEODESIC_TRACE =~ "terminal" ]] && echo "$(tput setaf 1)* TERMINAL TRACE: Checking if terminal responds to color queries...$(tput sgr0)" >&2 + stty -echo + echo -ne '\e]10;?\a\e]11;?\a' >/dev/tty + # If 2 seconds is not enough at startup, then the terminal is either non-responsive or too slow. + IFS=: read -rs -t 2 -d $'\a' x fg_rgb + exit_code=$? + [[ $exit_code -gt 128 ]] || exit_code=0 + IFS=: read -rs -t 0.5 -d $'\a' x bg_rgb + ((exit_code += $?)) + stty "$saved_state" + trap - EXIT + if [[ $exit_code -gt 128 ]] || [[ -z $fg_rgb ]] || [[ -z $bg_rgb ]]; then + if [[ $GEODESIC_TRACE =~ "terminal" ]]; then + echo "$(tput setaf 1)* TERMINAL TRACE: Terminal did not respond to OSC 10 and 11 queries. Disabling color mode detection.$(tput sgr0)" >&2 + fi + export GEODESIC_TERM_COLOR_AUTO=unsupported + fi + fi +} + +_verify_terminal_queries_are_supported + # Normally this function produces no output, but with -b, it outputs "true" or "false", # with -bb it outputs "true", "false", or "unknown". (Otherwise, unknown assume light mode.) # With -m it outputs "dark" or "light", with -mm it outputs "dark", "light", or "unknown". # and always returns true. With -l it outputs integer luminance values for foreground # and background colors. With -ll it outputs labels on the luminance values as well. function _is_term_dark_mode() { - local x fg_rgb bg_rgb fg_lum bg_lum + [[ ${GEODESIC_TERM_COLOR_AUTO} == "unsupported" ]] && case "$1" in + -b) echo "false" ;; + -bb) echo "unknown" ;; + -m) echo "light" ;; + -mm) echo "unknown" ;; + -l) echo "0 1000000000" ;; + -ll) echo "Foreground luminance: 0, Background luminance: 1000000000" ;; + *) return 1 ;; + esac && return 0 + + local x fg_rgb bg_rgb fg_lum bg_lum exit_code saved_state timeout_duration # Do not try to auto-detect if we are not in a terminal # or if termcap does not think we are in a color terminal @@ -28,18 +68,36 @@ function _is_term_dark_mode() { # Extract the RGB values of the foreground and background colors via OSC 10 and 11. # Redirect output to `/dev/tty` in case we are in a subshell where output is a pipe, # because this output has to go directly to the terminal. + saved_state=$(stty -g) + trap 'stty "$saved_state"' EXIT + [[ $GEODESIC_TRACE =~ "terminal" ]] && echo "$(tput setaf 1)* TERMINAL TRACE: Checking terminal color scheme...$(tput sgr0)" >&2 stty -echo echo -ne '\e]10;?\a\e]11;?\a' >/dev/tty - IFS=: read -t 0.1 -d $'\a' x fg_rgb - IFS=: read -t 0.1 -d $'\a' x bg_rgb - stty echo + # Timeout of 2 was not enough when waking for sleep. + # The second read should be part of the first response, should not need much time at all regardless. + # When in a signal handler, we might be waking from sleep or hibernation, so we give it a lot more time. + timeout_duration=$([[ ${GEODESIC_TERM_COLOR_SIGNAL} == "true" ]] && echo 30 || echo 1) + IFS=: read -rs -t "$timeout_duration" -d $'\a' x fg_rgb + exit_code=$? + [[ $exit_code -gt 128 ]] || [[ -z $fg_rgb ]] && [[ ${GEODESIC_TERM_COLOR_SIGNAL} == "true" ]] && export GEODESIC_TERM_COLOR_AUTO=disabled + [[ $exit_code -gt 128 ]] || exit_code=0 + IFS=: read -rs -t 0.5 -d $'\a' x bg_rgb + ((exit_code += $?)) + stty "$saved_state" + trap - EXIT else if [[ $GEODESIC_TRACE =~ "terminal" ]]; then echo "* TERMINAL TRACE: ${FUNCNAME[0]} called, but not running in a color terminal." >&2 fi fi - if [[ -z $fg_rgb ]] || [[ -z $bg_rgb ]]; then + if [[ ${GEODESIC_TERM_COLOR_SIGNAL} == "true" ]] && [[ ${GEODESIC_TERM_COLOR_AUTO} == "disabled" ]]; then + printf "\n\n\tTerminal light/dark mode detection failed from signal handler. Disabling automatic detection.\n" >&2 + printf "\tYou can manually change modes with\n\n\tupdate-terminal-color-mode [dark|light]\n\n" >&2 + printf "\tYou can re-enable automatic detection with\n\n\tunset GEODESIC_TERM_COLOR_AUTO\n\n" >&2 + fi + + if [[ $exit_code -gt 128 ]] || [[ -z $fg_rgb ]] || [[ -z $bg_rgb ]]; then if [[ $GEODESIC_TRACE =~ "terminal" ]] && tty -s; then echo "$(tput setaf 1)* TERMINAL TRACE: Terminal did not respond to OSC 10 and 11 queries.$(tput sgr0)" >&2 fi @@ -135,10 +193,11 @@ function _srgb_to_luminance() { # Normalize hexadecimal values to [0,1] and linearize them normalize_and_linearize() { - local hex=${1^^} # Uppercase the hex value, because bc requires it - local float=$(echo "ibase=16; $hex" | bc) - local max=$(echo "ibase=16; 1$(printf '%0*d' ${#hex} 0)" | bc) # Accommodate the number of digits - local normalized=$(echo "scale=10; $float / ($max - 1)" | bc) + local hex float max normalized R G B luminance + hex=${1^^} # Uppercase the hex value, because bc requires it + float=$(echo "ibase=16; $hex" | bc) + max=$(echo "ibase=16; 1$(printf '%0*d' ${#hex} 0)" | bc) # Accommodate the number of digits + normalized=$(echo "scale=10; $float / ($max - 1)" | bc) # Apply gamma correction if (($(echo "$normalized <= 0.04045" | bc))); then @@ -149,12 +208,12 @@ function _srgb_to_luminance() { } # Linearize each color component - local R=$(normalize_and_linearize $red) - local G=$(normalize_and_linearize $green) - local B=$(normalize_and_linearize $blue) + R=$(normalize_and_linearize $red) + G=$(normalize_and_linearize $green) + B=$(normalize_and_linearize $blue) # Calculate luminance - local luminance=$(echo "scale=10; 0.2126 * $R + 0.7152 * $G + 0.0722 * $B" | bc) + luminance=$(echo "scale=10; 0.2126 * $R + 0.7152 * $G + 0.0722 * $B" | bc) # Luminance is on a scale of 0 to 1, but we want to be able to # compare integers in bash, so we multiply by a big enough value diff --git a/rootfs/etc/profile.d/_10-colors.sh b/rootfs/etc/profile.d/_10-colors.sh index a347232b9..0525b2103 100755 --- a/rootfs/etc/profile.d/_10-colors.sh +++ b/rootfs/etc/profile.d/_10-colors.sh @@ -10,27 +10,38 @@ # The main change is that it uses the terminal's default colors for foreground and background, # whereas the previous version "reset" the color by setting it to black, which fails in dark mode. -function update-terminal-mode() { +function update-terminal-color-mode() { local new_mode="$1" + local quiet=false case $new_mode in dark | light) ;; + quiet) + quiet=true + # fall through + # shellcheck disable=SC2034 + ;& "") new_mode=$(_is_term_dark_mode -mm) ;; *) - echo "Usage: update-terminal-mode [dark|light]" >&2 + echo "Usage: update-terminal-color-mode [dark|light]" >&2 return 1 ;; esac if [[ $new_mode == "unknown" ]]; then if ! tty -s; then - echo "No terminal detected." >&2 + [[ "$quiet" == "true" ]] || echo "No terminal detected." >&2 elif [[ -z "$(tput op 2>/dev/null)" ]]; then - echo "Terminal does not appear to support color." >&2 + [[ "$quiet" == "true" ]] || echo "Terminal does not appear to support color." >&2 + elif [[ ${GEODESIC_TERM_COLOR_AUTO} == "unsupported" ]]; then + [[ "$quiet" == "true" ]] || echo "Terminal does not support color mode detection." >&2 + else + [[ "$quiet" == "true" ]] || echo "Terminal did not respond to color queries." >&2 fi + # "light" is historical default for unknown terminals. new_mode="light" fi @@ -40,10 +51,18 @@ function update-terminal-mode() { [[ "${_geodesic_tput_cache[TERM]}" != "$TERM" ]]; then _geodesic_tput_cache_init "$1" else - echo "Not updating terminal mode from $new_mode to $new_mode" + [[ "$quiet" == "true" ]] || echo "Not updating terminal mode from $new_mode to $new_mode" >&2 fi } +function get-terminal-color-mode() { + echo "${_geodesic_tput_cache[dark_mode]:-light}" +} + +function _is_color_term() { + [[ -t 1 ]] && tty -s && [[ -n "$(tput op 2 2>/dev/null)" ]] +} + # We call `tput` several times for every prompt, and it can add up, so we cache the results. function _geodesic_tput_cache_init() { declare -g -A _geodesic_tput_cache @@ -76,7 +95,7 @@ function _geodesic_tput_cache_init() { # from here, so we need to tell the user to run the command to fix them. if [[ $BASH_SUBSHELL != 0 ]]; then printf "\n* Terminal mode settings have been lost (%s,%s).\n" "$SHLVL" "$BASH_SUBSHELL" >&2 - printf "* Please run: update-terminal-mode \n\n" >&2 + printf "* Please run: update-terminal-color-mode \n\n" >&2 fi local bold=$(tput bold) @@ -108,6 +127,11 @@ function _geodesic_tput_cache_init() { case $new_mode in dark | light) ;; + quiet) + quiet=true + # fall through + # shellcheck disable=SC2034 + ;& "") new_mode=$(_is_term_dark_mode -m) ;; @@ -175,7 +199,7 @@ function _geodesic_color() { # # In bash, the expression `var[subscript]` has, unfortunately, two very different meanings, # depending on whether `var` has been declared as an associative array or not. If `var` has - # NOT been declared an associative arry, then `subscript` is treated as an arithmetic expression. + # NOT been declared an associative array, then `subscript` is treated as an arithmetic expression. # Within an expression, shell variables may be referenced by name without using the parameter # expansion syntax, meaning `subscript` evaluates to `$subscript`, and the value of the variable # `$subscript` is treated as an arithmetic expression (subject to recursive expansion), which is expected @@ -278,3 +302,37 @@ function reset_terminal_colors() { } _geodesic_tput_cache_init + +# Although SIGWINCH is a standard signal to indicate the window *size* has changed, +# some terminals (not sure which ones) also send a SIGWINCH signal when the window colors change. +# For the other terminals, catching SIGWINCH gives users an easy way of triggering a color update: resize the window. +# So we catch the signal to update the terminal colors, preserving any existing signal handlers. + +function _update-terminal-color-mode-sigwinch() { + [[ ${GEODESIC_TERM_COLOR_SIGNAL} == "true" ]] || [[ ${GEODESIC_TERM_COLOR_AUTO:-true} != "true" ]] && return 0 + + # Ignore repeated signals while a signal is being processed + export GEODESIC_TERM_COLOR_SIGNAL=true + update-terminal-color-mode quiet + unset GEODESIC_TERM_COLOR_SIGNAL +} + +if [[ ${GEODESIC_TERM_COLOR_AUTO} != "unsupported" ]] && _is_color_term; then + # We install the trap handler whether GEODESIC_TERM_COLOR_AUTO is set to "disabled" or "false", + # because we will not be able to detect the change in that variable if + # it started out disabled and then someone enables it. + + # Save existing trap (if any) + existing_trap=$(trap -p WINCH) + + # Set up new trap that runs both the existing trap and the update-terminal-color-mode function + if [ -n "$existing_trap" ]; then + # Extract the existing command from the trap output + existing_cmd=$(echo "$existing_trap" | sed "s/trap -- '\(.*\)' SIGWINCH/\1/") + trap "${existing_cmd}; _update-terminal-color-mode-sigwinch" WINCH + else + trap _update-terminal-color-mode-sigwinch WINCH + fi + + unset existing_trap existing_cmd +fi diff --git a/rootfs/etc/profile.d/_20-localhost.sh b/rootfs/etc/profile.d/_20-localhost.sh deleted file mode 100644 index bea2734d5..000000000 --- a/rootfs/etc/profile.d/_20-localhost.sh +++ /dev/null @@ -1,31 +0,0 @@ -# Files in the profile.d directory are executed by the lexicographical order of their file names. -# This file is named _20-localhost.sh. The leading underscore is needed to ensure this file executes before -# other files that depend on the file system mapping defined here. -# The number portion is to ensure proper ordering among the high-priority scripts. -# This file has only depends on colors.sh and should come before any scripts that -# attempt to access files on the host via `/localhost`. - -if [[ $SHLVL == 1 ]] && [[ -n $GEODESIC_HOST_UID ]] && [[ -n $GEODESIC_HOST_GID ]] && - [[ -n $GEODESIC_LOCALHOST ]] && df -a | grep -q " ${GEODESIC_LOCALHOST}\$"; then - # bindfs on Alpine does not support the `-o nonempty` option - [[ $GEODESIC_OS == "alpine" ]] || o_nonempty="-o nonempty" - if [[ $(df -a | grep ' /localhost$' | cut -f1 -d' ') == ${GEODESIC_LOCALHOST} ]]; then - echo "# Host file ownership mapping already configured" - export GEODESIC_LOCALHOST_MAPPED_DEVICE="${GEODESIC_LOCALHOST}" - elif df -a | grep -q ' /localhost$'; then - red "# Host filesystems found mounted at both /localhost and /localhost.bindfs." - red "# * Verify that content under /localhost is what you expect." - red "# * Report the issue at https://github.com/cloudposse/geodesic/issues" - red "# * Include the output of \`env | grep GEODESIC\` and \`df -a\` in your issue description." - elif bindfs $o_nonempty ${GEODESIC_BINDFS_OPTIONS} "--map=${GEODESIC_HOST_UID}/0:@${GEODESIC_HOST_GID}/@0" "${GEODESIC_LOCALHOST}" /localhost; then - green "# BindFS mapping of ${GEODESIC_LOCALHOST} to /localhost enabled." - green "# Files created under /localhost will have UID:GID ${GEODESIC_HOST_UID}:${GEODESIC_HOST_GID} on host." - export GEODESIC_LOCALHOST_MAPPED_DEVICE="${GEODESIC_LOCALHOST}" - else - red "# ERROR: Unable to mirror /localhost.bindfs to /localhost" - red "# * Report the issue at https://github.com/cloudposse/geodesic/issues" - red "# * Work around the issue by unsetting shell environment variable GEODESIC_HOST_BINDFS_ENABLED." - red "# * Exiting." - exec false - fi -fi diff --git a/rootfs/etc/profile.d/_20-mounts.sh b/rootfs/etc/profile.d/_20-mounts.sh new file mode 100644 index 000000000..87714d88a --- /dev/null +++ b/rootfs/etc/profile.d/_20-mounts.sh @@ -0,0 +1,206 @@ +# Files in the profile.d directory are executed by the lexicographical order of their file names. +# This file is named _20-mounts.sh. The leading underscore is needed to ensure this file executes before +# other files that depend on the file system mapping defined here. +# The number portion is to ensure proper ordering among the high-priority scripts. +# This file has only depends on colors.sh and should come before any scripts that +# attempt to access files on the host. + +# We only need to run this once, so we check the shell level to avoid running it in subshells. +# Still, we can run multiple shells, so it has to be idempotent. +function _map_mounts() { + if ! [[ -d "${HOME}" ]]; then + red "# ERROR: HOME directory ${HOME} does not exist. Fatal error." + return 9 + fi + + # If the user has set the `MAP_FILE_OWNERSHIP` environment variable to "true", + # we will have host mounts with host ownership under /.FS_HOST and + # and have the same mounts with ownership translation under /.FS_CONT (CONT = container). + # Otherwise, all host mounts will be mounted directly at their host paths. + local map="" + export GEODESIC_HOST_PATHS=() + if [[ "$MAP_FILE_OWNERSHIP" == "true" ]]; then + local src="/.FS_HOST" + local dest="/.FS_CONT" + if [[ -d "${src}" ]]; then + map="${dest}" + mkdir -p "${dest}" + else + red "# ERROR: Supposed host mount directory ${src} does not exist. Fatal error." + return 9 + fi + GEODESIC_HOST_PATHS+=("${src}/" "${dest}/") + if [[ -z $GEODESIC_HOST_UID ]] || [[ -z $GEODESIC_HOST_GID ]]; then + red '# ERROR: `$MAP_FILE_OWNERSHIP` is set to "true" but `$GEODESIC_HOST_UID` and `$GEODESIC_HOST_GID` are not set.' + red '# File ownership mapping will not be enabled.' + findmnt -fn "${dest}" >/dev/null || + mount --rbind "${src}" "${dest}" + else + green "# File ownership mapping enabled." + green "# Files created on host will have UID:GID ${GEODESIC_HOST_UID}:${GEODESIC_HOST_GID} on host." + local bindfs_opts=(-o nonempty ${GEODESIC_BINDFS_OPTIONS} "--map=${GEODESIC_HOST_UID}/0:@${GEODESIC_HOST_GID}/@0") + # use bindfs to map all the host mounts, under `/.FS_HOST` to a container mounts under `/.FS_CONT`. + # Accessing the container mounts will show the files with the mapped UID:GID. + # This single mounting is best because it correctly handles individual file mounts, not just directories. + findmnt -fn "${dest}" >/dev/null || + bindfs "${bindfs_opts[@]}" "${src}" "${dest}" && GEODESIC_HOST_PATHS+=("${src}/" "${dest}/") || + red "# ERROR: Failed to bindfs ${src} to ${dest} for file ownership mapping." + fi + fi + + # All the map functions (bindfs and mount --rbind) require that the target already exists before + # the mount is attempted. This function ensures that the source exists and is the correct type, + # and, if so, creates the target if it does not exist. + function _ensure_dest() { + local src="$1" + local dest="$2" + local type + + # Skip if the target is the same inode as the source + if [[ "${src}" -ef "${dest}" ]]; then + type="same" + # Skip if the source is a symlink, as this should never happen, + # because we take no care to be sure the target of the symlink is also mounted. + elif [[ -L "${src}" ]]; then + red "# ERROR: Supposedly mounted '${src}' is a symlink. Skipping." + type="symlink" + # Make the target directory if the source is a directory + elif [[ -d "${src}" ]]; then + mkdir -p "${dest}" + type="dir" + # Make the target file if the source is a file, + # which requires making the directory the target file will be in, + # if it does not already exist. + elif [[ -f "${src}" ]]; then + if ! [[ -f "${dest}" ]]; then + mkdir -p "$(dirname "${dest}")" + touch "${dest}" + fi + type="file" + else + red "# ERROR: Supposedly mounted '${src}' is not a directory or file. Skipping." + type="missing" + fi + echo "${type}" + } + + # Map a directory or file from the host path to the container path + function _map_host() { + local src="$1" + local dest="$2" + local type="$(_ensure_dest "${src}" "${dest}")" + + [[ $type == "dir" ]] && GEODESIC_HOST_PATHS+=("${src}/" "${dest}/") + + if [[ "$type" == "dir" ]] || [[ "$type" = "file" ]]; then + findmnt -fn "${dest}" >/dev/null || + mount --rbind "${src}" "${dest}" || red "# ERROR: Failed to mount ${src} to ${dest} for container path mapping." + fi + } + + # If file ownership mapping is enabled, map the ownership translated mounts + # to the host files system paths. Otherwise do nothing. + function _map_owner_mapped() { + [[ -n "${map}" ]] || return 0 + local dest="$1" + local src="${map}${dest}" + + local type="$(_ensure_dest "${src}" "${dest}")" + if [[ "$type" == "dir" ]] || [[ "$type" = "file" ]]; then + findmnt -fn "${dest}" >/dev/null || + mount --rbind "${src}" "${dest}" || red "# ERROR: Failed to mount ${src} to ${dest} for host path mapping." + fi + } + + # Host mounts are already mounted at the desired path, no need to alias them, + # but we may need to handle file ownership mapping. + IFS='|' read -ra paths <<<"${GEODESIC_HOST_MOUNTS}" + for p in "${paths[@]}"; do + _map_owner_mapped "$p" + [[ -d "$p" ]] && GEODESIC_HOST_PATHS+=("${p}/") + done + + # Map the workspace mount + # If Geodesic was started without the wrapper (e.g. `docker run ... geodesic`), the workspace + # will not be mounted. In that case, we will not map the workspace. + if [[ -z "${WORKSPACE_MOUNT_HOST_DIR}" ]] || [[ "${WORKSPACE_MOUNT_HOST_DIR}" == "${WORKSPACE_MOUNT}" ]]; then + WORKSPACE_MOUNT_HOST_DIR="${WORKSPACE_MOUNT}" + yellow "# No host mapping found for Workspace." + else + _map_owner_mapped "${WORKSPACE_MOUNT_HOST_DIR}" + _map_host "${WORKSPACE_MOUNT_HOST_DIR}" "${WORKSPACE_MOUNT}" + fi + + # Map the user's home directory subdirectories and files from the host ($LOCAL_HOME) to the container ($HOME) + # although we call it "dirs", it can be files too + local dirs + IFS='|' read -ra dirs <<<"${GEODESIC_HOMEDIR_MOUNTS}" + if ((${#dirs[@]} == 0)); then + yellow "# No host user home directories to map to container user home." + return 0 + fi + + if [[ -z "${LOCAL_HOME}" ]]; then + red "# ERROR: LOCAL_HOME is not set. Cannot map host user's home to container user's home." + return 0 + fi + + # Set up file ownership mapping for the LOCAL_HOME directory + for d in "${dirs[@]}"; do + _map_owner_mapped "${LOCAL_HOME}/$d" + done + + if [[ "${LOCAL_HOME}" == "${HOME}" ]]; then + yellow "# LOCAL_HOME is the same as HOME. No need to map directories." + return 0 + fi + + # Map the LOCAL_HOME directory to the HOME directory + for d in "${dirs[@]}"; do + _map_host "${LOCAL_HOME}/$d" "${HOME}/$d" + done +} + +function _add_symlinks() { + local links dest src + IFS='|' read -ra links <<<"${GEODESIC_HOST_SYMLINK}" + for l in "${links[@]}"; do + [[ -z "$l" ]] && continue + local src dest + IFS='>' read -r dest src <<<"$l" + if [[ -z $src ]] || [[ -z $dest ]]; then + red "# ERROR: Invalid symlink definition: $l" + continue + fi + mkdir -p "$(dirname "$dest")" + if [[ -e $dest ]]; then + red "# ERROR: Symlink destination already exists: '$dest'" + continue + fi + ln -sT "$src" "$dest" && yellow symlinking "'$src' -> '$dest'" || red "# ERROR: Failed to create symlink: '$src' -> '$dest'" + done +} + +# Ensure we only run this once per container +if [[ -f "${GEODESIC_HOST_PATHS_CACHE:=/tmp/geodesic-host-paths}" ]]; then + # Source the cached paths + . "${GEODESIC_HOST_PATHS_CACHE}" +else + _map_mounts + _add_symlinks + + # Ensure we do not have paths that match everything + paths=("${GEODESIC_HOST_PATHS[@]}") + GEODESIC_HOST_PATHS=() + for p in "${paths[@]}"; do + # Eliminate paths that are just slashes, or completely empty, which would match everything + if ! [[ $p =~ ^/*$ ]]; then + GEODESIC_HOST_PATHS+=("$p") + fi + done + declare -p GEODESIC_HOST_PATHS >"${GEODESIC_HOST_PATHS_CACHE}" + chmod 644 "${GEODESIC_HOST_PATHS_CACHE}" +fi + +unset GEODESIC_HOST_PATHS_CACHE +unset -f _map_mounts _map_owner _map_host _ensure_dest paths _add_symlinks diff --git a/rootfs/etc/profile.d/_30-geodesic-config.sh b/rootfs/etc/profile.d/_30-geodesic-config.sh index 96b44ce41..5ffbd35bf 100755 --- a/rootfs/etc/profile.d/_30-geodesic-config.sh +++ b/rootfs/etc/profile.d/_30-geodesic-config.sh @@ -16,8 +16,8 @@ # * If it is a directory, all the non-hidden files in that directory are loaded in glob sort order # # Several directories are searched for resources, in this order: -# * $base/defaults/ ($base itself defaults to /localhost/.geodesic, can be set via GEODESIC_CONFIG_HOME) -# * $base/ if and only if there is no $base/defaults/ directory +# * $base/defaults/ ($base itself defaults to ${HOME}/.config/geodesic, can be set via GEODESIC_CONFIG_HOME) +# * Prior to v4, but no longer: $base/ if and only if there is no $base/defaults/ directory # * $base/$(dirname $DOCKER_IMAGE)/defaults/ # * $base/$(dirname $DOCKER_IMAGE)/ if and only if there is no $base/$(dirname $DOCKER_IMAGE)/defaults/ directory # * $base/$(base $DOCKER_IMAGE)/ @@ -44,7 +44,7 @@ function _search_geodesic_dirs() { _expand_dir_or_file search_list "${resource}" "${base}/defaults" else [[ -n $_GEODESIC_TRACE_CUSTOMIZATION ]] && echo "trace: SKIPPING $base/defaults (bad directory)" - _expand_dir_or_file search_list "${resource}" "${base}" + # Removed in v4: _expand_dir_or_file search_list "${resource}" "${base}" fi local company=$(dirname "${DOCKER_IMAGE}") @@ -88,7 +88,7 @@ function _expand_dir_or_file() { local -n expand_list=$1 local resource=$2 local dir=${3-${PWD}} - local default_exclusion_pattern="(~|.bak|.log|.old|.orig|.txt|.md|.disabled)$" + local default_exclusion_pattern="(~|.bak|.log|.old|.orig|.txt|.md|.disabled|#)$" local exclude="${GEODESIC_AUTO_LOAD_EXCLUSIONS:-$default_exclusion_pattern}" [[ -n $_GEODESIC_TRACE_CUSTOMIZATION ]] && echo trace: LOOKING for resources of type "$resource" in "$dir" diff --git a/rootfs/etc/profile.d/_40-preferences.sh b/rootfs/etc/profile.d/_40-preferences.sh index 9dcf66074..edaff3638 100755 --- a/rootfs/etc/profile.d/_40-preferences.sh +++ b/rootfs/etc/profile.d/_40-preferences.sh @@ -5,17 +5,6 @@ # This file depends on colors.sh, geodesic-config.sh, and localhost.sh and should come after them. # This file loads user preferences/customizations and must load before any user-visible configuration takes place. -# In case this output is being piped into a shell, print a warning message -# Specifically, this guards against: -# docker run -it cloudposse/geodesic:latest-debian | bash -printf 'printf "\\nIf piping Geodesic output into a shell, do not attach a terminal (-t flag)\\n" >&2; exit 8;' -# In case this output is not being piped into a shell, hide the warning message -printf '\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b' -printf '\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b' -printf ' ' -printf '\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b' -printf '\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b' - # Parse the GEODESIC_TRACE variable and set the internal _GEODESIC_TRACE_CUSTOMIZATION flag if needed if [[ $GEODESIC_TRACE =~ custom ]]; then export _GEODESIC_TRACE_CUSTOMIZATION=true @@ -25,54 +14,51 @@ fi [[ -n $_GEODESIC_TRACE_CUSTOMIZATION ]] && echo trace: GEODESIC_CONFIG_HOME is found to be "${GEODESIC_CONFIG_HOME:-}" -# If LOCAL_HOME is set, create a symbolic link so host pathnames (at least the ones under $HOME) work inside the shell -if [[ -n $LOCAL_HOME && ! -e $LOCAL_HOME ]]; then - mkdir -p $(dirname "${LOCAL_HOME}") && ln -s /localhost "${LOCAL_HOME}" || - echo $(red Unable to create symbolic link $LOCAL_HOME '->' /localhost) - [[ -n $_GEODESIC_TRACE_CUSTOMIZATION ]] && echo trace: linked $LOCAL_HOME '->' /localhost -fi - # # Determine the base directory for all customizations. # We do some extra processing because GEODESIC_CONFIG_HOME needs to be set as a path in the Geodesic file system, # but the user may have set it as a path on the host computer system. We try to accomodate that by # searching a few other places for the directory if $GEODESIC_CONFIG_HOME does point to a valid directory export GEODESIC_CONFIG_HOME -_GEODESIC_CONFIG_HOME_DEFAULT="/localhost/.geodesic" +_GEODESIC_CONFIG_HOME_DEFAULT="/root/.config/geodesic" if [[ -z $GEODESIC_CONFIG_HOME ]]; then # Not set, use default GEODESIC_CONFIG_HOME="${_GEODESIC_CONFIG_HOME_DEFAULT}" elif [[ ! -d $GEODESIC_CONFIG_HOME ]]; then - # Set, but not correctly. See if it is relative to /localhost (host ~) - if [[ -d /localhost/$GEODESIC_CONFIG_HOME ]]; then - GEODESIC_CONFIG_HOME="/localhost/$GEODESIC_CONFIG_HOME" - # See if it is a full host path ending under host ~ - elif [[ -d /localhost/$(basename $GEODESIC_CONFIG_HOME) ]]; then - GEODESIC_CONFIG_HOME="/localhost/$(basename $GEODESIC_CONFIG_HOME)" + if [[ -n $KUBERNETES_PORT ]]; then + green "# Kubernetes host detected, Geodesic customization disabled." + export GEODESIC_CUSTOMIZATION_DISABLED="No config dir and Kubernetes detected" else - echo $(red Invalid value of GEODESIC_CONFIG_HOME: "${GEODESIC_CONFIG_HOME}") - echo $(red GEODESIC_CONFIG_HOME should be relative to /localhost \(normally your home directory\)) - echo $(red Using default value of ${_GEODESIC_CONFIG_HOME_DEFAULT} instead) - GEODESIC_CONFIG_HOME="${_GEODESIC_CONFIG_HOME_DEFAULT}" + red "# GEODESIC_CONFIG_HOME is set to a non-existent directory: ${GEODESIC_CONFIG_HOME}" >&2 + red "# No Geodesic configuration will be loaded." >&2 fi + mkdir -p "${GEODESIC_CONFIG_HOME}" fi -if [[ ! -d $GEODESIC_CONFIG_HOME ]]; then - if ! df -a | grep -q " ${GEODESIC_LOCALHOST:-/localhost}\$"; then - if [[ -n $KUBERNETES_PORT ]]; then - echo $(green Kubernetes host detected, Geodesic customization disabled.) - else - red "########################################################################################" >&2 - red "# No filesystem is mounted at $(bold ${GEODESIC_LOCALHOST:-/localhost}) which limits Geodesic functionality." >&2 - boot install - fi - export GEODESIC_CUSTOMIZATION_DISABLED="/localhost not a volume" - elif mkdir -p $GEODESIC_CONFIG_HOME; then - echo $(yellow Created directory "$GEODESIC_CONFIG_HOME" '(GEODESIC_CONFIG_HOME)') - else - echo $(red Cannot create directory "$GEODESIC_CONFIG_HOME" '(GEODESIC_CONFIG_HOME)') - fi +function _term_fold() { + local cols + cols=$(tput cols 2>/dev/null || echo "80") + [ -z "$cols" ] || [ "$cols" = "0" ] && cols=80 + fold -w "$cols" -s +} + +[[ -n ${WORKSPACE_MOUNT} ]] || export WORKSPACE_MOUNT=/workspace +if ! findmnt "${WORKSPACE_MOUNT}" >/dev/null 2>&1; then + # Keep the lines short, because some terminals will truncate them rather than wrap them, + # which causes important information to be lost. + red "############################################################" >&2 + red "# No filesystem is mounted at $(bold "${WORKSPACE_MOUNT}")" | _term_fold >&2 + red "# which limits Geodesic functionality." | _term_fold >&2 + boot install +elif [[ -z $(find "${WORKSPACE_MOUNT}" -mindepth 1 -maxdepth 1) ]]; then + red "################################################################" >&2 + red "# No files found under $(bold "${WORKSPACE_MOUNT}")." | _term_fold >&2 + red "# Run Geodesic from your source directory." | _term_fold >&2 + red "# Change (\`cd\`) to your source directory (in your git repo)" | _term_fold >&2 + red "# and run ${APP_NAME:-Geodesic} from there." | _term_fold >&2 + red "################################################################" >&2 + echo fi unset _GEODESIC_CONFIG_HOME_DEFAULT @@ -114,13 +100,8 @@ function _geodesic_set_histfile() { local histfile_list=(${HISTFILE:-${GEODESIC_CONFIG_HOME}/history}) _search_geodesic_dirs histfile_list history export HISTFILE="${histfile_list[-1]}" - if [[ ! $HISTFILE =~ ^/localhost/ ]]; then - echo "* $(yellow Not allowing \"HISTFILE=${HISTFILE}\".)" - mkdir -p "${GEODESIC_CONFIG_HOME}/${DOCKER_IMAGE}/" && HISTFILE="${GEODESIC_CONFIG_HOME}/${DOCKER_IMAGE}/history" && - touch "$HISTFILE" || HISTFILE="${GEODESIC_CONFIG_HOME}/history" - echo "* $(yellow HISTFILE forced to \"${HISTFILE}\".)" - fi - [[ -n $_GEODESIC_TRACE_CUSTOMIZATION ]] && echo trace: HISTFILE set to "${HISTFILE}" + [[ -n $HISTFILE ]] || HISTFILE="${GEODESIC_CONFIG_HOME}/history" + [[ -n $_GEODESIC_TRACE_CUSTOMIZATION ]] && echo 'trace: HISTFILE set to "'"${HISTFILE}"'"' } _geodesic_set_histfile diff --git a/rootfs/etc/profile.d/_50-workdir.sh b/rootfs/etc/profile.d/_50-workdir.sh deleted file mode 100644 index 64f9c773e..000000000 --- a/rootfs/etc/profile.d/_50-workdir.sh +++ /dev/null @@ -1,73 +0,0 @@ -# Files in the profile.d directory are executed by the lexicographical order of their file names. -# This file is named _50-workdir.sh. The leading underscore is needed to ensure this file -# executes before other files that may depend on it. -# The number portion is to ensure proper ordering among the high-priority scripts. -# This file depends on colors.sh, localhost.sh, and preferences,sh and must come after them -# - -# Outputs the device the file resides on, or /dev/null if the file does not exist -function _file_device() { - { [[ -e $1 ]] && df --output=source "$1" | tail -1; } || echo '/dev/null' -} - -# file_on_host is true when the argument is a file or directory that appears to be on the Host file system. -# Intended to support files on user-defined bind mounts in addition to `/localhost`. -# This function is run by the command line prompt setup, so it should be very fast. -# Therefore we cache some info in the environment. -if [[ $GEODESIC_LOCALHOST_DEVICE == "disabled" ]]; then - red "# Host filesystem device detection disabled." -elif df -a | grep -q " ${GEODESIC_LOCALHOST:-/localhost}\$"; then - export GEODESIC_LOCALHOST_DEVICE=$(_file_device "${GEODESIC_LOCALHOST:-/localhost}") - if [[ $GEODESIC_LOCALHOST_DEVICE == $(_file_device /) ]]; then - red "# Host filesystem device detection failed. Falling back to \"path starts with /localhost\"." - GEODESIC_LOCALHOST_DEVICE="same-as-root" - fi -else - export GEODESIC_LOCALHOST_DEVICE="missing" -fi - -function file_on_host() { - if [[ $GEODESIC_LOCALHOST_DEVICE =~ ^(disabled|missing)$ ]]; then - return 1 - elif [[ $GEODESIC_LOCALHOST_DEVICE == "same-as-root" ]]; then - [[ $(readlink -e "$1") =~ ^/localhost ]] - else - local dev="$(_file_device "$1")" - [[ $dev == $GEODESIC_LOCALHOST_DEVICE ]] || [[ $dev == $GEODESIC_LOCALHOST_MAPPED_DEVICE ]] - fi -} - -function _default_initial_wd() { - if [[ -d /stacks ]]; then - # Newer default using `atmos` and stacks - export GEODESIC_WORKDIR="/" - else - # Older default working directory - export GEODESIC_WORKDIR="/conf" - fi - red "# Defaulting initial working directory to \"${GEODESIC_WORKDIR}\"" -} - -# You can set GEODESIC_WORKDIR in your Geodesic preferences to have full control of your starting working directory -if [[ -d $GEODESIC_WORKDIR ]]; then - [[ $SHLVL == 1 ]] && green "# Initial working directory configured as ${GEODESIC_WORKDIR}" -else - if [[ -d $GEODESIC_HOST_CWD ]]; then - if [[ -n $LOCAL_HOME ]] && { [[ $GEODESIC_LOCALHOST_DEVICE == "disabled" ]] || file_on_host "$GEODESIC_HOST_CWD"; }; then - export GEODESIC_WORKDIR=$(readlink -e "${GEODESIC_HOST_CWD}") - green "# Initial working directory set from host CWD to ${GEODESIC_WORKDIR}" - else - red "# Host CWD \"${GEODESIC_HOST_CWD}\" does not appear to be accessible from this container" - _default_initial_wd - fi - else - red "# No configured working directory is accessible:" - red "# GEODESIC_WORKDIR is \"$GEODESIC_WORKDIR\"" - red "# GEODESIC_HOST_CWD is \"$GEODESIC_HOST_CWD\"" - _default_initial_wd - fi -fi - -[[ $SHLVL == 1 ]] && cd "${GEODESIC_WORKDIR}" - -unset -f _default_initial_wd diff --git a/rootfs/etc/profile.d/_50-workspace.sh b/rootfs/etc/profile.d/_50-workspace.sh new file mode 100644 index 000000000..f1265cc75 --- /dev/null +++ b/rootfs/etc/profile.d/_50-workspace.sh @@ -0,0 +1,30 @@ +# Files in the profile.d directory are executed by the lexicographical order of their file names. +# This file is named _50-workspace.sh. The leading underscore is needed to ensure this file +# executes before other files that may depend on it. +# The number portion is to ensure proper ordering among the high-priority scripts. +# This file depends on colors.sh, localhost.sh, and preferences.sh and must come after them +# + +# file_on_host is true when the argument is a file or directory that appears to be on the Host file system. + +function file_on_host() { + local file + file="$(readlink -e "$1")" || return 1 + local path + for path in "${GEODESIC_HOST_PATHS[@]}"; do + # Skip paths that are just slashes, or completely empty, which would match everything + [[ "$path" =~ ^/*$ ]] && continue + if [[ "$path" == "${file}/" || "${file}" == "$path"* ]]; then + return 0 + fi + done + return 1 +} + +if [[ $SHLVL == 1 ]]; then + if [[ -d ${WORKSPACE_FOLDER:=${WORKSPACE_MOUNT}} ]] && cd "${WORKSPACE_FOLDER}"; then + green "# Initial working directory configured as ${WORKSPACE_FOLDER}" + else + red "# Configured work directory ${WORKSPACE_FOLDER} does not appear to be accessible from this container" + fi +fi diff --git a/rootfs/etc/profile.d/atmos.sh b/rootfs/etc/profile.d/atmos.sh index 9e924fd6e..5891215f6 100644 --- a/rootfs/etc/profile.d/atmos.sh +++ b/rootfs/etc/profile.d/atmos.sh @@ -9,21 +9,21 @@ function atmos_configure_base_path() { return fi - # If $GEODESIC_WORKDIR contains both a "stacks" and "components" directory, + # If $WORKSPACE_FOLDER contains both a "stacks" and "components" directory, # use it as the $ATMOS_BASE_PATH - if [[ -d "${GEODESIC_WORKDIR}/stacks" ]] && [[ -d "${GEODESIC_WORKDIR}/components" ]]; then - export ATMOS_BASE_PATH="${GEODESIC_WORKDIR}" - green "# Setting ATMOS_BASE_PATH to \"$ATMOS_BASE_PATH\" based on children of workdir" + if [[ -d "${WORKSPACE_FOLDER}/stacks" ]] && [[ -d "${WORKSPACE_FOLDER}/components" ]]; then + export ATMOS_BASE_PATH="${WORKSPACE_FOLDER}" + green "# Setting ATMOS_BASE_PATH to \"$ATMOS_BASE_PATH\" based on children of workspace folder" return fi - # If $GEODESIC_WORKDIR is a descendent of either a "stacks" or "components" directory, + # If $WORKSPACE_FOLDER is a descendent of either a "stacks" or "components" directory, # use the parent of that directory as ATMOS_BASE_PATH - if [[ "${GEODESIC_WORKDIR}" =~ /(stacks|components)/ ]]; then - if [[ "${GEODESIC_WORKDIR}" =~ /stacks/ ]]; then - export ATMOS_BASE_PATH="${GEODESIC_WORKDIR%/stacks/*}" + if [[ "${WORKSPACE_FOLDER}" =~ /(stacks|components)/ ]]; then + if [[ "${WORKSPACE_FOLDER}" =~ /stacks/ ]]; then + export ATMOS_BASE_PATH="${WORKSPACE_FOLDER%/stacks/*}" else - export ATMOS_BASE_PATH="${GEODESIC_WORKDIR%/components/*}" + export ATMOS_BASE_PATH="${WORKSPACE_FOLDER%/components/*}" fi green "# Setting ATMOS_BASE_PATH to \"$ATMOS_BASE_PATH\" based on parent of workdir" return diff --git a/rootfs/etc/profile.d/aws.sh b/rootfs/etc/profile.d/aws.sh index 642801a16..c535cd731 100755 --- a/rootfs/etc/profile.d/aws.sh +++ b/rootfs/etc/profile.d/aws.sh @@ -2,33 +2,47 @@ export AWS_REGION_ABBREVIATION_TYPE=${AWS_REGION_ABBREVIATION_TYPE:-fixed} export AWS_DEFAULT_SHORT_REGION=${AWS_DEFAULT_SHORT_REGION:-$(aws-region --${AWS_REGION_ABBREVIATION_TYPE} ${AWS_DEFAULT_REGION:-us-west-2})} -export GEODESIC_AWS_HOME="${GEODESIC_AWS_HOME:-/localhost/.aws}" +export GEODESIC_AWS_HOME + +function _aws_config_home() { + for dir in "${GEODESIC_AWS_HOME}" "${LOCAL_HOME}/.aws" "${HOME}/.aws"; do + if [ -d "${dir}" ]; then + GEODESIC_AWS_HOME="${dir}" + break + fi + done + + if [ -z "${GEODESIC_AWS_HOME}" ]; then + yellow "# No AWS configuration directory found, using ${HOME}/.aws" + GEODESIC_AWS_HOME="${HOME}/.aws" + fi -# `aws configure` does not respect ENVs -if [ ! -e "${HOME}/.aws" ]; then - # -e fails if the target is a link to a non-existent file, remove dead link if it exists - [ -L "${HOME}/.aws" ] && rm -f "${HOME}/.aws" if [ ! -d "${GEODESIC_AWS_HOME}" ]; then - if mkdir ${GEODESIC_AWS_HOME}; then # allow error message to be printed - ln -s "${GEODESIC_AWS_HOME}" "${HOME}/.aws" - else + if ! mkdir "${GEODESIC_AWS_HOME}"; then # allow error message to be printed + local first_try="${GEODESIC_AWS_HOME}" export GEODESIC_AWS_HOME="${HOME}/.aws" - mkdir ${GEODESIC_AWS_HOME} - if [ -n "${AWS_CONFIG_FILE}" ] && [ ! -f "${AWS_CONFIG_FILE}" ]; then - AWS_CONFIG_FILE="${GEODESIC_AWS_HOME}/config" + if mkdir "${GEODESIC_AWS_HOME}"; then + if [ -n "${AWS_CONFIG_FILE}" ] && [ ! -f "${AWS_CONFIG_FILE}" ]; then + AWS_CONFIG_FILE="${GEODESIC_AWS_HOME}/config" + fi + else + red "# Could not use ${first_try}, or ${GEODESIC_AWS_HOME} for AWS configuration, giving up." + return 1 fi fi - chmod 700 ${GEODESIC_AWS_HOME} + chmod 700 "${GEODESIC_AWS_HOME}" fi - ln -s "${GEODESIC_AWS_HOME}" "${HOME}/.aws" -fi -if [ ! -f "${AWS_CONFIG_FILE:=${GEODESIC_AWS_HOME}/config}" ] && [ -d ${GEODESIC_AWS_HOME} ]; then - echo "# Initializing ${AWS_CONFIG_FILE}" - # Required for AWS_PROFILE=default - echo '[default]' >"${AWS_CONFIG_FILE}" - chmod 600 "${AWS_CONFIG_FILE}" -fi + if [ ! -f "${AWS_CONFIG_FILE:=${GEODESIC_AWS_HOME}/config}" ] && [ -d "${GEODESIC_AWS_HOME}" ]; then + echo "# Initializing ${AWS_CONFIG_FILE}" + # Required for AWS_PROFILE=default + echo '[default]' >"${AWS_CONFIG_FILE}" + chmod 600 "${AWS_CONFIG_FILE}" + fi +} + +_aws_config_home +unset -f _aws_config_home # Install autocompletion rules for aws CLI v1 and v2 for __aws in aws aws1 aws2; do @@ -107,8 +121,30 @@ function export_current_aws_role() { if [[ -n $profile_target ]]; then profile_arn=$(aws --profile "${profile_target}" sts get-caller-identity --output text --query 'Arn' 2>/dev/null | cut -d/ -f1-2) if [[ $profile_arn == $current_role ]]; then - export ASSUME_ROLE="$profile_target" - return + # Extract profile name from config file: + # 1. For default profile, look for a better name + # 2. Skip identity profiles (ending with -identity), as they are too generic + # 3. Use the first non-default, non-identity profile found + if [[ $profile_target == "default" ]] || [[ $profile_target =~ -identity$ ]]; then + # Make some effort to find a better name for the role, but only check the config file, not credentials. + local config_file="${AWS_CONFIG_FILE:-\~/.aws/config}" + if [[ -r $config_file ]]; then + # Assumed roles in AWS config file use the role ARN, not the assumed role ARN, so adjust accordingly. + local role_arn=$(printf "%s" "$current_role" | sed 's/:sts:/:iam:/g' | sed 's,:assumed-role/,:role/,') + role_name=($(crudini --get --format=lines "$config_file" | grep "$role_arn" | cut -d' ' -f 3)) + for rn in "${role_name[@]}"; do + if [[ $rn == "default" ]] || [[ $rn =~ -identity$ ]]; then + continue + else + export ASSUME_ROLE=$rn + return + fi + done + fi + else + export ASSUME_ROLE="$profile_target" + return + fi fi echo "* $(red Profile is set to $profile_target but current role does not match:)" echo "* $(red $current_role)" diff --git a/rootfs/etc/profile.d/banner.sh b/rootfs/etc/profile.d/banner.sh index a424774c1..fa0a839c1 100755 --- a/rootfs/etc/profile.d/banner.sh +++ b/rootfs/etc/profile.d/banner.sh @@ -1,10 +1,3 @@ -COLOR_RESET="" -BANNER_COMMAND="${BANNER_COMMAND:-figurine}" -BANNER_COLOR="${BANNER_COLOR:-}" -BANNER_INDENT="${BANNER_INDENT:- }" -# See font examples at http://www.figlet.org/examples.html -BANNER_FONT="${BANNER_FONT:-Nancyj.flf}" # " IDE parser fix - if [ "${SHLVL}" == "1" ]; then function _check_support() { if grep -qsE 'GenuineIntel|AuthenticAMD' /proc/cpuinfo; then @@ -28,6 +21,15 @@ if [ "${SHLVL}" == "1" ]; then } function _header() { + local ESC=$'\e' + local CYAN="${ESC}[36m" + local COLOR_RESET # Have to be careful because of dark mode + local BANNER_COMMAND="${BANNER_COMMAND:-figurine}" + local BANNER_COLOR="${BANNER_COLOR:-${CYAN}}" + local BANNER_INDENT="${BANNER_INDENT:- }" + # See font examples at http://www.figlet.org/examples.html + local BANNER_FONT="${BANNER_FONT:-Nancyj.flf}" + local vstring local debian_version="/etc/debian_version" @@ -43,6 +45,8 @@ if [ "${SHLVL}" == "1" ]; then fi if [ -n "${BANNER}" ]; then if [ "$BANNER_COMMAND" == "figlet" ]; then + COLOR_RESET="$(tput op 2>/dev/null)" # reset foreground and background colors to defaults + tty -s && [[ -n "${COLOR_RESET}" ]] || BANNER_COLOR="" echo "${BANNER_COLOR}" ${BANNER_COMMAND} -w 200 "${BANNER}" | sed "s/^/${BANNER_INDENT}/" echo "${COLOR_RESET}" @@ -51,6 +55,7 @@ if [ "${SHLVL}" == "1" ]; then else ${BANNER_COMMAND} fi + printf "\n\n" fi } _check_support diff --git a/rootfs/etc/profile.d/motd.sh b/rootfs/etc/profile.d/motd.sh index 6a50532a5..525a305cb 100755 --- a/rootfs/etc/profile.d/motd.sh +++ b/rootfs/etc/profile.d/motd.sh @@ -1,9 +1,11 @@ -if [[ $SHLVL -eq 1 ]]; then +if [[ $SHLVL -eq 1 ]] && ! [[ $GEODESIC_MOTD_ENABLED == "false" ]]; then if [ -f "/etc/motd" ]; then - cat "/etc/motd" + source "/etc/motd.sh" fi if [ -n "${MOTD_URL}" ]; then curl --fail --connect-timeout 1 --max-time 1 --silent "${MOTD_URL}" fi fi + +unset GEODESIC_MOTD_ENABLED diff --git a/rootfs/etc/profile.d/oathtool.sh b/rootfs/etc/profile.d/oathtool.sh deleted file mode 100755 index 5fdd7b2d2..000000000 --- a/rootfs/etc/profile.d/oathtool.sh +++ /dev/null @@ -1,11 +0,0 @@ -function mfa() { - local profile="${1:-${AWS_MFA_PROFILE}}" - local file="${AWS_DATA_PATH}/${profile}.mfa" - if [ -f "${file}" ]; then - oathtool --base32 --totp "$(cat ${file})" - elif [ -z "${profile}" ]; then - echo "No MFA profile defined" >&2 - else - echo "No MFA profile for $profile" >&2 - fi -} diff --git a/rootfs/etc/profile.d/ssh-agent.sh b/rootfs/etc/profile.d/ssh-agent.sh deleted file mode 100755 index 7927d1ffe..000000000 --- a/rootfs/etc/profile.d/ssh-agent.sh +++ /dev/null @@ -1,31 +0,0 @@ -export SSH_KEY="${SSH_KEY:-/localhost/.ssh/id_rsa}" - -# Attempt Re-use existing agent if one exists -if [ -f "${SSH_AGENT_CONFIG}" ]; then - echo "* Found SSH agent config" - . "${SSH_AGENT_CONFIG}" -fi - -trap ctrl_c INT - -function ctrl_c() { - echo "* Okay, nevermind =)" - killall -9 ssh-agent - rm -f "${SSH_AUTH_SOCK}" -} - -# Otherwise launch a new agent -if [ -z "${SSH_AUTH_SOCK}" ] || ! [ -e "${SSH_AUTH_SOCK}" ]; then - ssh-agent | grep -v '^echo' >"${SSH_AGENT_CONFIG}" - . "${SSH_AGENT_CONFIG}" - - # Add keys (if any) to the agent - if [ -n "${SSH_KEY}" ] && [ -f "${SSH_KEY}" ]; then - echo "Add your local private SSH key to the key chain. Hit ^C to skip." - ssh-add "${SSH_KEY}" - fi -fi - -# Clean up -trap - INT -unset -f ctrl_c diff --git a/rootfs/etc/profile.d/terraform.sh b/rootfs/etc/profile.d/terraform.sh index 9b2629f97..b47d8c82f 100755 --- a/rootfs/etc/profile.d/terraform.sh +++ b/rootfs/etc/profile.d/terraform.sh @@ -47,5 +47,5 @@ done # Set default plugin cache dir (must not be one of the mirror directories) # https://www.terraform.io/docs/commands/cli-config.html#implied-local-mirror-directories) -export TF_PLUGIN_CACHE_DIR="${TF_PLUGIN_CACHE_DIR:-/localhost/.terraform.d/plugin-cache}" +export TF_PLUGIN_CACHE_DIR="${TF_PLUGIN_CACHE_DIR:-${HOME}/.terraform.d/plugin-cache}" mkdir -p "$TF_PLUGIN_CACHE_DIR" || unset TF_PLUGIN_CACHE_DIR diff --git a/rootfs/templates/bootstrap b/rootfs/templates/bootstrap index 783dd110b..3780e3cef 100755 --- a/rootfs/templates/bootstrap +++ b/rootfs/templates/bootstrap @@ -1,6 +1,6 @@ #!/bin/bash export DOCKER_IMAGE="{{getenv "DOCKER_IMAGE" "cloudposse/geodesic"}}" -export DOCKER_TAG="{{- getenv "DOCKER_TAG" (printf "${1:-%s-%s}" ((index (getenv "GEODESIC_VERSION" | strings.Split " ") 0) | default "dev") (getenv "GEODESIC_OS" "alpine")) -}}" +export DOCKER_TAG="{{- getenv "DOCKER_TAG" (printf "${1:-%s-%s}" ((index (getenv "GEODESIC_VERSION" | strings.Split " ") 0) | default "dev") (getenv "GEODESIC_OS" "debian")) -}}" export APP_NAME=${APP_NAME:-$(basename $DOCKER_IMAGE)} export INSTALL_PATH=${INSTALL_PATH:-/usr/local/bin} export SAFE_INSTALL_PATH="$HOME/.local/bin" # per XDG recommendations diff --git a/rootfs/templates/wrapper b/rootfs/templates/wrapper deleted file mode 100755 index d7bbb91d1..000000000 --- a/rootfs/templates/wrapper +++ /dev/null @@ -1,360 +0,0 @@ -#!/usr/bin/env bash -# Geodesic Wrapper Script - -set -o pipefail - -# Geodesic Settings -export GEODESIC_PORT=${GEODESIC_PORT:-$((30000 + $$ % 30000))} - -export GEODESIC_HOST_CWD=$(pwd -P 2>/dev/null || pwd) - -readonly OS=$(uname -s) - -export USER_ID=$(id -u) -export GROUP_ID=$(id -g) - -export options=() -export targets=() - -function require_installed() { - if ! which $1 >/dev/null; then - echo "Cannot find $1 installed on this system. Please install and try again." - exit 1 - fi -} - -function options_to_env() { - local kv - local k - local v - - for option in ${options[@]}; do - kv=(${option/=/ }) - k=${kv[0]} # Take first element as key - k=${k#--} # Strip leading -- - k=${k//-/_} # Convert dashes to underscores - k=$(echo $k | tr '[:lower:]' '[:upper:]') # Convert to uppercase (bash3 compat) - - v=${kv[1]} # Treat second element as value - v=${v:-true} # Set it to true for boolean flags - - export $k="$v" - done -} - -function debug() { - if [ "${VERBOSE}" == "true" ]; then - echo "[DEBUG] $*" - fi -} - -function run_exit_hooks() { - command -v geodesic_on_exit >/dev/null && geodesic_on_exit -} - -function use() { - DOCKER_ARGS=() - if [ -t 1 ]; then - # Running in terminal - DOCKER_ARGS+=(-it --rm --name="${DOCKER_NAME}" --env LS_COLORS --env TERM --env TERM_COLOR --env TERM_PROGRAM) - - if [ -n "$SSH_AUTH_SOCK" ]; then - if [ "${OS}" == 'Linux' ]; then - # Bind-mount SSH agent socket into container (linux only) - DOCKER_ARGS+=(--volume "$SSH_AUTH_SOCK:$SSH_AUTH_SOCK" - --env SSH_AUTH_SOCK - --env SSH_CLIENT - --env SSH_CONNECTION - --env SSH_TTY - --env USER_ID - --env GROUP_ID) - elif [ "${OS}" == 'Darwin' ] && [ "${GEODESIC_MAC_FORWARD_SOCKET}" == 'true' ]; then - # Bind-mount SSH-agent socket (available in docker-for mac Edge 2.2 release) - # Note that the file/socket /run/host-services/ssh-auth.sock does not exist - # on the host OS, it is in the Moby Linux VM in which the Docker daemon `dockerd` runs. - # See https://github.com/docker/for-mac/issues/410#issuecomment-557613306 - # and https://docs.docker.com/docker-for-mac/osxfs/#namespaces - DOCKER_ARGS+=(--volume /run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock - -e SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock) - fi - fi - else - DOCKER_ARGS=() - fi - - if [ "${GEODESIC_HOST_BINDFS_ENABLED}" = "true" ]; then - if [ "${USER_ID}" = 0 ]; then - echo "# WARNING: Host user is root. This is DANGEROUS." - echo " * Geodesic should not be launched by the host root user." - echo " * Use \"rootless\" mode instead. See https://docs.docker.com/engine/security/rootless/" - echo "# Not enabling BindFS host filesystem mapping because host user is root." - else - echo "# Enabling BindFS mapping of file system owner and group ID." - DOCKER_ARGS+=( - --env GEODESIC_HOST_UID="${USER_ID}" - --env GEODESIC_HOST_GID="${GROUP_ID}" - --env GEODESIC_LOCALHOST="${GEODESIC_LOCALHOST:=/localhost.bindfs}" - --env GEODESIC_BINDFS_OPTIONS - ) - fi - fi - - if [ "${WITH_DOCKER}" == "true" ]; then - # Bind-mount docker socket into container - # Should work on Linux and Mac. - # Note that the mounted /var/run/docker.sock is not a file or - # socket in the Mac host OS, it is in the dockerd VM. - # https://docs.docker.com/docker-for-mac/osxfs/#namespaces - echo "# Enabling docker support. Be sure you install a docker CLI binary{{getenv "DOCKER_INSTALL_PROMPT"}}." - DOCKER_ARGS+=(--volume "/var/run/docker.sock:/var/run/docker.sock") - # Some reports say this is needed for Windows WSL - if [[ $(uname -r) =~ Microsoft$ ]]; then - DOCKER_ARGS+=(--user root) - fi - # NOTE: bind mounting the docker CLI binary is no longer recommended and usually does not work. - # Use a docker image with a docker CLI binary installed that is appropriate to the image's OS. - fi - - if [[ ${GEODESIC_CUSTOMIZATION_DISABLED-false} == false ]]; then - if [ -n "${GEODESIC_TRACE}" ]; then - DOCKER_ARGS+=(--env GEODESIC_TRACE) - fi - - if [ -n "${ENV_FILE}" ]; then - DOCKER_ARGS+=(--env-file ${ENV_FILE}) - fi - - # allow users to override value of GEODESIC_DEFAULT_ENV_FILE - local geodesic_default_env_file=${GEODESIC_DEFAULT_ENV_FILE:-~/.geodesic/env} - if [ -f "${geodesic_default_env_file}" ]; then - DOCKER_ARGS+=(--env-file=${geodesic_default_env_file}) - fi - else - echo "# Disabling user customizations: GEODESIC_CUSTOMIZATION_DISABLED is set and not 'false'" - DOCKER_ARGS+=(--env GEODESIC_CUSTOMIZATION_DISABLED) - fi - - if [ -n "${DOCKER_DNS}" ]; then - DOCKER_ARGS+=("--dns=${DOCKER_DNS}") - fi - - if [ -n "${LOCAL_HOME}" ]; then - local_home=${LOCAL_HOME} - elif [[ $(uname -r) =~ Microsoft$ ]]; then - - # Lookup correct mount path for WSL - mount_path=$(dirname $(findmnt -S C: -t drvfs -no target)) - - windows_user_name=$($mount_path/c/Windows/System32/cmd.exe /c 'echo %USERNAME%' | tr -d '\r') - user_local_app_data=$(cmd.exe /c echo %LOCALAPPDATA% | tr -d '\r' | sed -e 's/\\/\//g') - - if [ -d "$mount_path/c/Users/${windows_user_name}/AppData/Local/lxss/" ]; then - local_home=${user_local_app_data}/lxss${HOME} - else - local restore_nullglob=$(shopt -p nullglob) - shopt -s nullglob - for dir in $mount_path/c/Users/${windows_user_name}/AppData/Local/Packages/CanonicalGroupLimited.Ubuntu*; do - folder_name=$(basename ${dir}) - local_home=${user_local_app_data}/Packages/${folder_name}/LocalState/rootfs${HOME} - break - done - $restore_nullglob - fi - - if [ -z "${local_home}" ]; then - echo "ERROR: can't identify user home directory, you may specify path via LOCAL_HOME variable" - exit 1 - else - echo "Detected Windows Subsystem for Linux, mounting $local_home instead of $HOME" - fi - - else - local_home=${HOME} - fi - - if [ "${local_home}" == "/localhost" ]; then - echo "WARNING: not mounting ${local_home} because it conflicts with geodesic" - else - if [ "${GEODESIC_LOCALHOST:-/localhost}" != "/localhost" ]; then - echo "# Mounting ${local_home} into container at ${GEODESIC_LOCALHOST} with workdir ${GEODESIC_HOST_CWD}" - else - echo "# Mounting ${local_home} into container with workdir ${GEODESIC_HOST_CWD}" - fi - DOCKER_ARGS+=( - --volume="${local_home}:${GEODESIC_LOCALHOST:-/localhost}" - --env LOCAL_HOME="${local_home}" - ) - fi - - if [ -n "${XDG_CONFIG_HOME}" ]; then - DOCKER_ARGS+=(--env XDG_CONFIG_HOME) - fi - - DOCKER_ARGS+=( - --privileged - --publish ${GEODESIC_PORT}:${GEODESIC_PORT} - --name "${DOCKER_NAME}" - --rm - --env GEODESIC_PORT=${GEODESIC_PORT} - --env DOCKER_IMAGE="${DOCKER_IMAGE%:*}" - --env DOCKER_NAME="${DOCKER_NAME}" - --env DOCKER_TAG="${DOCKER_TAG}" - --env GEODESIC_HOST_CWD="${GEODESIC_HOST_CWD}" - ) - - trap run_exit_hooks EXIT - # the extra curly braces around .ID are because this file goes through go template substitution locally before being installed as a shell script - container_id=$(docker ps --filter name="^/${DOCKER_NAME}\$" --format '{{`{{ .ID }}`}}') - if [ -n "$container_id" ]; then - echo "# Attaching to existing ${DOCKER_NAME} session ($container_id)" - if [ $# -eq 0 ]; then - set -- "/bin/bash" "-l" "$@" - fi - docker exec -it --env GEODESIC_HOST_CWD="${GEODESIC_HOST_CWD}" "${DOCKER_NAME}" $* - else - echo "# Starting new ${DOCKER_NAME} session from ${DOCKER_IMAGE}" - echo "# Exposing port ${GEODESIC_PORT}" - [ -z "${GEODESIC_DOCKER_EXTRA_ARGS}" ] || echo "# Launching with extra Docker args: ${GEODESIC_DOCKER_EXTRA_ARGS}" - docker run "${DOCKER_ARGS[@]}" ${GEODESIC_DOCKER_EXTRA_ARGS} ${DOCKER_IMAGE} -l $* - fi -} - -function parse_args() { - while [[ $1 ]]; do - case "$1" in - -h | --help) - targets+=("help") - shift - ;; - -v | --verbose) - export VERBOSE=true - shift - ;; - --*) - options+=("${1}") - shift - ;; - --) # End of all options - shift - ;; - -*) - echo "Error: Unknown option: $1" >&2 - exit 1 - ;; - *=*) - declare -g "${1}" - shift - ;; - *) - targets+=("${1}") - shift - ;; - esac - done -} - -function uninstall() { - echo "# Uninstalling ${DOCKER_NAME}..." - docker rm -f ${DOCKER_NAME} >/dev/null 2>&1 || true - docker rmi -f ${DOCKER_IMAGE} >/dev/null 2>&1 || true - echo "# Not deleting $0" - exit 0 -} - -function update() { - echo "# Installing the latest version of ${DOCKER_IMAGE}" - docker run --rm ${DOCKER_IMAGE} | bash -s ${DOCKER_TAG} - if [ $? -eq 0 ]; then - echo "# ${DOCKER_IMAGE} has been updated." - exit 0 - else - echo "Failed to update ${DOCKER_IMAGE}" - exit 1 - fi -} - -function stop() { - echo "# Stopping ${DOCKER_NAME}..." - exec docker kill ${DOCKER_NAME} >/dev/null 2>&1 -} - -function help() { - echo "Usage: $0 [target] ARGS" - echo "" - echo " Targets:" - echo " update Upgrade geodesic wrapper shell" - echo " stop Stop a running shell" - echo " uninstall Remove geodesic image" - echo " Enter into a shell" - echo "" - echo " Arguments:" - echo " --env-file=... Pass an environment file containing key=value pairs" - echo "" -} - -require_installed tr -require_installed grep - -parse_args "$@" -options_to_env - -# Docker settings -export DOCKER_IMAGE="{{getenv "DOCKER_IMAGE" "cloudposse/geodesic"}}" -export DOCKER_TAG="{{getenv "DOCKER_TAG" "${DOCKER_TAG:-dev}"}}" -export DOCKER_NAME="{{getenv "APP_NAME" "${DOCKER_NAME:-$(basename $DOCKER_IMAGE)}"}}" - -if [ -n "${GEODESIC_NAME}" ]; then - export DOCKER_NAME=$(basename "${GEODESIC_NAME:-}") -fi - -if [ -n "${GEODESIC_TAG}" ]; then - export DOCKER_TAG=${GEODESIC_TAG} -fi - -if [ -n "${GEODESIC_IMAGE}" ]; then - export DOCKER_IMAGE=${GEODESIC_IMAGE:-${DOCKER_IMAGE}:${DOCKER_TAG}} -else - export DOCKER_IMAGE=${DOCKER_IMAGE}:${DOCKER_TAG} -fi - -export DOCKER_DNS=${DNS:-${DOCKER_DNS}} - -if [ "${GEODESIC_SHELL}" == "true" ]; then - echo "Cannot run while in a geodesic shell" - exit 1 -fi - -if [ -z "${DOCKER_IMAGE}" ]; then - echo "Error: --image not specified (E.g. --image=cloudposse/foobar.example.com:1.0)" - exit 1 -fi - -require_installed docker - -docker ps >/dev/null 2>&1 -if [ $? -ne 0 ]; then - echo "Unable to communicate with docker daemon. Make sure your environment is properly configured and then try again." - exit 1 -fi - -if [ -z "$targets" ]; then - # Execute default target - targets=("use") -fi - -for target in $targets; do - if [ "$target" == "update" ]; then - update - elif [ "$target" == "uninstall" ]; then - uninstall - elif [ "$target" == "stop" ]; then - stop - elif [ "$target" == "use" ]; then - use - elif [ "$target" == "help" ]; then - help - else - echo "Unknown target: $target" - exit 1 - fi -done diff --git a/rootfs/templates/wrapper-body.sh b/rootfs/templates/wrapper-body.sh new file mode 100755 index 000000000..3338bc934 --- /dev/null +++ b/rootfs/templates/wrapper-body.sh @@ -0,0 +1,594 @@ +# Default directory mounts for the user's home directory +homedir_default_mounts=".aws,.config,.emacs.d,.geodesic,.kube,.ssh,.terraform.d" + +function require_installed() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Cannot find '$1' installed on this system. Please install and try again." + exit 1 + fi +} + +## Verify we have the foundations in place + +if [ "${GEODESIC_SHELL}" == "true" ]; then + echo "Cannot run while in a geodesic shell" + exit 1 +fi + +require_installed tr +require_installed grep +require_installed docker + +if ! docker ps >/dev/null 2>&1; then + echo "Unable to communicate with docker daemon. Make sure your environment is properly configured and then try again." + exit 1 +fi + +## Set up the default configuration + +# We use `WORKSPACE` as a shorthand, but it is too generic to be used as an environment variable. +# So we cache and unset it here to see if it otherwise would have been used. +# The user can set it in their launch_options.sh if they want to use it, or they can use GEODESIC_WORKSPACE. +# If the only setting comes from the inherited environment, then we print a warning later. +exported_workspace="${WORKSPACE}" +unset WORKSPACE + +### Geodesic Settings +export GEODESIC_PORT=${GEODESIC_PORT:-$((30000 + $$ % 30000))} + +export GEODESIC_HOST_CWD=$(pwd -P 2>/dev/null || pwd) + +readonly OS=$(uname -s) + +export USER_ID=$(id -u) +export GROUP_ID=$(id -g) + +export options=() +export targets=() + +### Docker defaults + +export DOCKER_DNS=${DNS:-${DOCKER_DNS}} +DOCKER_DETACH_KEYS="ctrl-@,ctrl-[,ctrl-@" + +## Read in custom configuration here, so it can override defaults + +export GEODESIC_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}/geodesic" +if ! [ -d "$GEODESIC_CONFIG_HOME" ] && [ -d "$HOME/.geodesic" ]; then + GEODESIC_CONFIG_HOME="$HOME/.geodesic" +fi + +verbose_buffer=() +launch_options="$GEODESIC_CONFIG_HOME/defaults/launch-options.sh" +if [ -f "$launch_options" ]; then + source "$launch_options" && verbose_buffer+=("Configuration loaded from $launch_options") || printf 'Error loading configuration from %s\n' "$launch_options" >&2 +else + verbose_buffer+=("Not found (OK): $launch_options") +fi + +# Wait until here to parse $DOCKER_IMAGE, so that it can be overridden in $GEODESIC_CONFIG_HOME/launch-options.sh + +if [ -n "${GEODESIC_NAME}" ]; then + export DOCKER_NAME=$(basename "${GEODESIC_NAME:-}") +fi + +if [ -n "${GEODESIC_TAG}" ]; then + export DOCKER_TAG=${GEODESIC_TAG} +fi + +if [ -n "${GEODESIC_IMAGE}" ]; then + export DOCKER_IMAGE=${GEODESIC_IMAGE:-${DOCKER_IMAGE}}:${DOCKER_TAG} +else + export DOCKER_IMAGE=${DOCKER_IMAGE}:${DOCKER_TAG} +fi + +if [ -z "${DOCKER_IMAGE}" ]; then + echo "Error: --image not specified (E.g. --image=cloudposse/foobar.example.com:1.0)" + exit 1 +fi + +docker_stage="${DOCKER_IMAGE##*/}" # remove the registry and org +docker_stage="${docker_stage%%:*}" # remove the tag +docker_org="${DOCKER_IMAGE%/*}" # remove the name and tag +# If the docker image is in the form of "docker.io/library/alpine:latest", then docker_org is "docker.io/library". +# Remove the "docker.io/" prefix if it exists. +docker_org="${docker_org#*/}" + +for dir in "$docker_org" "$docker_stage" "$docker_org/$docker_stage"; do + docker_image_launch_options="$GEODESIC_CONFIG_HOME/${dir}/launch-options.sh" + if [ -f "$docker_image_launch_options" ]; then + source "$docker_image_launch_options" && verbose_buffer+=("Configuration loaded from $docker_image_launch_options") || printf 'Error loading configuration from %s' "$docker_image_launch_options" >&2 + else + verbose_buffer+=("Not found (OK): $docker_image_launch_options") + fi +done + +# GEODESIC_CONFIG_HOME="${GEODESIC_CONFIG_HOME#${HOME}/}" + +function parse_args() { + local arg + while [[ $1 ]]; do + arg="$1" + shift + case "$arg" in + -h | --help) + targets+=("help") + ;; + -v | --verbose) + export VERBOSE=true + ;; + --solo) + export ONE_SHELL=true + ;; + --no-solo|--no-one-shell) + export ONE_SHELL=false + ;; + --trace) + export GEODESIC_TRACE=custom + ;; + --trace=*) + export GEODESIC_TRACE="${1#*=}" + ;; + --no-custom*) + export GEODESIC_CUSTOMIZATION_DISABLED=true + ;; + --no-motd*) + export GEODESIC_MOTD_ENABLED=false + ;; + --workspace=*) + unset WORKSPACE_FOLDER_HOST_DIR + ;& # fall through + --*) + options+=("${arg}") + ;; + --) # End of all options + break + ;; + -*) + echo "Error: Unknown option: ${arg}" >&2 + exit 1 + ;; + *=*) + declare -g "${arg}" + ;; + *) + targets+=("${arg}") + ;; + esac + done +} + +function help() { + echo "Usage: $0 [target] [options] [ARGS]" + echo "" + echo " Targets:" + echo " | use Enter into a shell, passing ARGS to the shell" + echo " help Show this help" + echo " stop [container-name] Stop a running Geodesic container" + echo "" + echo " Options:" + echo " --no-custom Disable loading of custom configuration" + echo " --no-motd Disable the MOTD" + echo " --solo Launch a new container exclusively for this shell" + echo " --no-solo Override the 'solo/ONE_SHELL' setting in your configuration" + echo " --trace Enable tracing of shell customization within Geodesic" + echo " --trace= Enable tracing of specific parts of shell configuration" + echo " -v --verbose Enable verbose output of launch configuration" + echo "" + echo " trace options can be any of:" + echo " custom Trace the loading of custom configuration in Geodesic" + echo " hist Trace the determination of which shell history file to use" + echo " terminal Trace the terminal color mode detection" + echo " You can specify multiple modes, separated by commas, e.g. --trace=custom,hist" + echo "" + echo " Options that only take effect when starting a container:" + echo " --workspace Set which host directory is used as the working directory in the container " + echo "" +} + +function options_to_env() { + local kv + local k + local v + + for option in "${options[@]}"; do + # Safely split on '=' + IFS='=' read -r -a kv <<<"$option" + k=${kv[0]} # Take first element as key + k=${k#--} # Strip leading -- + k=${k//-/_} # Convert dashes to underscores + k=$(echo "$k" | tr '[:lower:]' '[:upper:]') # Convert to uppercase (bash3 compat) + # Treat remaining elements as value, restoring the '=' separator + # This preserves multiple consecutive whitespace characters + v="$(IFS='=' echo "${kv[*]:1}")" + v="${v:-true}" # Set it to true for boolean flags + + export "$k"="$v" + done +} + +parse_args "$@" +options_to_env + +[ "$VERBOSE" = "true" ] && [ -n "$verbose_buffer" ] && printf "%s\n" "${verbose_buffer[@]}" + +function debug() { + if [ "${VERBOSE}" == "true" ]; then + echo "[DEBUG] $*" + fi +} + +function _running_shell_count() { + local count=$(docker exec "${DOCKER_NAME}" pgrep -f "^/bin/(ba)?sh -l" 2>/dev/null | wc -l | tr -d " " || true) + [ -n "${count}" ] || count=0 + echo "${count}" +} + +function _on_shell_exit() { + command -v "${ON_SHELL_EXIT:=geodesic_on_exit}" >/dev/null && "${ON_SHELL_EXIT}" +} + +function _on_container_exit() { + export GEODESIC_EXITING_CONTAINER_ID="${CONTAINER_ID:0:12}" + export GEODESIC_EXITING_CONTAINER_NAME="${DOCKER_NAME}" + _on_shell_exit + [ -n "${ON_CONTAINER_EXIT}" ] && command -v "${ON_CONTAINER_EXIT}" >/dev/null && "${ON_CONTAINER_EXIT}" +} + +function run_exit_hooks() { + # This runs as soon as the terminal is detached. It may take moments for the shell to actually exit. + # It can then take at least a second for the init process to quit. + # There can then be a further delay before the container exits. + # So we need to build in some delays to allow for these events to occur. + + if [[ ${ONE_SHELL} == "true" ]]; then + # We can expect the Docker container to exit quickly, and do not need to report on it. + _on_container_exit + return 0 + fi + + # Initial count of running shells + shells=$(_running_shell_count) + if [ "$shells" -gt 1 ]; then + # Even if our shell is included in the count, we know there are extra shells running. + # Wait for our shell to quit and count again + sleep 1 + shells=$(_running_shell_count) + if [ "$shells" -eq 0 ]; then # coincidence, other shells quit too + echo Other shells quit, too, and Docker container exited + _on_container_exit + return 0 + fi + else # 1 or zero shells. The 1 might be ours, so we wait for it to quit. + for i in {1..6}; do + if [ $i -eq 6 ] || [ $(docker ps -q --filter "id=${CONTAINER_ID:0:12}" | wc -l | tr -d " ") -eq 0 ]; then + break + fi + [ $i -lt 5 ] && sleep 1 + done + if [ $i -eq 6 ]; then + shells=$(_running_shell_count) + if [ "$shells" -eq 0 ]; then + printf 'All shells terminated, but docker container still running.\n' >&2 + printf 'Forcibly kill it with:\n\n docker kill %s\n\n' "${DOCKER_NAME}" >&2 + _on_shell_exit + return 6 + fi + else + echo Docker container exited + _on_container_exit + return 0 + fi + fi + + # If we get here, container is still running and shells != 0 + printf "Docker container still running. " >&2 + [ "$shells" -eq 1 ] && echo -n "Quit 1 other shell " >&2 || echo -n "Quit $shells other shells " >&2 + printf 'to terminate.\n Use `%s stop` to stop gracefully, or\n force quit with `docker kill %s`\n' "$(basename $0)" "${DOCKER_NAME}" >&2 + _on_shell_exit +} + +function use() { + # TODO: Mark each shell with the wrapper's PID, so we can tell which shell is which. + # Then, when exiting, we can distinguish between this wrapper's shell still running + # and other shells still running, and be more efficient and informative in the exit message. + [ "$1" = "use" ] && shift + trap run_exit_hooks EXIT + + if [ "$ONE_SHELL" != "true" ]; then + CONTAINER_ID=$(docker ps --filter name="^/${DOCKER_NAME}\$" --format '{{ .ID }}') + if [ -n "$CONTAINER_ID" ]; then + echo "# Starting shell in already running ${DOCKER_NAME} container ($CONTAINER_ID)" + if [ $# -eq 0 ]; then + set -- "/bin/bash" "-l" "$@" + fi + # We set unusual detach keys because (a) the default first char is ctrl-p, which is used for command history, + # and (b) if you detach from the shell, there is no way to reattach to it, so we want to effectively disable detach. + docker exec -it --detach-keys "ctrl-^,ctrl-[,ctrl-@" --env GEODESIC_HOST_CWD="${GEODESIC_HOST_CWD}" "${DOCKER_NAME}" $* + return 0 + fi + fi + + DOCKER_ARGS=() + if [ -t 1 ]; then + # Running in terminal + DOCKER_ARGS+=(-it --rm --env LS_COLORS --env TERM --env TERM_COLOR --env TERM_PROGRAM --env GEODESIC_MOTD_ENABLED) + if [ -n "$SSH_AUTH_SOCK" ]; then + DOCKER_ARGS+=(--volume /run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock + -e SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock) + fi + # Some settings from the host environment need to propagate into the container + # Set them explicitly so they do not have to be exported in `launch-options.sh` + for v in GEODESIC_CONFIG_HOME GEODESIC_MOTD_ENABLED GEODESIC_TERM_COLOR_AUTO; do + # Test if variable is set in a way that works on bash 3.2, which is what macOS has. + if [ -n "${!v+x}" ]; then + DOCKER_ARGS+=(--env "$v=${!v}") + fi + done + fi + + if [ "${WITH_DOCKER}" == "true" ]; then + # Bind-mount docker socket into container + # Should work on Linux and Mac. + # Note that the mounted /var/run/docker.sock is not a file or + # socket in the Mac host OS, it is in the dockerd VM. + # https://docs.docker.com/docker-for-mac/osxfs/#namespaces + echo "# Enabling docker support. Be sure you install a docker CLI binary${docker_install_prompt}." + DOCKER_ARGS+=(--volume "/var/run/docker.sock:/var/run/docker.sock") + # NOTE: bind mounting the docker CLI binary is no longer recommended and usually does not work. + # Use a docker image with a docker CLI binary installed that is appropriate to the image's OS. + fi + + if [[ ${GEODESIC_CUSTOMIZATION_DISABLED-false} == false ]]; then + if [ -n "${GEODESIC_TRACE}" ]; then + DOCKER_ARGS+=(--env GEODESIC_TRACE) + fi + + if [ -n "${ENV_FILE}" ]; then + DOCKER_ARGS+=(--env-file ${ENV_FILE}) + fi + else + echo "# Disabling user customizations: GEODESIC_CUSTOMIZATION_DISABLED is set and not 'false'" + DOCKER_ARGS+=(--env GEODESIC_CUSTOMIZATION_DISABLED) + fi + + if [ -n "${DOCKER_DNS}" ]; then + DOCKER_ARGS+=("--dns=${DOCKER_DNS}") + fi + + # Mount the user's home directory into the container + # but allow them to specify some directory other than their actual home directory + if [ -n "${LOCAL_HOME}" ]; then + local_home=${LOCAL_HOME} + else + local_home=${HOME} + fi + + ## Directory/file mounting plan: + ## + ## Host directory/file -> Container directory/file + ## * Normal case, Docker handles mapping of file ownership between host and container. + ## Mount the host directory into the container at the same path. + ## * If Docker is not properly mapping file ownership, we set up explicit mapping using `bindfs`. + ## * This is enabled by the user setting MAP_FILE_OWNERSHIP=true. + ## * In this case, we mount the host directory into the container under /.FS_HOST, but again + ## using the same host path. Example: /home/user -> /.FS_HOST/home/user + ## * We then (inside the container as part of login) bind mount /.FS_HOST into /.FS_CONT + ## using `bindfs` to do explicit file ownership mapping. Example: /.FS_HOST/home/user -> /.FS_CONT/home/user + ## * This allows the user to access the files with the correct ownership. More importantly, + ## it correctly manages individual file mounts, not just directories. + ## * Still inside the container, we then mount the mounts under /.FS_CONT into the + ## host path, but now with the correct ownership. Example: /.FS_CONT/home/user -> /home/user + ## + ## Container host path to container path: + ## Most of the mounted directories need to appear at a different path in the container than on + ## the host. After the above mappings setting up the host paths, we then mount the host paths + ## into the container at the correct container path. + mount_dir="" + if [ -n "${GEODESIC_HOST_BINDFS_ENABLED+x}" ]; then + echo "# WARNING: GEODESIC_HOST_BINDFS_ENABLED is deprecated. Use MAP_FILE_OWNERSHIP instead." + export MAP_FILE_OWNERSHIP="${GEODESIC_HOST_BINDFS_ENABLED}" + fi + if [ "${MAP_FILE_OWNERSHIP}" = "true" ]; then + if [ "${USER_ID}" = 0 ]; then + echo "# WARNING: Host user is root. This is DANGEROUS." + echo " * Geodesic should not be launched by the host root user." + echo " * Use \"rootless\" mode instead. See https://docs.docker.com/engine/security/rootless/" + echo "# Not enabling BindFS host filesystem mapping because host user is root, same as default container user." + else + echo "# Enabling explicit mapping of file owner and group ID between container and host." + mount_dir="/.FS_HOST" + DOCKER_ARGS+=( + --env GEODESIC_HOST_UID="${USER_ID}" + --env GEODESIC_HOST_GID="${GROUP_ID}" + --env GEODESIC_BINDFS_OPTIONS + --env MAP_FILE_OWNERSHIP=true + ) + fi + fi + + # Although we call it "dirs", it can be files too + export GEODESIC_HOMEDIR_MOUNTS="" + DOCKER_ARGS+=(--env GEODESIC_HOMEDIR_MOUNTS --env LOCAL_HOME="${local_home}") + [ -z "${HOMEDIR_MOUNTS+x}" ] && HOMEDIR_MOUNTS=("${homedir_default_mounts[@]}") + IFS=, read -ra HOMEDIR_MOUNTS <<<"${HOMEDIR_MOUNTS}" + IFS=, read -ra HOMEDIR_ADDITIONAL_MOUNTS <<<"${HOMEDIR_ADDITIONAL_MOUNTS}" + for dir in "${HOMEDIR_MOUNTS[@]}" "${HOMEDIR_ADDITIONAL_MOUNTS[@]}"; do + if [ -d "${local_home}/${dir}" ] || [ -f "${local_home}/${dir}" ]; then + DOCKER_ARGS+=(--volume="${local_home}/${dir}:${mount_dir}${local_home}/${dir}") + GEODESIC_HOMEDIR_MOUNTS+="${dir}|" + debug "Mounting '${local_home}/${dir}' into container'" + else + debug "Not mounting '${local_home}/${dir}' into container because it is not a directory or file" + fi + done + + # WORKSPACE_MOUNT is the directory in the container that is to be the mount point for the host filesystem + WORKSPACE_MOUNT="${WORKSPACE_MOUNT:-/workspace}" + # WORKSPACE_HOST_DIR is the directory on the host that is to be the working directory + if [ -n "$WORKSPACE" ] && [ -n "$WORKSPACE_FOLDER_HOST_DIR" ] && [ "$WORKSPACE" != "$WORKSPACE_FOLDER_HOST_DIR" ]; then + echo "# WORKSPACE is set to '${WORKSPACE}'." + echo "# WORKSPACE_FOLDER_HOST_DIR is set to '${WORKSPACE_FOLDER_HOST_DIR}'." + echo "# Ignoring WORKSPACE and using WORKSPACE_FOLDER_HOST_DIR as the workspace folder/work directory." + unset exported_workspace + fi + WORKSPACE_FOLDER_HOST_DIR="${WORKSPACE_FOLDER_HOST_DIR:-${WORKSPACE:-${GEODESIC_HOST_CWD}}}" + if [ -n "$exported_workspace" ] && [ "$exported_workspace" != "$WORKSPACE" ]; then + echo "# Ignoring exported WORKSPACE setting of '$exported_workspace'." >&2 + echo "# Export GEODESIC_WORKSPACE or set WORKSPACE in 'launch-config.sh' or via '--workspace' if you want Geodesic to use it." >&2 + echo "# Using '$WORKSPACE' as the workspace folder/work directory." >&2 + fi + git_root=$( + cd "${WORKSPACE_FOLDER_HOST_DIR}" || { + echo Cannot change to workspace folder directory "'${WORKSPACE_FOLDER_HOST_DIR}'", quitting >&2 + echo "WORKSPACE or WORKSPACE_FOLDER_HOST_DIR, if set, must be set to an accessible directory" >&2 + exit 33 + } && + git rev-parse --show-toplevel 2>/dev/null + ) + [ "$?" -eq 33 ] && exit 33 # do not abort if git rev-parse fails + # Resolve symbolic links to get the actual path + local configured_wfhd + configured_wfhd="$WORKSPACE_FOLDER_HOST_DIR" + WORKSPACE_FOLDER_HOST_DIR="$(cd "${WORKSPACE_FOLDER_HOST_DIR}" && pwd -P || echo "${WORKSPACE_FOLDER_HOST_DIR}")" + if [ -z "${git_root}" ] || [ "$git_root" = "${WORKSPACE_FOLDER_HOST_DIR}" ]; then + # WORKSPACE_HOST_PATH is the directory on the host that is to be mounted into the container + WORKSPACE_MOUNT_HOST_DIR="${WORKSPACE_FOLDER_HOST_DIR}" + WORKSPACE_FOLDER="${WORKSPACE_FOLDER:-${WORKSPACE_MOUNT}}" + else + # If we are in a git repo, mount the git root into the container at /workspace + WORKSPACE_MOUNT_HOST_DIR="${git_root}" + WORKSPACE_FOLDER="${WORKSPACE_FOLDER:-${WORKSPACE_MOUNT}/${WORKSPACE_FOLDER_HOST_DIR#${git_root}/}}" + fi + if [ "$configured_wfhd" != "$WORKSPACE_FOLDER_HOST_DIR" ]; then + echo "# Resolved ${configured_wfhd} to '${WORKSPACE_FOLDER_HOST_DIR}'" + export GEODESIC_HOST_SYMLINK+="${configured_wfhd}>${WORKSPACE_FOLDER_HOST_DIR}|" + fi + + echo "# Mounting '${WORKSPACE_MOUNT_HOST_DIR}' into container at '${WORKSPACE_MOUNT}'" + echo "# Setting container working directory to '${WORKSPACE_FOLDER}'" + + DOCKER_ARGS+=( + --volume="${WORKSPACE_MOUNT_HOST_DIR}:${mount_dir}${WORKSPACE_MOUNT_HOST_DIR}" + --env WORKSPACE_MOUNT_HOST_DIR="${WORKSPACE_MOUNT_HOST_DIR}" + --env WORKSPACE_MOUNT="${WORKSPACE_MOUNT}" + --env WORKSPACE_FOLDER="${WORKSPACE_FOLDER}" + ) + [ -n "${GEODESIC_HOST_SYMLINK}" ] && DOCKER_ARGS+=(--env GEODESIC_HOST_SYMLINK) + + # Mount the host mounts wherever the users asks for them to be mounted. + # However, if file ownership mapping is enabled, + # we still need to mount them under /.FS_HOST first. + # To enable final mapping, Geodesic needs to know what is mounted from the host, + # so we provide that information in GEODESIC_HOST_MOUNTS. + export GEODESIC_HOST_MOUNTS="" + IFS=, read -ra HOST_MOUNTS <<<"${HOST_MOUNTS}" + for dir in "${HOST_MOUNTS[@]}"; do + d="${dir%%:*}" + if [ -d "${d}" ] || [ -f "${d}" ]; then + if [ "${dir}" != "${d}" ]; then + DOCKER_ARGS+=(--volume="${d}:${mount_dir}${dir#*:}") + debug "Mounting ${d} into container at ${dir#*:}" + GEODESIC_HOST_MOUNTS+="${dir#*:}|" + else + DOCKER_ARGS+=(--volume="${d}:${mount_dir}${d}") + debug "Mounting ${d} into container at ${d}" + GEODESIC_HOST_MOUNTS+="${d}|" + fi + else # not a directory or file + debug "Not mounting ${d} into container because it is not a directory or file" + fi + done + + DOCKER_ARGS+=(--env GEODESIC_HOST_MOUNTS) + + #echo "Computed DOCKER_ARGS:" + #printf " %s\n" "${DOCKER_ARGS[@]}" + + DOCKER_ARGS+=( + --privileged + --publish ${GEODESIC_PORT}:${GEODESIC_PORT} + --rm + --env GEODESIC_PORT=${GEODESIC_PORT} + --env DOCKER_IMAGE="${DOCKER_IMAGE%:*}" + --env DOCKER_NAME="${DOCKER_NAME}" + --env DOCKER_TAG="${DOCKER_TAG}" + --env GEODESIC_HOST_CWD="${GEODESIC_HOST_CWD}" + ) + + if [ "$ONE_SHELL" = "true" ]; then + DOCKER_NAME="${DOCKER_NAME}-$(date +%d%H%M%S)" + echo "# Starting single shell ${DOCKER_NAME} session from ${DOCKER_IMAGE}" + echo "# Exposing port ${GEODESIC_PORT}" + [ -z "${GEODESIC_DOCKER_EXTRA_ARGS}" ] || echo "# Launching with extra Docker args: ${GEODESIC_DOCKER_EXTRA_ARGS}" + docker run --name "${DOCKER_NAME}" "${DOCKER_ARGS[@]}" ${GEODESIC_DOCKER_EXTRA_ARGS} ${DOCKER_IMAGE} -l $* + else + echo "# Running new ${DOCKER_NAME} container from ${DOCKER_IMAGE}" + echo "# Exposing port ${GEODESIC_PORT}" + [ -z "${GEODESIC_DOCKER_EXTRA_ARGS}" ] || echo "# Launching with extra Docker args: ${GEODESIC_DOCKER_EXTRA_ARGS}" + # docker run "${DOCKER_ARGS[@]}" ${GEODESIC_DOCKER_EXTRA_ARGS} ${DOCKER_IMAGE} -l $* + CONTAINER_ID=$(docker run --detach --init --name "${DOCKER_NAME}" "${DOCKER_ARGS[@]}" ${GEODESIC_DOCKER_EXTRA_ARGS} ${DOCKER_IMAGE} /usr/local/sbin/shell-monitor) + echo "# Started session ${CONTAINER_ID:0:12}. Starting shell via \`docker exec\`..." + docker exec -it --detach-keys "ctrl-^,ctrl-[,ctrl-@" --env GEODESIC_HOST_CWD="${GEODESIC_HOST_CWD}" "${DOCKER_NAME}" /bin/bash -l $* + fi + true +} + +_polite_stop() { + name="$1" + [ -n "$name" ] || return 1 + if [ $(docker ps -q --filter "name=${name}" | wc -l | tr -d " ") -eq 0 ]; then + echo "# No running containers found for ${name}" + return + fi + + printf "# Signalling '%s' to stop..." "${name}" + docker kill -s TERM "${name}" >/dev/null + for i in {1..9}; do + if [ $i -eq 9 ] || [ $(docker ps -q --filter "name=${name}" | wc -l | tr -d " ") -eq 0 ]; then + printf " '%s' stopped gracefully.\n\n" "${name}" + return 0 + fi + [ $i -lt 8 ] && sleep 1 + done + + printf " '%s' did not stop gracefully. Killing it.\n\n" "${name}" + docker kill -s TERM "${name}" >/dev/null + return 138 +} + +function stop() { + exec 1>&2 + name=${targets[1]} + if [ -n "$name" ]; then + _polite_stop ${name} + return $? + fi + RUNNING_NAMES=($(docker ps --filter name="^/${DOCKER_NAME}(-\d{8})?\$" --format '{{ .Names }}')) + if [ -z "$RUNNING_NAMES" ]; then + echo "# No running containers found for ${DOCKER_NAME}" + return + fi + if [ ${#RUNNING_NAMES[@]} -eq 1 ]; then + echo "# Stopping ${RUNNING_NAMES[@]}..." + _polite_stop "${RUNNING_NAMES[@]}" + return $? + fi + if [ ${#RUNNING_NAMES[@]} -gt 1 ]; then + echo "# Multiple containers found for ${DOCKER_NAME}:" + for id in "${RUNNING_NAMES[@]}"; do + echo "# ${id}" + done + echo "# Please specify a unique container name." + echo "# $0 stop " + return 1 + fi +} + +if [ "${targets[0]}" = "stop" ]; then + stop +elif [ -z "${targets[0]}" ] || [ "${targets[0]}" = "use" ]; then + use "${targets[@]}" +else + help +fi diff --git a/rootfs/templates/wrapper-header.sh.tmpl b/rootfs/templates/wrapper-header.sh.tmpl new file mode 100644 index 000000000..58418944b --- /dev/null +++ b/rootfs/templates/wrapper-header.sh.tmpl @@ -0,0 +1,24 @@ +{{/* + * This is the templated part of the wrapper script. + * It includes the entire header so that we only need 2 files, not 3. + * The bulk of the wrapper script is in the `wrapper-body.sh` file. + */ -}} +#!/usr/bin/env bash + +# Geodesic Wrapper Script +# We keep this compatible with bash 3.2 because that is what macOS ships with. +# Among other things, this means we cannot use [ -v var ] to check if a variable is set, +# so we use [ -n "${var+x}" ] instead. + +set -o pipefail + +# Customized launch settings for this installation + +export DOCKER_IMAGE="{{ getenv "DOCKER_IMAGE" "cloudposse/geodesic" }}" +export DOCKER_TAG="{{ getenv "DOCKER_TAG" "${DOCKER_TAG:-dev}" }}" +export DOCKER_NAME="{{ getenv "APP_NAME" "${DOCKER_NAME:-$(basename $DOCKER_IMAGE)}" }}" + +# Per OS settings +docker_install_prompt="{{ getenv "DOCKER_INSTALL_PROMPT" }}" + +## End of installation configuration diff --git a/rootfs/usr/local/bin/boot b/rootfs/usr/local/bin/boot index 7f238b7ff..6fb89d07e 100755 --- a/rootfs/usr/local/bin/boot +++ b/rootfs/usr/local/bin/boot @@ -5,31 +5,41 @@ if [[ $1 == "install" ]]; then function color() { echo "$(tput setaf 1)$*$(tput op)" >&2; } color "# EXIT THIS SHELL and on your host computer," - color "# run the following to install the script that runs " + color "# run the following to install the script that runs ${APP_NAME:-$(basename ${DOCKER_IMAGE:-Geodesic})}:" elif [[ $1 = "wrapper" ]]; then function color() { echo "$*"; } color color "########################################################################################" color "# This is the end of the script that installs Geodesic. You should not be seeing this." - color "# This should have been piped into bash. Use the following to install the script that runs" + color "# This should have been piped into bash. Use the following to install the script that" + color "# runs Geodesic with all its features (the recommended way to use Geodesic):" elif [[ -n $DOCKER_IMAGE ]] && [[ -n $DOCKER_TAG ]]; then exec /usr/local/bin/init else function color() { echo "$*" >&2; } color "########################################################################################" color "# Attach a terminal (docker run --rm --it ...) if you want to run a shell." - color "# Run the following to install the script that runs " + color "# Run the following to install the script that runs ${APP_NAME:-$(basename ${DOCKER_IMAGE:-Geodesic})} " + color "# with all its features (the recommended way to use Geodesic):" fi source /etc/os-release || true # ignore exit code -color "# Geodesic with all its features (the recommended way to use Geodesic):" +version="${DOCKER_TAG}" +if [[ -z $version ]]; then + if [[ -n ${GEODESIC_VERSION% *} ]]; then + version="${GEODESIC_VERSION% *}${ID:+-$ID}" + else + version="dev${ID:+-$ID}" + fi +fi + color "#" -color "# docker run --rm ${DOCKER_IMAGE:-cloudposse/geodesic}:${DOCKER_TAG:-latest${ID:+-$ID}} init | bash" +color "# docker run --rm ${DOCKER_IMAGE:-cloudposse/geodesic}:${version} init | bash" color "#" color "# After that, you should be able to launch Geodesic just by typing" color "#" -color "# geodesic" +color "# $(basename ${DOCKER_IMAGE:-geodesic})" color "#" color "########################################################################################" echo diff --git a/rootfs/usr/local/bin/conf-directory b/rootfs/usr/local/bin/conf-directory deleted file mode 100755 index 0cfb3e20c..000000000 --- a/rootfs/usr/local/bin/conf-directory +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# Determine the directory under /conf to use based on the directory configuration and environment variables. -# * If HELMFILE_DIR is set, use that. -# * Otherwise, determine the AWS region. If DEPLOY_REGION is set, use that. Otherwise, use $AWS_REGION. -# * If there is a region-specific helmfile directory for the region we just determined, use that. -# * Otherwise, default to /conf/helmfiles - -project="${1:-helmfiles}" -project_var="${project^^}_DIR" - -if [[ -n ${!project_var} ]]; then - echo "${!project_var}" && exit 0 -fi - -if [[ -d /conf/${DEPLOY_REGION:-$AWS_REGION}/${project} ]]; then - echo "/conf/${DEPLOY_REGION:-$AWS_REGION}/${project}" -else - echo "/conf/${project}" -fi diff --git a/rootfs/usr/local/bin/wrapper b/rootfs/usr/local/bin/wrapper index 2a28f2312..8ce4b72f1 100755 --- a/rootfs/usr/local/bin/wrapper +++ b/rootfs/usr/local/bin/wrapper @@ -1,3 +1,4 @@ #!/bin/bash export DOCKER_INSTALL_PROMPT=" ($(install-docker-command))" || unset DOCKER_INSTALL_PROMPT -gomplate -f /templates/wrapper +gomplate -f /templates/wrapper-header.sh.tmpl +cat /templates/wrapper-body.sh diff --git a/rootfs/usr/local/sbin/shell-monitor b/rootfs/usr/local/sbin/shell-monitor new file mode 100755 index 000000000..fa7bfaab6 --- /dev/null +++ b/rootfs/usr/local/sbin/shell-monitor @@ -0,0 +1,48 @@ +#!/bin/bash + +# Function to count active shell sessions launched by wrapper +# Far from perfect: +# Does not account for shells with detached sessions which cannot be resumed +# Does not account for shells launched without -l flag in the first position +# Does not account for alternative shells like rbash or dash +count_shells() { + pgrep -f "^(/(usr/)?bin/)?(ba)?sh -l" | wc -l +} + +kill_shells() { + pkill -HUP -f "^(/(usr/)?bin/)?(ba)?sh -l" + for i in {1..4}; do + [ $(count_shells) -eq 0 ] && return 0 + sleep 1 + done + + pkill -TERM -f "^(/(usr/)?bin/)?(ba)?sh -l" + + for i in {1..3}; do + [ $(count_shells) -eq 0 ] && return 0 + sleep 1 + done + pkill -KILL -f "^(/(usr/)?bin/)?(ba)?sh -l" + return 137 +} + +trap 'kill_shells; exit $?' TERM HUP INT QUIT EXIT + +# Wait up to 4 seconds for the first connection +i=0 +while [ $(count_shells) -eq 0 ]; do + sleep 1 + i=$((i + 1)) + if [ $i -ge 4 ]; then + echo "No shell sessions detected after 4 seconds, exiting..." + exit 1 + fi +done + +# Monitor shell sessions and exit when none remain +while [ $(count_shells) -gt 0 ]; do + sleep 1 +done + +# Clean up and exit +exit 0