#!/bin/bash #################################################################### #### Code-Server Install Script #### #### #### #### Installs code-server with Nginx, Apache, or Caddy reverse #### #### proxy, Let's Encrypt TLS certificate, and systemd service. #### #### #### #### Supported: RHEL/Rocky/Alma 8+, Oracle Linux 8+, #### #### Debian 11+, Ubuntu 20.04+ #### #### #### #### Author: Phil Connor #### #### Contact: contact@mylinux.work #### #### Website: https://mylinux.work #### #### License: MIT #### #### Version: 2.0 #### #### #### #### Usage: #### #### Edit the User Configuration section below, then run: #### #### sudo ./install-code-server.sh #### #### #### #### Or pass options on the command line: #### #### sudo ./install-code-server.sh --server code.example.com #### #### --email admin@example.com --http nginx #### #### --password 'YourPassword' #### #### #### #### sudo ./install-code-server.sh --help #### #################################################################### set -euo pipefail # ============================================================================ # USER CONFIGURATION -- edit these or override with command-line options # ============================================================================ CODEDIR="/code" # Home directory for your code EMAIL="admin@mydomain.com" # Email for Let's Encrypt registration HTTPTYPE="NGINX" # APACHE, NGINX, or CADDY PASSWD="" # code-server password (prompted if empty) UNAME="" # Username for Caddy basic auth (optional) SERVDIR="/usr/local/code-server" # code-server install directory SERVERNAME="" # Server FQDN (required) USRDIR="/var/lib/code-server" # User data directory (settings, extensions) INSTALL_CRON=0 # Set to 1 to install auto-update cron CRON_DAYS=25 # Days between automatic updates # ============================================================================ # INTERNAL VARIABLES # ============================================================================ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' VERSION="2.0" ARCH="" # ============================================================================ # HELPER FUNCTIONS # ============================================================================ log() { echo -e "${GREEN}[+]${NC} $1"; } log_warn() { echo -e "${YELLOW}[!]${NC} $1"; } log_err() { echo -e "${RED}[x]${NC} $1" >&2; } log_info() { echo -e "${CYAN}[>]${NC} $1"; } show_help() { cat </dev/null 2>&1; then log_err "Required command not found: $cmd" exit 1 fi done # FQDN check if [[ -z "$SERVERNAME" ]]; then log_err "Server FQDN is required (--server or edit SERVERNAME in script)" exit 1 fi # HTTP type check case "$HTTPTYPE" in APACHE|NGINX|CADDY) ;; *) log_err "Invalid HTTP type: $HTTPTYPE (must be APACHE, NGINX, or CADDY)" exit 1 ;; esac # Password -- prompt if empty if [[ -z "$PASSWD" ]]; then echo -n "Enter code-server password: " read -rs PASSWD echo if [[ -z "$PASSWD" ]]; then log_err "Password cannot be empty" exit 1 fi fi # DNS check if ! host "$SERVERNAME" >/dev/null 2>&1 && ! dig +short "$SERVERNAME" 2>/dev/null | grep -q .; then log_warn "DNS lookup for $SERVERNAME failed -- certbot may fail if DNS is not configured" fi } # ============================================================================ # SYSTEM UPDATE # ============================================================================ update_system() { log "Updating system packages" if [[ "$PKG_TYPE" == "deb" ]]; then apt-get -y update apt-get -y upgrade else dnf -y update fi } # ============================================================================ # INSTALL CODE-SERVER # ============================================================================ install_codeserver() { log "Installing code-server" # Get latest version local version version=$(curl -fsSL "https://api.github.com/repos/coder/code-server/releases/latest" | \ grep '"tag_name"' | head -1 | grep -oP 'v\K[0-9]+\.[0-9]+\.[0-9]+') if [[ -z "$version" ]]; then log_err "Failed to determine latest code-server version" exit 1 fi log_info "Latest version: $version" # Download local tarball="code-server-${version}-linux-${ARCH}.tar.gz" local url="https://github.com/coder/code-server/releases/download/v${version}/${tarball}" cd /tmp log_info "Downloading $tarball" curl -fsSL -o "$tarball" "$url" tar xzf "$tarball" # Install mkdir -p "$SERVDIR" cp -r "code-server-${version}-linux-${ARCH}"/* "$SERVDIR"/ # Create symlink ln -sf "${SERVDIR}/bin/code-server" /usr/bin/code-server # Create directories mkdir -p "$CODEDIR" mkdir -p "$USRDIR" # Cleanup rm -rf "/tmp/$tarball" "/tmp/code-server-${version}-linux-${ARCH}" # Verify if ! command -v code-server >/dev/null 2>&1; then log_err "code-server installation failed" exit 1 fi log "code-server $(code-server --version | head -1) installed to $SERVDIR" } # ============================================================================ # SYSTEMD SERVICE # ============================================================================ create_service() { log "Creating systemd service" local after_service="network.target" case "$HTTPTYPE" in NGINX) after_service="nginx.service" ;; APACHE) after_service="httpd.service" ;; CADDY) after_service="caddy.service" ;; esac cat > /lib/systemd/system/code-server.service < "${confdir}/code-server.conf" < "$conffile" < ServerName $SERVERNAME RewriteEngine On RewriteCond %{HTTP:Upgrade} =websocket [NC] RewriteRule /(.*) ws://127.0.0.1:8080/\$1 [P,L] RewriteCond %{HTTP:Upgrade} !=websocket [NC] RewriteRule /(.*) http://127.0.0.1:8080/\$1 [P,L] ProxyRequests off ProxyPreserveHost On ProxyPass / http://127.0.0.1:8080/ nocanon ProxyPassReverse / http://127.0.0.1:8080/ RequestHeader set X-Forwarded-Proto "http" EOF if [[ "$PKG_TYPE" == "deb" ]]; then a2ensite code-server.conf a2dissite 000-default.conf 2>/dev/null || true systemctl enable --now apache2 systemctl reload apache2 else systemctl enable --now httpd systemctl reload httpd fi log "Apache configured and running" } # ============================================================================ # REVERSE PROXY -- CADDY # ============================================================================ install_caddy() { log "Installing Caddy" if [[ "$PKG_TYPE" == "deb" ]]; then apt-get -y install debian-keyring debian-archive-keyring apt-transport-https curl curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \ gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \ tee /etc/apt/sources.list.d/caddy-stable.list apt-get -y update apt-get -y install caddy else dnf -y install 'dnf-command(copr)' dnf -y copr enable @caddy/caddy dnf -y install caddy fi # Caddy handles TLS automatically -- no certbot needed if [[ -n "$UNAME" ]]; then # With basic auth local caddy_hash caddy_hash=$(caddy hash-password --plaintext "$PASSWD" 2>/dev/null || \ echo "$PASSWD" | caddy hash-password 2>/dev/null || echo "") if [[ -z "$caddy_hash" ]]; then log_warn "Could not generate Caddy password hash -- writing config without basic auth" cat > /etc/caddy/Caddyfile < /etc/caddy/Caddyfile < /etc/caddy/Caddyfile </dev/null; snap refresh core 2>/dev/null snap install --classic certbot 2>/dev/null ln -sf /snap/bin/certbot /usr/bin/certbot else dnf -y install epel-release dnf -y install certbot if [[ "$HTTPTYPE" == "NGINX" ]]; then dnf -y install python3-certbot-nginx elif [[ "$HTTPTYPE" == "APACHE" ]]; then dnf -y install python3-certbot-apache fi fi # Request certificate log_info "Requesting TLS certificate for $SERVERNAME" if [[ "$HTTPTYPE" == "NGINX" ]]; then certbot --non-interactive --redirect --agree-tos \ --nginx -d "$SERVERNAME" -m "$EMAIL" systemctl reload nginx elif [[ "$HTTPTYPE" == "APACHE" ]]; then certbot --non-interactive --redirect --agree-tos \ --apache -d "$SERVERNAME" -m "$EMAIL" if [[ "$PKG_TYPE" == "deb" ]]; then systemctl reload apache2 else systemctl reload httpd fi fi # Certbot auto-renewal is handled by the systemd timer installed by certbot if systemctl is-enabled certbot.timer >/dev/null 2>&1; then log_info "Certbot auto-renewal timer is active" elif systemctl is-enabled snap.certbot.renew.timer >/dev/null 2>&1; then log_info "Certbot snap auto-renewal timer is active" else log_warn "No certbot auto-renewal timer found -- add a cron job for 'certbot renew'" fi log "TLS certificate installed" } # ============================================================================ # SELINUX (RHEL-based only) # ============================================================================ configure_selinux() { if ! command -v getenforce >/dev/null 2>&1; then return fi if [[ "$(getenforce 2>/dev/null)" == "Enforcing" ]]; then log_info "Configuring SELinux for reverse proxy" setsebool -P httpd_can_network_connect 1 2>/dev/null || true fi } # ============================================================================ # AUTO-UPDATE CRON # ============================================================================ install_update_cron() { if [[ $INSTALL_CRON -eq 0 ]]; then return fi log "Installing auto-update cron job" local update_url="https://mylinux.work/downloads/update-code-server.sh" local script_dest="/usr/local/bin/update-code-server.sh" curl -fsSL -o "$script_dest" "$update_url" chmod 700 "$script_dest" log_info "Update script installed to $script_dest" # Build cron entry -- run every N days at 3:00 AM local cron_line="0 3 */${CRON_DAYS} * * ${script_dest} 2>&1 | logger -t update-code-server" local cron_marker="# code-server auto-update" # Remove existing code-server cron entries local existing_cron existing_cron=$(crontab -l 2>/dev/null || true) local new_cron new_cron=$(echo "$existing_cron" | grep -v "$cron_marker" | grep -v "update-code-server" || true) # Add new entry echo "${new_cron} ${cron_line} ${cron_marker}" | crontab - log "Cron job installed -- updates every $CRON_DAYS days at 3:00 AM" log_info "View with: crontab -l" } # ============================================================================ # MAIN # ============================================================================ main() { parse_args "$@" echo "" echo "==============================================" echo " Code-Server Install Script v${VERSION}" echo "==============================================" echo "" detect_os detect_arch preflight_checks log_info "OS: $OS_ID $OS_VERSION ($PKG_TYPE)" log_info "Architecture: $ARCH" log_info "Server: $SERVERNAME" log_info "Reverse proxy: $HTTPTYPE" log_info "Code directory: $CODEDIR" log_info "Install directory: $SERVDIR" log_info "Data directory: $USRDIR" echo "" update_system install_codeserver create_service case "$HTTPTYPE" in NGINX) install_nginx ;; APACHE) install_apache ;; CADDY) install_caddy ;; esac configure_selinux install_certbot install_update_cron echo "" echo "==============================================" echo -e " ${GREEN}Installation complete${NC}" echo "==============================================" echo "" echo " code-server is running at:" echo " https://$SERVERNAME" echo "" echo " Reverse proxy: $HTTPTYPE" echo " Data directory: $USRDIR" echo " Code directory: $CODEDIR" echo "" echo " Manage the service:" echo " systemctl status code-server" echo " systemctl restart code-server" echo "" echo "==============================================" } main "$@"