mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 16:21:30 +00:00
0729ee46e0
Post-transfer cosmetic + release-critical URL refresh after moving the
repo from github.com/shankar0123/certctl to github.com/certctl-io/certctl
(2026-05-03). GitHub HTTP redirects continue to forward old URLs forever,
so existing operators are not broken — but aligns the canonical
references with the new owner so:
- procurement engineers / contributors browsing the docs see the right
URL on first read
- operators copying the agent install one-liner hit the new path
directly without going through a redirect
- the Helm chart's default image repository points at the canonical org
registry path
- the OnboardingWizard rendered to first-run UI users shows the new
URL in the install snippets and doc anchor links
- the GitHub Actions release workflow pushes container images to
ghcr.io/certctl-io/certctl-{server,agent} (was: shankar0123)
- the release-notes Markdown body in release.yml — which gets stamped
into every future release page — references the post-transfer
cert-identity (cosign keyless signing now uses the certctl-io
workflow URL) and the post-transfer SLSA provenance source-uri.
Without this, every cosign verify / slsa-verifier command on a
v2.1.0+ release would fail because the cert-identity-regexp would
not match the signing identity GitHub Actions OIDC issues post-
transfer. Old releases (v2.0.67 and earlier) keep their immutable
release-notes pointing at the shankar0123 path and remain
verifiable via their own published instructions.
Customer impact:
- Operators on ghcr.io/shankar0123/certctl-{server,agent}:latest
silently freeze on whatever tag was current at transfer time. They
get no errors; they just stop receiving updates. The next release
notes need a one-line callout (Phase 3.1 of cowork/transfer-
certctl-to-org.md) telling them to update their image path to
ghcr.io/certctl-io/certctl-{server,agent}.
- All other URLs (git clone, install one-liner, raw.githubusercontent
URLs, browser links, GitHub API) continue to resolve via permanent
HTTP redirects. The sweep is cosmetic for those.
Files swept (30 total):
.github/workflows/release.yml — IMAGE_NAMESPACE, source-uri,
cosign cert-identity-regexp, IMAGE= snippet (5 refs total).
CHANGELOG.md, README.md — anchor links, badges, install one-liner,
cosign verify snippets in operator-facing sections.
api/openapi.yaml — info / externalDocs URLs.
install-agent.sh — GITHUB_REPO const + systemd unit Documentation=
field.
deploy/ENVIRONMENTS.md, deploy/helm/{CHART_SUMMARY,INDEX,
INSTALLATION,README}.md, deploy/helm/certctl/{Chart.yaml,
README.md,values.yaml}, deploy/helm/examples/values-*.yaml —
chart docs + image repository defaults across dev / prod-ha
overrides.
docs/{certctl-for-cert-manager-users,connector-iis,connectors,
migrate-from-acmesh,migrate-from-certbot,quickstart,test-env,
why-certctl}.md — operator-facing doc URLs.
examples/{acme-nginx,acme-wildcard-dns01,multi-issuer,
private-ca-traefik,step-ca-haproxy}/docker-compose.yml +
examples/step-ca-haproxy/step-ca-haproxy.md — example image:
paths and accompanying narrative.
web/src/pages/OnboardingWizard.tsx — first-run-UI URL refs (curl
install one-liners, agent docker image path, doc anchor links).
Files intentionally NOT swept (Choice A from cowork/transfer-certctl-
to-org.md):
go.mod, go.sum — module declaration stays github.com/shankar0123/
certctl. Existing imports compile because Go uses the path
declared in go.mod, not the URL it was fetched from. Internal-
only project; no external Go consumers; rename will land as a
mechanical sed when one materializes.
~250 *.go files — every import remains github.com/shankar0123/
certctl/internal/...
deploy/test/f5-mock-icontrol/go.mod — separate test sub-module;
same Choice A logic; module path stays.
Files intentionally NOT swept (other reasons):
README.md lines 244-245 — Scarf-pixel docker-pull commands.
shankar0123.docker.scarf.sh/... is a Scarf-account hostname
(per-user, not per-repo) and the pixel keeps tracking pulls
against the operator's personal Scarf account. Migrating to a
certctl-io Scarf account is a separate decision (create org
Scarf account → re-create package → update README).
deploy/test/f5-mock-icontrol/f5-mock-icontrol — checked-in
compiled binary with shankar0123/certctl baked into Go build
info via the sub-module path. Out of scope for a URL sweep;
will refresh on the next `make test-integration` rebuild.
Verification:
gofmt: clean (no .go files touched).
go vet ./...: clean (verified at this SHA in 1.3 of the transfer
checklist; no .go changes since).
go build ./...: clean (same).
go test -short on representative packages: green (same).
Diff shape: 30 files, 74 insertions / 74 deletions, net-zero size,
pure URL substitution.
631 lines
20 KiB
Bash
631 lines
20 KiB
Bash
#!/bin/bash
|
|
# certctl Agent Install Script
|
|
# Detects OS (Linux/macOS) and architecture, downloads binary from GitHub Releases,
|
|
# installs to system path, configures service (systemd/launchd), and prompts for config.
|
|
|
|
set -euo pipefail
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Configuration
|
|
GITHUB_REPO="certctl-io/certctl"
|
|
RELEASE_URL="https://github.com/${GITHUB_REPO}/releases/latest/download"
|
|
INSTALL_DIR="/usr/local/bin"
|
|
SERVICE_NAME="certctl-agent"
|
|
|
|
# Detect OS and architecture
|
|
detect_platform() {
|
|
local os="$(uname -s)"
|
|
local arch="$(uname -m)"
|
|
|
|
case "$os" in
|
|
Linux*)
|
|
OS_TYPE="linux"
|
|
;;
|
|
Darwin*)
|
|
OS_TYPE="darwin"
|
|
;;
|
|
*)
|
|
echo -e "${RED}Error: Unsupported OS: $os${NC}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
case "$arch" in
|
|
x86_64)
|
|
ARCH_TYPE="amd64"
|
|
;;
|
|
aarch64|arm64)
|
|
ARCH_TYPE="arm64"
|
|
;;
|
|
*)
|
|
echo -e "${RED}Error: Unsupported architecture: $arch${NC}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Print usage information
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $0 [OPTIONS]
|
|
|
|
Install and configure the certctl agent on your system.
|
|
|
|
OPTIONS:
|
|
-h, --help Show this help message
|
|
--server-url URL Set CERTCTL_SERVER_URL (skips interactive prompt)
|
|
--api-key KEY Set CERTCTL_API_KEY (skips interactive prompt)
|
|
--agent-id ID Set CERTCTL_AGENT_ID (defaults to hostname)
|
|
--no-start Install but don't start the service
|
|
|
|
EXAMPLES:
|
|
# Interactive install (download first):
|
|
curl -sSLO https://raw.githubusercontent.com/${GITHUB_REPO}/master/install-agent.sh
|
|
chmod +x install-agent.sh
|
|
sudo ./install-agent.sh
|
|
|
|
# Non-interactive install (pipe via curl):
|
|
curl -sSL https://raw.githubusercontent.com/${GITHUB_REPO}/master/install-agent.sh \\
|
|
| sudo bash -s -- \\
|
|
--server-url https://certctl.example.com \\
|
|
--api-key YOUR_API_KEY
|
|
|
|
CONTROL-PLANE TLS TRUST:
|
|
The certctl server is HTTPS-only as of v2.2. This installer does NOT copy a CA
|
|
bundle — the generated agent.env leaves TLS trust to the system root store by
|
|
default. If the server uses a private/enterprise or self-signed CA, set
|
|
CERTCTL_SERVER_CA_BUNDLE_PATH in the generated agent.env to point at the CA
|
|
bundle, or (dev only) CERTCTL_SERVER_TLS_INSECURE_SKIP_VERIFY=true. See the
|
|
commented block in the generated agent.env for the full menu.
|
|
|
|
EOF
|
|
}
|
|
|
|
# Parse command-line arguments
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
--server-url)
|
|
SERVER_URL="${2:-}"
|
|
if [[ -z "$SERVER_URL" ]]; then
|
|
echo -e "${RED}Error: --server-url requires a value${NC}" >&2
|
|
exit 1
|
|
fi
|
|
shift 2
|
|
;;
|
|
--server-url=*)
|
|
SERVER_URL="${1#*=}"
|
|
shift
|
|
;;
|
|
--api-key)
|
|
API_KEY="${2:-}"
|
|
if [[ -z "$API_KEY" ]]; then
|
|
echo -e "${RED}Error: --api-key requires a value${NC}" >&2
|
|
exit 1
|
|
fi
|
|
shift 2
|
|
;;
|
|
--api-key=*)
|
|
API_KEY="${1#*=}"
|
|
shift
|
|
;;
|
|
--agent-id)
|
|
AGENT_ID="${2:-}"
|
|
if [[ -z "$AGENT_ID" ]]; then
|
|
echo -e "${RED}Error: --agent-id requires a value${NC}" >&2
|
|
exit 1
|
|
fi
|
|
shift 2
|
|
;;
|
|
--agent-id=*)
|
|
AGENT_ID="${1#*=}"
|
|
shift
|
|
;;
|
|
--no-start)
|
|
NO_START=true
|
|
shift
|
|
;;
|
|
*)
|
|
echo -e "${RED}Error: Unknown option: $1${NC}" >&2
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Ensure stdin is interactive before prompting. When the script is piped via
|
|
# curl|bash, stdin is the pipe from curl, so `read` hits EOF immediately and
|
|
# set -e aborts the script silently. Reopen stdin from the controlling terminal
|
|
# (/dev/tty) if available; otherwise print a helpful error pointing at the
|
|
# flag-based non-interactive install.
|
|
ensure_interactive_input() {
|
|
# If all required config is already provided via flags, no prompting needed.
|
|
if [[ -n "${SERVER_URL:-}" && -n "${API_KEY:-}" ]]; then
|
|
return
|
|
fi
|
|
|
|
# Already interactive — nothing to do.
|
|
if [[ -t 0 ]]; then
|
|
return
|
|
fi
|
|
|
|
# Piped stdin — try to reopen from the controlling terminal. Actually
|
|
# attempt to open /dev/tty inside a subshell: the device node may exist
|
|
# even when the process has no controlling terminal (ENXIO on open), so
|
|
# `[[ -r /dev/tty ]]` is not reliable.
|
|
if ( exec </dev/tty ) 2>/dev/null; then
|
|
exec </dev/tty
|
|
return
|
|
fi
|
|
|
|
# No terminal available — emit clear guidance and exit.
|
|
# Use printf '%b' so the ANSI color escapes in $RED/$NC are interpreted
|
|
# rather than rendered as literal backslash sequences (a heredoc would
|
|
# keep them as raw text).
|
|
{
|
|
printf '%b\n' "${RED}Error: No interactive terminal available.${NC}"
|
|
printf '\n'
|
|
printf 'The installer was piped through curl and no controlling terminal (/dev/tty)\n'
|
|
printf 'is available for prompts. Pass the required values as flags instead:\n'
|
|
printf '\n'
|
|
printf ' curl -sSL https://raw.githubusercontent.com/%s/master/install-agent.sh \\\n' "$GITHUB_REPO"
|
|
printf ' | sudo bash -s -- \\\n'
|
|
printf ' --server-url https://certctl.example.com \\\n'
|
|
printf ' --api-key YOUR_API_KEY\n'
|
|
printf '\n'
|
|
printf 'Or download the script first and run it directly:\n'
|
|
printf '\n'
|
|
printf ' curl -sSLO https://raw.githubusercontent.com/%s/master/install-agent.sh\n' "$GITHUB_REPO"
|
|
printf ' chmod +x install-agent.sh\n'
|
|
printf ' sudo ./install-agent.sh\n'
|
|
printf '\n'
|
|
} >&2
|
|
exit 1
|
|
}
|
|
|
|
# Check if running as root/sudo on Linux
|
|
check_privileges() {
|
|
if [[ "$OS_TYPE" == "linux" && "$EUID" -ne 0 ]]; then
|
|
echo -e "${RED}Error: This script must be run as root on Linux. Try: sudo $0${NC}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Download agent binary from GitHub Releases
|
|
# IMPORTANT: main() captures this function's stdout via `binary_path=$(download_binary)`,
|
|
# so every status/error message MUST go to stderr (>&2). Only the final
|
|
# `echo "$temp_file"` is allowed on stdout — that's the return value.
|
|
#
|
|
# We deliberately do NOT register an EXIT trap to clean up $temp_file: because
|
|
# of the command substitution, this function runs in a subshell, and any EXIT
|
|
# trap set here fires when the subshell exits — which is *before* install_binary
|
|
# gets a chance to cp the file. Cleanup on success is install_binary's job
|
|
# (after the cp), and cleanup on curl failure is handled inline below.
|
|
download_binary() {
|
|
local binary_name="certctl-agent-${OS_TYPE}-${ARCH_TYPE}"
|
|
local download_url="${RELEASE_URL}/${binary_name}"
|
|
|
|
echo -e "${YELLOW}Downloading certctl agent (${OS_TYPE}-${ARCH_TYPE})...${NC}" >&2
|
|
|
|
if ! command -v curl &> /dev/null; then
|
|
echo -e "${RED}Error: curl is required but not installed${NC}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
local temp_file
|
|
temp_file=$(mktemp)
|
|
|
|
if ! curl -sSL -f "$download_url" -o "$temp_file" >&2; then
|
|
rm -f "$temp_file"
|
|
echo -e "${RED}Error: Failed to download binary from $download_url${NC}" >&2
|
|
echo "Make sure the latest release exists on GitHub with the binary asset for ${OS_TYPE}-${ARCH_TYPE}." >&2
|
|
exit 1
|
|
fi
|
|
|
|
chmod +x "$temp_file"
|
|
echo "$temp_file"
|
|
}
|
|
|
|
# Install binary to system path
|
|
install_binary() {
|
|
local binary_path="$1"
|
|
|
|
echo -e "${YELLOW}Installing to $INSTALL_DIR/$SERVICE_NAME...${NC}"
|
|
|
|
if [[ "$OS_TYPE" == "linux" ]]; then
|
|
cp "$binary_path" "$INSTALL_DIR/$SERVICE_NAME"
|
|
else
|
|
# macOS: use sudo if not already running as root
|
|
if [[ "$EUID" -ne 0 ]]; then
|
|
sudo cp "$binary_path" "$INSTALL_DIR/$SERVICE_NAME"
|
|
else
|
|
cp "$binary_path" "$INSTALL_DIR/$SERVICE_NAME"
|
|
fi
|
|
fi
|
|
|
|
chmod +x "$INSTALL_DIR/$SERVICE_NAME"
|
|
echo -e "${GREEN}Binary installed: $INSTALL_DIR/$SERVICE_NAME${NC}"
|
|
|
|
# Clean up the temp file created by download_binary. We can't use an EXIT
|
|
# trap inside download_binary because it runs in a subshell (command
|
|
# substitution), so the trap would fire before we got here. Doing it
|
|
# explicitly after the successful cp is the simplest correct pattern.
|
|
rm -f "$binary_path"
|
|
}
|
|
|
|
# Prompt for configuration. Any value supplied via flag is honored as-is
|
|
# and we only prompt for the missing pieces. `read || true` prevents set -e
|
|
# from aborting the script on EOF — instead the empty check below fires the
|
|
# proper "required" error message.
|
|
prompt_for_config() {
|
|
if [[ -z "${SERVER_URL:-}" ]]; then
|
|
echo ""
|
|
echo -e "${YELLOW}Enter certctl server URL (e.g., https://certctl.example.com):${NC}"
|
|
read -r SERVER_URL || true
|
|
if [[ -z "${SERVER_URL:-}" ]]; then
|
|
echo -e "${RED}Error: Server URL is required${NC}" >&2
|
|
echo "Hint: pass --server-url <URL> to run non-interactively." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [[ -z "${API_KEY:-}" ]]; then
|
|
echo -e "${YELLOW}Enter certctl API key:${NC}"
|
|
read -rs API_KEY || true
|
|
echo ""
|
|
if [[ -z "${API_KEY:-}" ]]; then
|
|
echo -e "${RED}Error: API key is required${NC}" >&2
|
|
echo "Hint: pass --api-key <KEY> to run non-interactively." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [[ -z "${AGENT_ID:-}" ]]; then
|
|
local default_agent_id
|
|
default_agent_id="$(hostname)"
|
|
# If stdin is still piped (no /dev/tty was available but SERVER_URL +
|
|
# API_KEY arrived via flags), skip the prompt entirely and use the
|
|
# default — no need to block on an optional value.
|
|
if [[ -t 0 ]]; then
|
|
echo -e "${YELLOW}Enter agent ID (default: $default_agent_id):${NC}"
|
|
read -r AGENT_ID || true
|
|
fi
|
|
if [[ -z "${AGENT_ID:-}" ]]; then
|
|
AGENT_ID="$default_agent_id"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Create configuration directory and env file (Linux)
|
|
setup_linux_config() {
|
|
local config_dir="/etc/certctl"
|
|
local config_file="$config_dir/agent.env"
|
|
local key_dir="/var/lib/certctl/keys"
|
|
|
|
echo -e "${YELLOW}Creating configuration directory...${NC}"
|
|
|
|
# Create /etc/certctl with restrictive permissions
|
|
mkdir -p "$config_dir"
|
|
chmod 755 "$config_dir"
|
|
|
|
# Create key storage directory with 0700 permissions
|
|
mkdir -p "$key_dir"
|
|
chmod 700 "$key_dir"
|
|
|
|
# Write agent configuration (overwrite if exists)
|
|
cat > "$config_file" <<EOF
|
|
# certctl Agent Configuration
|
|
# Generated by install-agent.sh on $(date)
|
|
|
|
# Agent ID (unique identifier in the fleet)
|
|
CERTCTL_AGENT_ID=$AGENT_ID
|
|
|
|
# Control plane server URL (HTTPS-only as of v2.2)
|
|
CERTCTL_SERVER_URL=$SERVER_URL
|
|
|
|
# API authentication key
|
|
CERTCTL_API_KEY=$API_KEY
|
|
|
|
# Key generation mode (agent = agent-side keygen, server = server-side for demo only)
|
|
CERTCTL_KEYGEN_MODE=agent
|
|
|
|
# Key storage directory (agent-side keygen)
|
|
CERTCTL_KEY_DIR=$key_dir
|
|
|
|
# ---- Control-plane TLS trust ----
|
|
# The certctl server is HTTPS-only (v2.2+). The agent's HTTP client MUST trust the
|
|
# server's certificate chain. Pick ONE of the approaches below:
|
|
#
|
|
# 1) Public CA (Let's Encrypt, DigiCert, etc.) — no config needed; system trust store works.
|
|
# 2) Private / enterprise CA — point the agent at the CA bundle that signed the server cert:
|
|
# CERTCTL_SERVER_CA_BUNDLE_PATH=/etc/certctl/server-ca.crt
|
|
#
|
|
# 3) Self-signed server cert (Helm/compose bootstrap) — same env var, just point at the
|
|
# extracted self-signed CA bundle (e.g. from the certctl-server-tls Kubernetes secret
|
|
# via: kubectl get secret certctl-server-tls -o jsonpath='{.data.ca\.crt}' | base64 -d).
|
|
#
|
|
# 4) Dev/eval only — disable verification entirely (NEVER do this in production):
|
|
# CERTCTL_SERVER_TLS_INSECURE_SKIP_VERIFY=true
|
|
|
|
# Logging level (debug, info, warn, error)
|
|
# CERTCTL_LOG_LEVEL=info
|
|
|
|
# Discovery directories (comma-separated paths to scan for existing certs)
|
|
# CERTCTL_DISCOVERY_DIRS=/etc/letsencrypt/live,/etc/ssl/certs
|
|
|
|
# Enable deployment verification (TLS endpoint check post-deployment)
|
|
# CERTCTL_VERIFY_DEPLOYMENT=true
|
|
EOF
|
|
|
|
# Restrict permissions on env file (contains API key)
|
|
chmod 600 "$config_file"
|
|
echo -e "${GREEN}Configuration written to: $config_file${NC}"
|
|
}
|
|
|
|
# Create configuration directory and env file (macOS)
|
|
setup_macos_config() {
|
|
local config_dir="$HOME/.certctl"
|
|
local config_file="$config_dir/agent.env"
|
|
local key_dir="$config_dir/keys"
|
|
|
|
echo -e "${YELLOW}Creating configuration directory...${NC}"
|
|
|
|
# Create ~/.certctl with restrictive permissions
|
|
mkdir -p "$config_dir"
|
|
chmod 700 "$config_dir"
|
|
|
|
# Create key storage directory
|
|
mkdir -p "$key_dir"
|
|
chmod 700 "$key_dir"
|
|
|
|
# Write agent configuration (overwrite if exists)
|
|
cat > "$config_file" <<EOF
|
|
# certctl Agent Configuration
|
|
# Generated by install-agent.sh on $(date)
|
|
|
|
# Agent ID (unique identifier in the fleet)
|
|
CERTCTL_AGENT_ID=$AGENT_ID
|
|
|
|
# Control plane server URL (HTTPS-only as of v2.2)
|
|
CERTCTL_SERVER_URL=$SERVER_URL
|
|
|
|
# API authentication key
|
|
CERTCTL_API_KEY=$API_KEY
|
|
|
|
# Key generation mode (agent = agent-side keygen, server = server-side for demo only)
|
|
CERTCTL_KEYGEN_MODE=agent
|
|
|
|
# Key storage directory (agent-side keygen)
|
|
CERTCTL_KEY_DIR=$key_dir
|
|
|
|
# ---- Control-plane TLS trust ----
|
|
# The certctl server is HTTPS-only (v2.2+). The agent's HTTP client MUST trust the
|
|
# server's certificate chain. Pick ONE of the approaches below:
|
|
#
|
|
# 1) Public CA (Let's Encrypt, DigiCert, etc.) — no config needed; system trust store works.
|
|
# 2) Private / enterprise CA — point the agent at the CA bundle that signed the server cert:
|
|
# CERTCTL_SERVER_CA_BUNDLE_PATH=$HOME/.certctl/server-ca.crt
|
|
#
|
|
# 3) Self-signed server cert (Helm/compose bootstrap) — same env var, just point at the
|
|
# extracted self-signed CA bundle (e.g. from the certctl-server-tls Kubernetes secret
|
|
# via: kubectl get secret certctl-server-tls -o jsonpath='{.data.ca\.crt}' | base64 -d).
|
|
#
|
|
# 4) Dev/eval only — disable verification entirely (NEVER do this in production):
|
|
# CERTCTL_SERVER_TLS_INSECURE_SKIP_VERIFY=true
|
|
|
|
# Logging level (debug, info, warn, error)
|
|
# CERTCTL_LOG_LEVEL=info
|
|
|
|
# Discovery directories (comma-separated paths to scan for existing certs)
|
|
# CERTCTL_DISCOVERY_DIRS=/etc/letsencrypt/live,/etc/ssl/certs
|
|
|
|
# Enable deployment verification (TLS endpoint check post-deployment)
|
|
# CERTCTL_VERIFY_DEPLOYMENT=true
|
|
EOF
|
|
|
|
# Restrict permissions on env file (contains API key)
|
|
chmod 600 "$config_file"
|
|
echo -e "${GREEN}Configuration written to: $config_file${NC}"
|
|
}
|
|
|
|
# Create and enable systemd service (Linux only)
|
|
setup_systemd_service() {
|
|
local service_file="/etc/systemd/system/${SERVICE_NAME}.service"
|
|
|
|
echo -e "${YELLOW}Creating systemd service file...${NC}"
|
|
|
|
cat > "$service_file" <<'EOF'
|
|
[Unit]
|
|
Description=certctl Agent - Certificate Lifecycle Management
|
|
Documentation=https://github.com/certctl-io/certctl
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=root
|
|
Restart=on-failure
|
|
RestartSec=10s
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
|
|
# Load environment from /etc/certctl/agent.env
|
|
EnvironmentFile=/etc/certctl/agent.env
|
|
|
|
# Command to start the agent
|
|
ExecStart=/usr/local/bin/certctl-agent
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
chmod 644 "$service_file"
|
|
echo -e "${GREEN}Service file created: $service_file${NC}"
|
|
|
|
# Reload systemd daemon
|
|
systemctl daemon-reload
|
|
}
|
|
|
|
# Create and enable launchd plist (macOS only)
|
|
setup_launchd_service() {
|
|
local plist_file="$HOME/Library/LaunchAgents/com.certctl.agent.plist"
|
|
local config_file="$HOME/.certctl/agent.env"
|
|
local launcher_script="$HOME/.certctl/launcher.sh"
|
|
local home_dir="$HOME"
|
|
|
|
echo -e "${YELLOW}Creating launchd service file...${NC}"
|
|
|
|
mkdir -p "$(dirname "$plist_file")"
|
|
|
|
# Create wrapper script that sources env file before executing agent
|
|
cat > "$launcher_script" <<'LAUNCHER_SCRIPT'
|
|
#!/bin/bash
|
|
set -a
|
|
source "$HOME/.certctl/agent.env"
|
|
set +a
|
|
exec /usr/local/bin/certctl-agent
|
|
LAUNCHER_SCRIPT
|
|
|
|
chmod 755 "$launcher_script"
|
|
|
|
# Create plist that references the launcher script
|
|
cat > "$plist_file" <<EOF
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>Label</key>
|
|
<string>com.certctl.agent</string>
|
|
<key>ProgramArguments</key>
|
|
<array>
|
|
<string>$home_dir/.certctl/launcher.sh</string>
|
|
</array>
|
|
<key>EnvironmentVariables</key>
|
|
<dict>
|
|
<key>PATH</key>
|
|
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
<key>HOME</key>
|
|
<string>$home_dir</string>
|
|
</dict>
|
|
<key>KeepAlive</key>
|
|
<true/>
|
|
<key>RunAtLoad</key>
|
|
<true/>
|
|
<key>StandardErrorPath</key>
|
|
<string>$home_dir/.certctl/agent.log</string>
|
|
<key>StandardOutPath</key>
|
|
<string>$home_dir/.certctl/agent.log</string>
|
|
</dict>
|
|
</plist>
|
|
EOF
|
|
|
|
chmod 644 "$plist_file"
|
|
echo -e "${GREEN}Service file created: $plist_file${NC}"
|
|
echo -e "${GREEN}Launcher script created: $launcher_script${NC}"
|
|
}
|
|
|
|
# Start the agent service
|
|
start_service() {
|
|
if [[ "${NO_START:-false}" == "true" ]]; then
|
|
echo -e "${YELLOW}Service not started (--no-start flag used)${NC}"
|
|
return
|
|
fi
|
|
|
|
echo -e "${YELLOW}Starting certctl agent service...${NC}"
|
|
|
|
if [[ "$OS_TYPE" == "linux" ]]; then
|
|
systemctl enable "$SERVICE_NAME"
|
|
systemctl start "$SERVICE_NAME"
|
|
sleep 2
|
|
if systemctl is-active --quiet "$SERVICE_NAME"; then
|
|
echo -e "${GREEN}Service started successfully${NC}"
|
|
else
|
|
echo -e "${RED}Warning: Service may not have started. Check logs with: systemctl status $SERVICE_NAME${NC}"
|
|
fi
|
|
else
|
|
# macOS: load launchd service for current user
|
|
launchctl load "$HOME/Library/LaunchAgents/com.certctl.agent.plist" 2>/dev/null || true
|
|
sleep 1
|
|
echo -e "${GREEN}Service loaded into launchd${NC}"
|
|
fi
|
|
}
|
|
|
|
# Print success message with next steps
|
|
print_summary() {
|
|
echo ""
|
|
echo -e "${GREEN}========================================${NC}"
|
|
echo -e "${GREEN}certctl Agent Installation Complete${NC}"
|
|
echo -e "${GREEN}========================================${NC}"
|
|
echo ""
|
|
echo "Configuration:"
|
|
if [[ "$OS_TYPE" == "linux" ]]; then
|
|
echo " Config file: /etc/certctl/agent.env"
|
|
echo " Key storage: /var/lib/certctl/keys"
|
|
echo " Service: /etc/systemd/system/${SERVICE_NAME}.service"
|
|
echo " View logs: journalctl -u ${SERVICE_NAME} -f"
|
|
else
|
|
echo " Config file: $HOME/.certctl/agent.env"
|
|
echo " Key storage: $HOME/.certctl/keys"
|
|
echo " Service: $HOME/Library/LaunchAgents/com.certctl.agent.plist"
|
|
echo " View logs: tail -f $HOME/.certctl/agent.log"
|
|
fi
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo " 1. Verify the service is running"
|
|
if [[ "$OS_TYPE" == "linux" ]]; then
|
|
echo " systemctl status ${SERVICE_NAME}"
|
|
else
|
|
echo " launchctl list | grep certctl"
|
|
fi
|
|
echo ""
|
|
echo " 2. Visit your certctl dashboard: $SERVER_URL"
|
|
echo " 3. The agent should appear in the fleet overview within 30 seconds"
|
|
echo ""
|
|
}
|
|
|
|
# Main installation flow
|
|
main() {
|
|
parse_args "$@"
|
|
detect_platform
|
|
check_privileges
|
|
|
|
echo -e "${GREEN}certctl Agent Installer${NC}"
|
|
echo "Detected platform: ${OS_TYPE}-${ARCH_TYPE}"
|
|
echo ""
|
|
|
|
ensure_interactive_input
|
|
prompt_for_config
|
|
|
|
# Download and install binary
|
|
local binary_path
|
|
binary_path=$(download_binary)
|
|
install_binary "$binary_path"
|
|
|
|
# Setup OS-specific configuration
|
|
if [[ "$OS_TYPE" == "linux" ]]; then
|
|
setup_linux_config
|
|
setup_systemd_service
|
|
else
|
|
setup_macos_config
|
|
setup_launchd_service
|
|
fi
|
|
|
|
# Start the service
|
|
start_service
|
|
|
|
# Print summary
|
|
print_summary
|
|
}
|
|
|
|
main "$@"
|