diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..f5bf71b --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,18 @@ +# [Choice] Go version: 1, 1.16, 1.15 +ARG VARIANT=1 +FROM mcr.microsoft.com/vscode/devcontainers/go:0-${VARIANT} + +# [Option] Install Node.js +ARG INSTALL_NODE="true" +ARG NODE_VERSION="lts/*" +RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment the next line to use go get to install anything else you need +# RUN go get -x + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 \ No newline at end of file diff --git a/.devcontainer/base.Dockerfile b/.devcontainer/base.Dockerfile new file mode 100644 index 0000000..75daccc --- /dev/null +++ b/.devcontainer/base.Dockerfile @@ -0,0 +1,42 @@ +# [Choice] Go version: 1, 1.16, 1.15 +ARG VARIANT=1 +FROM golang:${VARIANT} + +# [Option] Install zsh +ARG INSTALL_ZSH="true" +# [Option] Upgrade OS packages to their latest versions +ARG UPGRADE_PACKAGES="true" + +# Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies. +ARG USERNAME=vscode +ARG USER_UID=1000 +ARG USER_GID=$USER_UID +COPY library-scripts/common-debian.sh /tmp/library-scripts/ +RUN bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" "true" "true" \ + && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts + +# Install Go tools +ENV GO111MODULE=auto +COPY library-scripts/go-debian.sh /tmp/library-scripts/ +RUN bash /tmp/library-scripts/go-debian.sh "none" "/usr/local/go" "${GOPATH}" "${USERNAME}" "false" \ + && apt-get clean -y && rm -rf /tmp/library-scripts + +# [Option] Install Node.js +ARG INSTALL_NODE="true" +ARG NODE_VERSION="none" +ENV NVM_DIR=/usr/local/share/nvm +ENV NVM_SYMLINK_CURRENT=true \ + PATH=${NVM_DIR}/current/bin:${PATH} +COPY library-scripts/node-debian.sh /tmp/library-scripts/ +RUN if [ "$INSTALL_NODE" = "true" ]; then bash /tmp/library-scripts/node-debian.sh "${NVM_DIR}" "${NODE_VERSION}" "${USERNAME}"; fi \ + && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts + +# [Optional] Uncomment the next line to use go get to install anything else you need +# RUN go get -x + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..8ff3b70 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,37 @@ +{ + "name": "Go", + "build": { + "dockerfile": "Dockerfile", + "args": { + // Update the VARIANT arg to pick a version of Go: 1, 1.16, 1.15 + "VARIANT": "1.16", + // Options + "INSTALL_NODE": "false", + "NODE_VERSION": "lts/*" + } + }, + "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], + + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "go.toolsManagement.checkForUpdates": "local", + "go.useLanguageServer": true, + "go.gopath": "/go", + "go.goroot": "/usr/local/go" + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "golang.Go" + ], + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "go version", + + // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode" +} diff --git a/.devcontainer/library-scripts/README.md b/.devcontainer/library-scripts/README.md new file mode 100644 index 0000000..d06dfd1 --- /dev/null +++ b/.devcontainer/library-scripts/README.md @@ -0,0 +1,5 @@ +# Warning: Folder contents may be replaced + +The contents of this folder will be automatically replaced with a file of the same name in the [vscode-dev-containers](https://github.com/microsoft/vscode-dev-containers) repository's [script-library folder](https://github.com/microsoft/vscode-dev-containers/tree/master/script-library) whenever the repository is packaged. + +To retain your edits, move the file to a different location. You may also delete the files if they are not needed. \ No newline at end of file diff --git a/.devcontainer/library-scripts/common-debian.sh b/.devcontainer/library-scripts/common-debian.sh new file mode 100755 index 0000000..b5a6aff --- /dev/null +++ b/.devcontainer/library-scripts/common-debian.sh @@ -0,0 +1,396 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- +# +# Docs: https://github.com/microsoft/vscode-dev-containers/blob/master/script-library/docs/common.md +# Maintainer: The VS Code and Codespaces Teams +# +# Syntax: ./common-debian.sh [install zsh flag] [username] [user UID] [user GID] [upgrade packages flag] [install Oh My Zsh! flag] [Add non-free packages] + +INSTALL_ZSH=${1:-"true"} +USERNAME=${2:-"automatic"} +USER_UID=${3:-"automatic"} +USER_GID=${4:-"automatic"} +UPGRADE_PACKAGES=${5:-"true"} +INSTALL_OH_MYS=${6:-"true"} +ADD_NON_FREE_PACKAGES=${7:-"false"} + +set -e + +if [ "$(id -u)" -ne 0 ]; then + echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' + exit 1 +fi + +# Ensure that login shells get the correct path if the user updated the PATH using ENV. +rm -f /etc/profile.d/00-restore-env.sh +echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh +chmod +x /etc/profile.d/00-restore-env.sh + +# If in automatic mode, determine if a user already exists, if not use vscode +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in ${POSSIBLE_USERS[@]}; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=vscode + fi +elif [ "${USERNAME}" = "none" ]; then + USERNAME=root + USER_UID=0 + USER_GID=0 +fi + +# Load markers to see which steps have already run +MARKER_FILE="/usr/local/etc/vscode-dev-containers/common" +if [ -f "${MARKER_FILE}" ]; then + echo "Marker file found:" + cat "${MARKER_FILE}" + source "${MARKER_FILE}" +fi + +# Ensure apt is in non-interactive to avoid prompts +export DEBIAN_FRONTEND=noninteractive + +# Function to call apt-get if needed +apt-get-update-if-needed() +{ + if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then + echo "Running apt-get update..." + apt-get update + else + echo "Skipping apt-get update." + fi +} + +# Run install apt-utils to avoid debconf warning then verify presence of other common developer tools and dependencies +if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then + + PACKAGE_LIST="apt-utils \ + git \ + openssh-client \ + gnupg2 \ + iproute2 \ + procps \ + lsof \ + htop \ + net-tools \ + psmisc \ + curl \ + wget \ + rsync \ + ca-certificates \ + unzip \ + zip \ + nano \ + vim-tiny \ + less \ + jq \ + lsb-release \ + apt-transport-https \ + dialog \ + libc6 \ + libgcc1 \ + libkrb5-3 \ + libgssapi-krb5-2 \ + libicu[0-9][0-9] \ + liblttng-ust0 \ + libstdc++6 \ + zlib1g \ + locales \ + sudo \ + ncdu \ + man-db \ + strace \ + manpages \ + manpages-dev " + + # Needed for adding manpages-posix and manpages-posix-dev which are non-free packages in Debian + if [ "${ADD_NON_FREE_PACKAGES}" = "true" ]; then + CODENAME="$(cat /etc/os-release | grep -oE '^VERSION_CODENAME=.+$' | cut -d'=' -f2)" + sed -i "s/deb http:\/\/deb\.debian\.org\/debian ${CODENAME} main/deb http:\/\/deb\.debian\.org\/debian ${CODENAME} main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb-src http:\/\/deb\.debian\.org\/debian ${CODENAME} main/deb http:\/\/deb\.debian\.org\/debian ${CODENAME} main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb http:\/\/deb\.debian\.org\/debian ${CODENAME}-updates main/deb http:\/\/deb\.debian\.org\/debian ${CODENAME}-updates main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb-src http:\/\/deb\.debian\.org\/debian ${CODENAME}-updates main/deb http:\/\/deb\.debian\.org\/debian ${CODENAME}-updates main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb http:\/\/deb\.debian\.org\/debian ${CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${CODENAME}-backports main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb-src http:\/\/deb\.debian\.org\/debian ${CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${CODENAME}-backports main contrib non-free/" /etc/apt/sources.list + echo "Running apt-get update..." + apt-get update + PACKAGE_LIST="${PACKAGE_LIST} manpages-posix manpages-posix-dev" + else + apt-get-update-if-needed + fi + + # Install libssl1.1 if available + if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then + PACKAGE_LIST="${PACKAGE_LIST} libssl1.1" + fi + + # Install appropriate version of libssl1.0.x if available + LIBSSL=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '') + if [ "$(echo "$LIBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then + if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then + # Debian 9 + PACKAGE_LIST="${PACKAGE_LIST} libssl1.0.2" + elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then + # Ubuntu 18.04, 16.04, earlier + PACKAGE_LIST="${PACKAGE_LIST} libssl1.0.0" + fi + fi + + echo "Packages to verify are installed: ${PACKAGE_LIST}" + apt-get -y install --no-install-recommends ${PACKAGE_LIST} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 ) + + PACKAGES_ALREADY_INSTALLED="true" +fi + +# Get to latest versions of all packages +if [ "${UPGRADE_PACKAGES}" = "true" ]; then + apt-get-update-if-needed + apt-get -y upgrade --no-install-recommends + apt-get autoremove -y +fi + +# Ensure at least the en_US.UTF-8 UTF-8 locale is available. +# Common need for both applications and things like the agnoster ZSH theme. +if [ "${LOCALE_ALREADY_SET}" != "true" ] && ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then + echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen + locale-gen + LOCALE_ALREADY_SET="true" +fi + +# Create or update a non-root user to match UID/GID. +if id -u ${USERNAME} > /dev/null 2>&1; then + # User exists, update if needed + if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -G $USERNAME)" ]; then + groupmod --gid $USER_GID $USERNAME + usermod --gid $USER_GID $USERNAME + fi + if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then + usermod --uid $USER_UID $USERNAME + fi +else + # Create user + if [ "${USER_GID}" = "automatic" ]; then + groupadd $USERNAME + else + groupadd --gid $USER_GID $USERNAME + fi + if [ "${USER_UID}" = "automatic" ]; then + useradd -s /bin/bash --gid $USERNAME -m $USERNAME + else + useradd -s /bin/bash --uid $USER_UID --gid $USERNAME -m $USERNAME + fi +fi + +# Add add sudo support for non-root user +if [ "${USERNAME}" != "root" ] && [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" ]; then + echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME + chmod 0440 /etc/sudoers.d/$USERNAME + EXISTING_NON_ROOT_USER="${USERNAME}" +fi + +# ** Shell customization section ** +if [ "${USERNAME}" = "root" ]; then + USER_RC_PATH="/root" +else + USER_RC_PATH="/home/${USERNAME}" +fi + +# .bashrc/.zshrc snippet +RC_SNIPPET="$(cat << 'EOF' + +if [ -z "${USER}" ]; then export USER=$(whoami); fi +if [[ "${PATH}" != *"$HOME/.local/bin"* ]]; then export PATH="${PATH}:$HOME/.local/bin"; fi + +# Display optional first run image specific notice if configured and terminal is interactive +if [ -t 1 ] && [ ! -f "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed" ]; then + if [ -f "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" ]; then + cat "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" + elif [ -f "/workspaces/.codespaces/shared/first-run-notice.txt" ]; then + cat "/workspaces/.codespaces/shared/first-run-notice.txt" + fi + mkdir -p "$HOME/.config/vscode-dev-containers" + # Mark first run notice as displayed after 10s to avoid problems with fast terminal refreshes hiding it + (sleep 10s; touch "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed") & >/dev/null 2>&1 + disown %+ +fi + +EOF +)" + +# code shim, it fallbacks to code-insiders if code is not available +cat << 'EOF' > /usr/local/bin/code +#!/bin/sh + +get_in_path_except_current() { + which -a "$1" | grep -A1 "$0" | grep -v "$0" +} + +code="$(get_in_path_except_current code)" + +if [ -n "$code" ]; then + exec "$code" "$@" +elif [ "$(command -v code-insiders)" ]; then + exec code-insiders "$@" +else + echo "code or code-insiders is not installed" >&2 + exit 127 +fi +EOF +chmod +x /usr/local/bin/code + +# Codespaces bash and OMZ themes - partly inspired by https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/robbyrussell.zsh-theme +CODESPACES_BASH="$(cat \ +<<'EOF' + +# Codespaces bash prompt theme +__bash_prompt() { + local userpart='`export XIT=$? \ + && [ ! -z "${GITHUB_USER}" ] && echo -n "\[\033[0;32m\]@${GITHUB_USER} " || echo -n "\[\033[0;32m\]\u " \ + && [ "$XIT" -ne "0" ] && echo -n "\[\033[1;31m\]➜" || echo -n "\[\033[0m\]➜"`' + local gitbranch='`\ + export BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null); \ + if [ "${BRANCH}" = "HEAD" ]; then \ + export BRANCH=$(git describe --contains --all HEAD 2>/dev/null); \ + fi; \ + if [ "${BRANCH}" != "" ]; then \ + echo -n "\[\033[0;36m\](\[\033[1;31m\]${BRANCH}" \ + && if git ls-files --error-unmatch -m --directory --no-empty-directory -o --exclude-standard ":/*" > /dev/null 2>&1; then \ + echo -n " \[\033[1;33m\]✗"; \ + fi \ + && echo -n "\[\033[0;36m\]) "; \ + fi`' + local lightblue='\[\033[1;34m\]' + local removecolor='\[\033[0m\]' + PS1="${userpart} ${lightblue}\w ${gitbranch}${removecolor}\$ " + unset -f __bash_prompt +} +__bash_prompt + +EOF +)" +CODESPACES_ZSH="$(cat \ +<<'EOF' +__zsh_prompt() { + local prompt_username + if [ ! -z "${GITHUB_USER}" ]; then + prompt_username="@${GITHUB_USER}" + else + prompt_username="%n" + fi + PROMPT="%{$fg[green]%}${prompt_username} %(?:%{$reset_color%}➜ :%{$fg_bold[red]%}➜ )" + PROMPT+='%{$fg_bold[blue]%}%~%{$reset_color%} $(git_prompt_info)%{$fg[white]%}$ %{$reset_color%}' + unset -f __zsh_prompt +} +ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[cyan]%}(%{$fg_bold[red]%}" +ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%} " +ZSH_THEME_GIT_PROMPT_DIRTY=" %{$fg_bold[yellow]%}✗%{$fg_bold[cyan]%})" +ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg_bold[cyan]%})" +__zsh_prompt +EOF +)" + +# Add notice that Oh My Bash! has been removed from images and how to provide information on how to install manually +OMB_README="$(cat \ +<<'EOF' +"Oh My Bash!" has been removed from this image in favor of a simple shell prompt. If you +still wish to use it, remove "~/.oh-my-bash" and install it from: https://github.com/ohmybash/oh-my-bash +You may also want to consider "Bash-it" as an alternative: https://github.com/bash-it/bash-it +See here for infomation on adding it to your image or dotfiles: https://aka.ms/codespaces/omb-remove +EOF +)" +OMB_STUB="$(cat \ +<<'EOF' +#!/usr/bin/env bash +if [ -t 1 ]; then + cat $HOME/.oh-my-bash/README.md +fi +EOF +)" + +# Add RC snippet and custom bash prompt +if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then + echo "${RC_SNIPPET}" >> /etc/bash.bashrc + echo "${CODESPACES_BASH}" >> "${USER_RC_PATH}/.bashrc" + if [ "${USERNAME}" != "root" ]; then + echo "${CODESPACES_BASH}" >> "/root/.bashrc" + fi + chown ${USERNAME}:${USERNAME} "${USER_RC_PATH}/.bashrc" + RC_SNIPPET_ALREADY_ADDED="true" +fi + +# Add stub for Oh My Bash! +if [ ! -d "${USER_RC_PATH}/.oh-my-bash}" ] && [ "${INSTALL_OH_MYS}" = "true" ]; then + mkdir -p "${USER_RC_PATH}/.oh-my-bash" "/root/.oh-my-bash" + echo "${OMB_README}" >> "${USER_RC_PATH}/.oh-my-bash/README.md" + echo "${OMB_STUB}" >> "${USER_RC_PATH}/.oh-my-bash/oh-my-bash.sh" + chmod +x "${USER_RC_PATH}/.oh-my-bash/oh-my-bash.sh" + if [ "${USERNAME}" != "root" ]; then + echo "${OMB_README}" >> "/root/.oh-my-bash/README.md" + echo "${OMB_STUB}" >> "/root/.oh-my-bash/oh-my-bash.sh" + chmod +x "/root/.oh-my-bash/oh-my-bash.sh" + fi + chown -R "${USERNAME}:${USERNAME}" "${USER_RC_PATH}/.oh-my-bash" +fi + +# Optionally install and configure zsh and Oh My Zsh! +if [ "${INSTALL_ZSH}" = "true" ]; then + if ! type zsh > /dev/null 2>&1; then + apt-get-update-if-needed + apt-get install -y zsh + fi + if [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then + echo "${RC_SNIPPET}" >> /etc/zsh/zshrc + ZSH_ALREADY_INSTALLED="true" + fi + + # Adapted, simplified inline Oh My Zsh! install steps that adds, defaults to a codespaces theme. + # See https://github.com/ohmyzsh/ohmyzsh/blob/master/tools/install.sh for offical script. + OH_MY_INSTALL_DIR="${USER_RC_PATH}/.oh-my-zsh" + if [ ! -d "${OH_MY_INSTALL_DIR}" ] && [ "${INSTALL_OH_MYS}" = "true" ]; then + TEMPLATE_PATH="${OH_MY_INSTALL_DIR}/templates/zshrc.zsh-template" + USER_RC_FILE="${USER_RC_PATH}/.zshrc" + umask g-w,o-w + mkdir -p ${OH_MY_INSTALL_DIR} + git clone --depth=1 \ + -c core.eol=lf \ + -c core.autocrlf=false \ + -c fsck.zeroPaddedFilemode=ignore \ + -c fetch.fsck.zeroPaddedFilemode=ignore \ + -c receive.fsck.zeroPaddedFilemode=ignore \ + "https://github.com/ohmyzsh/ohmyzsh" "${OH_MY_INSTALL_DIR}" 2>&1 + echo -e "$(cat "${TEMPLATE_PATH}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${USER_RC_FILE} + sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="codespaces"/g' ${USER_RC_FILE} + mkdir -p ${OH_MY_INSTALL_DIR}/custom/themes + echo "${CODESPACES_ZSH}" > "${OH_MY_INSTALL_DIR}/custom/themes/codespaces.zsh-theme" + # Shrink git while still enabling updates + cd "${OH_MY_INSTALL_DIR}" + git repack -a -d -f --depth=1 --window=1 + # Copy to non-root user if one is specified + if [ "${USERNAME}" != "root" ]; then + cp -rf "${USER_RC_FILE}" "${OH_MY_INSTALL_DIR}" /root + chown -R ${USERNAME}:${USERNAME} "${USER_RC_PATH}" + fi + fi +fi + +# Write marker file +mkdir -p "$(dirname "${MARKER_FILE}")" +echo -e "\ + PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\ + LOCALE_ALREADY_SET=${LOCALE_ALREADY_SET}\n\ + EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\ + RC_SNIPPET_ALREADY_ADDED=${RC_SNIPPET_ALREADY_ADDED}\n\ + ZSH_ALREADY_INSTALLED=${ZSH_ALREADY_INSTALLED}" > "${MARKER_FILE}" + +echo "Done!" diff --git a/.devcontainer/library-scripts/go-debian.sh b/.devcontainer/library-scripts/go-debian.sh new file mode 100755 index 0000000..29f4bf6 --- /dev/null +++ b/.devcontainer/library-scripts/go-debian.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- +# +# Docs: https://github.com/microsoft/vscode-dev-containers/blob/master/script-library/docs/go.md +# Maintainer: The VS Code and Codespaces Teams +# +# Syntax: ./go-debian.sh [Go version] [GOROOT] [GOPATH] [non-root user] [Add GOPATH, GOROOT to rc files flag] [Install tools flag] + +TARGET_GO_VERSION=${1:-"latest"} +TARGET_GOROOT=${2:-"/usr/local/go"} +TARGET_GOPATH=${3:-"/go"} +USERNAME=${4:-"automatic"} +UPDATE_RC=${5:-"true"} +INSTALL_GO_TOOLS=${6:-"true"} + +set -e + +if [ "$(id -u)" -ne 0 ]; then + echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' + exit 1 +fi + +# Ensure that login shells get the correct path if the user updated the PATH using ENV. +rm -f /etc/profile.d/00-restore-env.sh +echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh +chmod +x /etc/profile.d/00-restore-env.sh + +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in ${POSSIBLE_USERS[@]}; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi + +function updaterc() { + if [ "${UPDATE_RC}" = "true" ]; then + echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..." + echo -e "$1" >> /etc/bash.bashrc + if [ -f "/etc/zsh/zshrc" ]; then + echo -e "$1" >> /etc/zsh/zshrc + fi + fi +} + +export DEBIAN_FRONTEND=noninteractive + +# Install curl, tar, git, other dependencies if missing +if ! dpkg -s curl ca-certificates tar git g++ gcc libc6-dev make pkg-config > /dev/null 2>&1; then + if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then + apt-get update + fi + apt-get -y install --no-install-recommends curl ca-certificates tar git g++ gcc libc6-dev make pkg-config +fi + +# Get latest version number if latest is specified +if [ "${TARGET_GO_VERSION}" = "latest" ] || [ "${TARGET_GO_VERSION}" = "current" ] || [ "${TARGET_GO_VERSION}" = "lts" ]; then + TARGET_GO_VERSION=$(curl -sSL "https://golang.org/VERSION?m=text" | sed -n '/^go/s///p' ) +fi + +# Install Go +GO_INSTALL_SCRIPT="$(cat < /dev/null 2>&1; then + mkdir -p "${TARGET_GOROOT}" "${TARGET_GOPATH}" + chown -R ${USERNAME} "${TARGET_GOROOT}" "${TARGET_GOPATH}" + su ${USERNAME} -c "${GO_INSTALL_SCRIPT}" +else + echo "Go already installed. Skipping." +fi + +# Install Go tools that are isImportant && !replacedByGopls based on +# https://github.com/golang/vscode-go/blob/0c6dce4a96978f61b022892c1376fe3a00c27677/src/goTools.ts#L188 +# exception: golangci-lint is installed using their install script below. +GO_TOOLS="\ + golang.org/x/tools/gopls \ + honnef.co/go/tools/... \ + golang.org/x/lint/golint \ + github.com/mgechev/revive \ + github.com/uudashr/gopkgs/v2/cmd/gopkgs \ + github.com/ramya-rao-a/go-outline \ + github.com/go-delve/delve/cmd/dlv \ + github.com/golangci/golangci-lint/cmd/golangci-lint" +if [ "${INSTALL_GO_TOOLS}" = "true" ]; then + echo "Installing common Go tools..." + export PATH=${TARGET_GOROOT}/bin:${PATH} + mkdir -p /tmp/gotools /usr/local/etc/vscode-dev-containers ${TARGET_GOPATH}/bin + cd /tmp/gotools + export GOPATH=/tmp/gotools + export GOCACHE=/tmp/gotools/cache + + # Go tools w/module support + export GO111MODULE=on + (echo "${GO_TOOLS}" | xargs -n 1 go get -v )2>&1 | tee -a /usr/local/etc/vscode-dev-containers/go.log + + # Move Go tools into path and clean up + mv /tmp/gotools/bin/* ${TARGET_GOPATH}/bin/ + rm -rf /tmp/gotools + chown -R ${USERNAME} "${TARGET_GOPATH}" +fi + +# Add GOPATH variable and bin directory into PATH in bashrc/zshrc files (unless disabled) +updaterc "$(cat << EOF +export GOPATH="${TARGET_GOPATH}" +if [[ "\${PATH}" != *"\${GOPATH}/bin"* ]]; then export PATH="\${PATH}:\${GOPATH}/bin"; fi +export GOROOT="${TARGET_GOROOT}" +if [[ "\${PATH}" != *"\${GOROOT}/bin"* ]]; then export PATH="\${PATH}:\${GOROOT}/bin"; fi +EOF +)" + +echo "Done!" + diff --git a/.devcontainer/library-scripts/node-debian.sh b/.devcontainer/library-scripts/node-debian.sh new file mode 100755 index 0000000..4baa46e --- /dev/null +++ b/.devcontainer/library-scripts/node-debian.sh @@ -0,0 +1,122 @@ +#!/bin/bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- +# +# Docs: https://github.com/microsoft/vscode-dev-containers/blob/master/script-library/docs/node.md +# Maintainer: The VS Code and Codespaces Teams +# +# Syntax: ./node-debian.sh [directory to install nvm] [node version to install (use "none" to skip)] [non-root user] [Update rc files flag] + +export NVM_DIR=${1:-"/usr/local/share/nvm"} +export NODE_VERSION=${2:-"lts/*"} +USERNAME=${3:-"automatic"} +UPDATE_RC=${4:-"true"} + +set -e + +if [ "$(id -u)" -ne 0 ]; then + echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' + exit 1 +fi + +# Ensure that login shells get the correct path if the user updated the PATH using ENV. +rm -f /etc/profile.d/00-restore-env.sh +echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh +chmod +x /etc/profile.d/00-restore-env.sh + +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in ${POSSIBLE_USERS[@]}; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi + +if [ "${NODE_VERSION}" = "none" ]; then + export NODE_VERSION= +fi + +function updaterc() { + if [ "${UPDATE_RC}" = "true" ]; then + echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..." + echo -e "$1" >> /etc/bash.bashrc + if [ -f "/etc/zsh/zshrc" ]; then + echo -e "$1" >> /etc/zsh/zshrc + fi + fi +} + +# Ensure apt is in non-interactive to avoid prompts +export DEBIAN_FRONTEND=noninteractive + +# Install curl, apt-transport-https, tar, or gpg if missing +if ! dpkg -s apt-transport-https curl ca-certificates tar > /dev/null 2>&1 || ! type gpg > /dev/null 2>&1; then + if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then + apt-get update + fi + apt-get -y install --no-install-recommends apt-transport-https curl ca-certificates tar gnupg2 +fi + +# Install yarn +if type yarn > /dev/null 2>&1; then + echo "Yarn already installed." +else + curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | (OUT=$(apt-key add - 2>&1) || echo $OUT) + echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list + apt-get update + apt-get -y install --no-install-recommends yarn +fi + +# Install the specified node version if NVM directory already exists, then exit +if [ -d "${NVM_DIR}" ]; then + echo "NVM already installed." + if [ "${NODE_VERSION}" != "" ]; then + su ${USERNAME} -c ". $NVM_DIR/nvm.sh && nvm install ${NODE_VERSION} && nvm clear-cache" + fi + exit 0 +fi + +# Create nvm group, nvm dir, and set sticky bit +if ! cat /etc/group | grep -e "^nvm:" > /dev/null 2>&1; then + groupadd -r nvm +fi +umask 0002 +usermod -a -G nvm ${USERNAME} +mkdir -p ${NVM_DIR} +chown :nvm ${NVM_DIR} +chmod g+s ${NVM_DIR} +su ${USERNAME} -c "$(cat << EOF + set -e + umask 0002 + # Do not update profile - we'll do this manually + export PROFILE=/dev/null + curl -so- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash + source ${NVM_DIR}/nvm.sh + if [ "${NODE_VERSION}" != "" ]; then + nvm alias default ${NODE_VERSION} + fi + nvm clear-cache +EOF +)" 2>&1 +# Update rc files +if [ "${UPDATE_RC}" = "true" ]; then +updaterc "$(cat <", + "license": "MIT", + "dependencies": { + "glob": "^7.1.6", + "json-graphql-server": "^2.2.0", + "serverless-http": "2.7.0", + "sync-fetch": "0.3.0" + } +} \ No newline at end of file diff --git a/hosting/netlify/provider.go b/hosting/netlify/provider.go new file mode 100644 index 0000000..0747a43 --- /dev/null +++ b/hosting/netlify/provider.go @@ -0,0 +1,82 @@ +package netlify + +import ( + // import go:embed + _ "embed" + "os" + "path" + + "github.com/devrel-blox/blox/hosting" + "github.com/pterm/pterm" +) + +func init() { + vp := &Provider{ + internalName: "netlify", + internalDescription: "Netlify express api-only provider", + } + hosting.Register(vp.Name(), vp) +} + +// Provider represents the Netlify provider +type Provider struct { + internalName string + internalDescription string +} + +// Name is the name +func (p *Provider) Name() string { + return p.internalName +} + +// Description is the description +func (p *Provider) Description() string { + return p.internalDescription +} + +// Install installs netlify files +func (p *Provider) Install() error { + // make api directory + root, err := os.Getwd() + if err != nil { + return err + } + api := path.Join(root, "api") + err = os.MkdirAll(api, 0755) + if err != nil { + return err + } + // create index.js in api directory + + index := path.Join(api, "index.mjs") + err = hosting.CreateFileWithContents(index, indexjs) + if err != nil { + return err + } + + // create package.json + pkg := path.Join(root, "package.json") + err = hosting.CreateFileWithContents(pkg, packagejson) + if err != nil { + return err + } + // create netlify.toml + vc := path.Join(root, "netlify.toml") + err = hosting.CreateFileWithContents(vc, netlifyToml) + if err != nil { + return err + } + pterm.Info.Println("Netlify provider installed.") + pterm.Info.Println("Run `npm install` to install dependencies.") + + return nil +} + +//go:embed index.mjs +var indexjs string + +//go:embed netlify.toml +var netlifyToml string + +//go:embed package.json +var packagejson string diff --git a/hosting/provider.go b/hosting/provider.go new file mode 100644 index 0000000..2fa9fc3 --- /dev/null +++ b/hosting/provider.go @@ -0,0 +1,55 @@ +package hosting + +import ( + "sync" +) + +var ( + providersMu sync.RWMutex + providers = make(map[string]Provider) +) + +// Provider is an interface that all hosting +// providers will implement +type Provider interface { + Name() string + Description() string + Install() error +} + +// Register stores a provider in the map +func Register(name string, provider Provider) { + providersMu.Lock() + defer providersMu.Unlock() + if provider == nil { + panic("hosting: Register provider is nil") + } + if _, dup := providers[name]; dup { + panic("hosting: Register called twice for provider " + name) + } + providers[name] = provider +} + +// Providers returns the list of registered providers +func Providers() []Provider { + providersMu.RLock() + defer providersMu.RUnlock() + list := make([]Provider, 0, len(providers)) + for name := range providers { + list = append(list, providers[name]) + } + + return list +} + +// GetProvider returns a single provider +// by name +func GetProvider(name string) Provider { + providersMu.RLock() + defer providersMu.RUnlock() + + if p, ok := providers[name]; ok { + return p + } + return nil +} diff --git a/hosting/vercel/index.js b/hosting/vercel/index.js new file mode 100644 index 0000000..430f5e2 --- /dev/null +++ b/hosting/vercel/index.js @@ -0,0 +1,14 @@ +const fetch = require('sync-fetch') + +import jsonGraphqlExpress from 'json-graphql-server'; + +const data = fetch('https://github.com/rawkode/rawkode/releases/download/blox/data.json').json(); +const app = require('express')(); + + +app.use('/api/graphql', jsonGraphqlExpress(data)); +console.log(data); + +const port = process.env.PORT || 3000; + +module.exports = app.listen(port, () => console.log(`Server running on ${port}, http://localhost:${port}`)); \ No newline at end of file diff --git a/hosting/vercel/package.json b/hosting/vercel/package.json new file mode 100644 index 0000000..11e0bc1 --- /dev/null +++ b/hosting/vercel/package.json @@ -0,0 +1,15 @@ +{ + "name": "cms", + "version": "0.0.1", + "description": "cms stuff", + "main": "index.js", + "author": "Brian Ketelsen ", + "license": "MIT", + "dependencies": { + "glob": "^7.1.6", + "js-yaml": "^4.0.0", + "json-graphql-server": "^2.2.0", + "markdown-to-json": "^0.5.4", + "sync-fetch": "^0.3.0" + } +} \ No newline at end of file diff --git a/hosting/vercel/provider.go b/hosting/vercel/provider.go new file mode 100644 index 0000000..8dd3e3f --- /dev/null +++ b/hosting/vercel/provider.go @@ -0,0 +1,82 @@ +package vercel + +import ( + // import go:embed + _ "embed" + "os" + "path" + + "github.com/devrel-blox/blox/hosting" + "github.com/pterm/pterm" +) + +func init() { + vp := &Provider{ + internalName: "vercel", + internalDescription: "Vercel express api-only provider", + } + hosting.Register(vp.Name(), vp) +} + +// Provider represents vercel +type Provider struct { + internalName string + internalDescription string +} + +// Name returns name +func (p *Provider) Name() string { + return p.internalName +} + +// Description returns description +func (p *Provider) Description() string { + return p.internalDescription +} + +// Install installs vercel hosting +func (p *Provider) Install() error { + // make api directory + root, err := os.Getwd() + if err != nil { + return err + } + api := path.Join(root, "api") + err = os.MkdirAll(api, 0755) + if err != nil { + return err + } + // create index.js in api directory + + index := path.Join(api, "index.js") + err = hosting.CreateFileWithContents(index, indexjs) + if err != nil { + return err + } + + // create package.json + pkg := path.Join(root, "package.json") + err = hosting.CreateFileWithContents(pkg, packagejson) + if err != nil { + return err + } + // create vercel.json + vc := path.Join(root, "vercel.json") + err = hosting.CreateFileWithContents(vc, verceljson) + if err != nil { + return err + } + pterm.Info.Println("Vercel provider installed.") + pterm.Info.Println("Run `npm install` to install dependencies.") + + return nil +} + +//go:embed index.js +var indexjs string + +//go:embed vercel.json +var verceljson string + +//go:embed package.json +var packagejson string diff --git a/hosting/vercel/vercel.json b/hosting/vercel/vercel.json new file mode 100644 index 0000000..1a2baeb --- /dev/null +++ b/hosting/vercel/vercel.json @@ -0,0 +1,13 @@ +{ + "functions": { + "api/index.js": { + "maxDuration": 5 + } + }, + "rewrites": [ + { + "source": "/(.*)", + "destination": "/api" + } + ] +} \ No newline at end of file diff --git a/tests/data/profiles/invalid.yaml b/tests/data/profiles/invalid.yaml new file mode 100644 index 0000000..b8a4e60 --- /dev/null +++ b/tests/data/profiles/invalid.yaml @@ -0,0 +1,2 @@ +first_name: John +last_name: Doe diff --git a/tests/data/profiles/rawkode.md b/tests/data/profiles/rawkode.md new file mode 100644 index 0000000..edfbafe --- /dev/null +++ b/tests/data/profiles/rawkode.md @@ -0,0 +1,11 @@ +--- +name: David +organization: Company +title: Title +social_accounts: +- network: twitter + username: username + url: "" +--- + +Body \ No newline at end of file diff --git a/tests/data/profiles/valid.yaml b/tests/data/profiles/valid.yaml new file mode 100644 index 0000000..ed6f877 --- /dev/null +++ b/tests/data/profiles/valid.yaml @@ -0,0 +1,7 @@ +first_name: David +last_name: McKay +company: Rawkode Enterprises +title: Chief Encouragement Officer +social_accounts: + - network: twitter + username: rawkode diff --git a/tests/data/profiles/valid.yml b/tests/data/profiles/valid.yml new file mode 100644 index 0000000..f695406 --- /dev/null +++ b/tests/data/profiles/valid.yml @@ -0,0 +1,7 @@ +first_name: Brian +last_name: Ketelsen +company: Ketelsen Enterprises +title: Chief Motivational Officer +social_accounts: + - network: github + username: bketelsen diff --git a/tests/out/data.json b/tests/out/data.json new file mode 100644 index 0000000..95f233f --- /dev/null +++ b/tests/out/data.json @@ -0,0 +1 @@ +{"profile":[{"ID":"rawkode","body_raw":"\nBody\n","name":"David","organization":"Company","social_accounts":[{"network":"twitter","url":"","username":"username"}],"title":"Title"}]} \ No newline at end of file diff --git a/tests/out/profiles/rawkode.yaml b/tests/out/profiles/rawkode.yaml new file mode 100644 index 0000000..d8ffafd --- /dev/null +++ b/tests/out/profiles/rawkode.yaml @@ -0,0 +1,10 @@ +first_name: David +last_name: McKay +company: Company +title: Title +social_accounts: + - network: twitter + username: rawkode +body: | + + Body diff --git a/tests/out/websites/rawkode.com.yaml b/tests/out/websites/rawkode.com.yaml new file mode 100644 index 0000000..b7a4be5 --- /dev/null +++ b/tests/out/websites/rawkode.com.yaml @@ -0,0 +1,5 @@ +--- +url: https://rawkode.com +profile_id: rawkode +--- +