#!/bin/bash # CryptoPro CSP 5.0 R3 Linux installer (deb packages) # Uses locally provided .deb packages under /opt/cryptopro/downloads (host volume). # No Wine dependency. Runs offline against the supplied packages only. # # Env: # CRYPTOPRO_INSTALL_FROM Path to folder with .deb packages (default /opt/cryptopro/downloads) # CRYPTOPRO_ACCEPT_EULA Must be 1 to proceed (default 0 -> hard stop with warning) # CRYPTOPRO_SKIP_APT_FIX Set to 1 to skip `apt-get -f install` (offline strict) # CRYPTOPRO_PACKAGE_FILTER Optional glob (e.g., "cprocsp*amd64.deb") to narrow selection # # Exit codes: # 0 success; 1 missing dir/files; 2 incompatible arch; 3 EULA not accepted. set -euo pipefail INSTALL_FROM="${CRYPTOPRO_INSTALL_FROM:-/opt/cryptopro/downloads}" PACKAGE_FILTER="${CRYPTOPRO_PACKAGE_FILTER:-*.deb}" SKIP_APT_FIX="${CRYPTOPRO_SKIP_APT_FIX:-0}" STAGING_DIR="/tmp/cryptopro-debs" MINIMAL="${CRYPTOPRO_MINIMAL:-1}" INCLUDE_PLUGIN="${CRYPTOPRO_INCLUDE_PLUGIN:-0}" arch_from_uname() { local raw raw="$(uname -m)" case "${raw}" in x86_64) echo "amd64" ;; aarch64) echo "arm64" ;; arm64) echo "arm64" ;; i386|i686) echo "i386" ;; *) echo "${raw}" ;; esac } HOST_ARCH="$(dpkg --print-architecture 2>/dev/null || arch_from_uname)" log() { echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] [cryptopro-install] $*" } log_err() { echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] [cryptopro-install] [ERROR] $*" >&2 } require_eula() { if [[ "${CRYPTOPRO_ACCEPT_EULA:-0}" != "1" ]]; then log_err "License not accepted. Set CRYPTOPRO_ACCEPT_EULA=1 only if you hold a valid CryptoPro license for these binaries and agree to the vendor EULA." exit 3 fi } maybe_extract_bundle() { # Prefer a bundle that matches host arch in filename, otherwise first *.tgz mapfile -t TGZ < <(find "${INSTALL_FROM}" -maxdepth 1 -type f -name "*.tgz" -print 2>/dev/null | sort) if [[ ${#TGZ[@]} -eq 0 ]]; then return fi local chosen="" for candidate in "${TGZ[@]}"; do if [[ "${candidate}" == *"${HOST_ARCH}"* ]]; then chosen="${candidate}" break fi done if [[ -z "${chosen}" ]]; then chosen="${TGZ[0]}" fi log "Extracting bundle ${chosen} into ${STAGING_DIR}" rm -rf "${STAGING_DIR}" mkdir -p "${STAGING_DIR}" tar -xf "${chosen}" -C "${STAGING_DIR}" # If bundle contains a single subfolder, use it as install root local subdir subdir="$(find "${STAGING_DIR}" -maxdepth 1 -type d ! -path "${STAGING_DIR}" | head -n1)" if [[ -n "${subdir}" ]]; then INSTALL_FROM="${subdir}" else INSTALL_FROM="${STAGING_DIR}" fi } gather_packages() { if [[ ! -d "${INSTALL_FROM}" ]]; then log_err "Package directory not found: ${INSTALL_FROM}" exit 1 fi maybe_extract_bundle mapfile -t PKGS < <(find "${INSTALL_FROM}" -maxdepth 2 -type f -name "${PACKAGE_FILTER}" -print 2>/dev/null | sort) if [[ ${#PKGS[@]} -eq 0 ]]; then log_err "No .deb packages found in ${INSTALL_FROM} (filter=${PACKAGE_FILTER})" exit 1 fi } apply_minimal_filter() { if [[ "${MINIMAL}" != "1" ]]; then return fi local -a keep_exact=( "lsb-cprocsp-base" "lsb-cprocsp-ca-certs" "lsb-cprocsp-capilite-64" "lsb-cprocsp-kc1-64" "lsb-cprocsp-pkcs11-64" "lsb-cprocsp-rdr-64" "cprocsp-curl-64" "cprocsp-pki-cades-64" "cprocsp-compat-debian" ) if [[ "${INCLUDE_PLUGIN}" == "1" ]]; then keep_exact+=("cprocsp-pki-plugin-64" "cprocsp-rdr-gui-gtk-64") fi local -a filtered=() for pkg in "${PKGS[@]}"; do local name name="$(dpkg-deb -f "${pkg}" Package 2>/dev/null || basename "${pkg}")" for wanted in "${keep_exact[@]}"; do if [[ "${name}" == "${wanted}" ]]; then filtered+=("${pkg}") break fi done done if [[ ${#filtered[@]} -gt 0 ]]; then log "Applying minimal package set (CRYPTOPRO_MINIMAL=1); kept ${#filtered[@]} of ${#PKGS[@]}" PKGS=("${filtered[@]}") else log "Minimal filter yielded no matches; using full package set" fi } filter_by_arch() { FILTERED=() for pkg in "${PKGS[@]}"; do local pkg_arch pkg_arch="$(dpkg-deb -f "${pkg}" Architecture 2>/dev/null || echo "unknown")" if [[ "${pkg_arch}" == "all" || "${pkg_arch}" == "${HOST_ARCH}" ]]; then FILTERED+=("${pkg}") else log "Skipping ${pkg} (arch=${pkg_arch}, host=${HOST_ARCH})" fi done if [[ ${#FILTERED[@]} -eq 0 ]]; then log_err "No packages match host architecture ${HOST_ARCH}" exit 2 fi } print_matrix() { log "Discovered packages (arch filter: host=${HOST_ARCH}):" for pkg in "${FILTERED[@]}"; do local name ver arch name="$(dpkg-deb -f "${pkg}" Package 2>/dev/null || basename "${pkg}")" ver="$(dpkg-deb -f "${pkg}" Version 2>/dev/null || echo "unknown")" arch="$(dpkg-deb -f "${pkg}" Architecture 2>/dev/null || echo "unknown")" echo " - ${name} ${ver} (${arch}) <- ${pkg}" done } install_packages() { log "Installing ${#FILTERED[@]} package(s) from ${INSTALL_FROM}" if ! dpkg -i "${FILTERED[@]}"; then if [[ "${SKIP_APT_FIX}" == "1" ]]; then log_err "dpkg reported errors and CRYPTOPRO_SKIP_APT_FIX=1; aborting." exit 1 fi log "Resolving dependencies with apt-get -f install (may require network if deps missing locally)" apt-get update >/dev/null DEBIAN_FRONTEND=noninteractive apt-get -y -f install fi log "CryptoPro packages installed. Verify with: dpkg -l | grep cprocsp" } main() { require_eula gather_packages apply_minimal_filter filter_by_arch print_matrix install_packages log "Installation finished. For headless/server use on Ubuntu 22.04 (amd64), the 'linux-amd64_deb.tgz' bundle is preferred and auto-selected." } main "$@"