Welcome to Sock Darning! Have a sock with a thinning heel or a toe hole you want to fix? Share your damage maps, yarn blend questions, and stitch experiments with other darning-minded visitors. Reading along is welcome too, so pull up a chair and join the conversation.

phase

  • Thread starter Thread starter Austin Songer, CISSP
  • Start date Start date
A

Austin Songer, CISSP

Guest
phase.sh

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Show hidden characters

#!/bin/bash
# Modified: 2025-10-05
# Phase Console Installation Script for Rocky Linux and CentOS
# Installs and configures Phase Console server with Tor hidden service
set -euo pipefail
# Configuration variables
PHASE_CONSOLE_DIR="/opt/phase-console"
PHASE_USER="phaseuser"
PHASE_GROUP="phaseuser"
TOR_DATA_DIR="/var/lib/tor"
SSH_PORT="2222"
PHASE_CONSOLE_PORT="3000"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging function
log() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
}
warn() {
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING:${NC} $1"
}
error() {
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR:${NC} $1"
exit 1
}
# Check if running as root
check_root() {
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root"
fi
}
# Check OS version (Rocky Linux or CentOS)
check_os_version() {
if ! grep -qE "(Rocky Linux|CentOS)" /etc/os-release; then
error "This script is designed for Rocky Linux or CentOS"
fi
local os_name=$(grep PRETTY_NAME /etc/os-release | cut -d'"' -f2)
local version=$(grep VERSION_ID /etc/os-release | cut -d'"' -f2)
log "Detected OS: $os_name (version: $version)"
}
# Update system packages
update_system() {
log "Updating system packages..."
dnf update -y
dnf install -y epel-release
dnf update -y
}
# Install required packages
install_packages() {
log "Installing required packages..."
# Core packages
dnf install -y \
curl \
wget \
git \
jq \
openssl \
firewalld \
policycoreutils-python-utils \
selinux-policy-devel
# Install Docker
log "Installing Docker..."
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Install Tor
log "Installing Tor..."
dnf install -y tor
# Install OpenSSH server
log "Installing OpenSSH server..."
dnf install -y openssh-server
}
# Ensure Tor user and group exist
ensure_tor_user() {
log "Ensuring Tor user and group exist..."
# Check if tor group exists, create if not
if ! getent group tor >/dev/null 2>&1; then
log "Creating tor group..."
groupadd -r tor
else
log "Tor group already exists"
fi
# Check if tor user exists, create if not
if ! getent passwd tor >/dev/null 2>&1; then
log "Creating tor user..."
useradd -r -g tor -d /var/lib/tor -s /bin/false -c "Tor daemon user" tor
else
log "Tor user already exists"
fi
# Ensure tor user home directory exists with correct ownership
if [[ ! -d /var/lib/tor ]]; then
mkdir -p /var/lib/tor
fi
chown tor:tor /var/lib/tor
chmod 700 /var/lib/tor
log "Tor user and group setup completed"
}
# Configure system services
configure_services() {
log "Configuring system services..."
# Enable and start Docker
systemctl enable docker
systemctl start docker
# Enable and start SSH
systemctl enable sshd
systemctl start sshd
# Configure Tor (will start later after configuration)
systemctl enable tor
}
# Create Phase Console user
create_phase_user() {
log "Creating Phase Console user..."
if ! id "$PHASE_USER" &>/dev/null; then
useradd -r -m -s /bin/bash -d "/home/$PHASE_USER" "$PHASE_USER"
usermod -aG docker "$PHASE_USER"
else
log "User $PHASE_USER already exists"
fi
}
# Create directory structure
create_directories() {
log "Creating directory structure..."
mkdir -p "$PHASE_CONSOLE_DIR"/{data,config,logs,ssh-keys,secrets}
mkdir -p "$TOR_DATA_DIR/phase-console"
mkdir -p "/home/$PHASE_USER/.ssh"
# Set ownership
chown -R "$PHASE_USER:$PHASE_GROUP" "$PHASE_CONSOLE_DIR"
chown -R "$PHASE_USER:$PHASE_GROUP" "/home/$PHASE_USER"
# Verify tor user exists before setting ownership
if getent passwd tor >/dev/null 2>&1; then
chown -R tor:tor "$TOR_DATA_DIR"
log "Set Tor data directory ownership to tor:tor"
else
error "Tor user does not exist. This should have been created by ensure_tor_user function."
fi
# Set permissions
chmod 700 "/home/$PHASE_USER/.ssh"
chmod 700 "$PHASE_CONSOLE_DIR/secrets"
chmod 755 "$PHASE_CONSOLE_DIR"/{data,config,logs,ssh-keys}
}
# Generate secrets
generate_secrets() {
log "Generating secrets..."
local secrets_dir="$PHASE_CONSOLE_DIR/secrets"
# Phase Console infrastructure secrets
log " Generating Phase Console infrastructure secrets..."
openssl rand -base64 32 > "$secrets_dir/postgres_password"
openssl rand -base64 32 > "$secrets_dir/redis_password"
openssl rand -base64 64 > "$secrets_dir/jwt_secret"
openssl rand -base64 32 > "$secrets_dir/encryption_key"
# ShadowBin Marketplace application secrets
log " Generating ShadowBin Marketplace application secrets..."
echo "base64:$(openssl rand -base64 32)" > "$secrets_dir/app_key"
openssl rand -base64 32 > "$secrets_dir/session_encryption_key"
openssl rand -hex 32 > "$secrets_dir/sanctum_secret_key"
openssl rand -base64 64 > "$secrets_dir/jwt_secret_app"
openssl rand -base64 32 > "$secrets_dir/encryption_master_key"
# Database secrets for ShadowBin
log " Generating database secrets..."
openssl rand -base64 24 > "$secrets_dir/db_password"
openssl rand -base64 32 > "$secrets_dir/db_root_password"
openssl rand -base64 24 > "$secrets_dir/redis_app_password"
# External API key placeholders (to be updated with real keys)
log " Creating external API key placeholders..."
echo "your_coinmarketcap_api_key_here" > "$secrets_dir/coinmarketcap_api_key"
echo "your_coingecko_api_key_here" > "$secrets_dir/coingecko_api_key"
echo "your_kraken_api_key_here" > "$secrets_dir/kraken_api_key"
echo "your_kraken_api_secret_here" > "$secrets_dir/kraken_api_secret"
# Monitoring secrets
log " Generating monitoring secrets..."
openssl rand -hex 32 > "$secrets_dir/telescope_secret"
openssl rand -hex 16 > "$secrets_dir/health_check_secret"
openssl rand -hex 24 > "$secrets_dir/metrics_api_token"
openssl rand -base64 32 > "$secrets_dir/log_encryption_key"
# Backup and recovery secrets
log " Generating backup secrets..."
openssl rand -base64 32 > "$secrets_dir/backup_encryption_key"
echo "your_s3_access_key_here" > "$secrets_dir/s3_access_key_id"
echo "your_s3_secret_key_here" > "$secrets_dir/s3_secret_access_key"
openssl rand -base64 32 > "$secrets_dir/disaster_recovery_key"
# Tor and network security
log " Generating network security secrets..."
openssl rand -base64 24 > "$secrets_dir/tor_control_password"
openssl rand -base64 32 > "$secrets_dir/proxy_auth_token"
# Set secure permissions
chmod 600 "$secrets_dir"/*
chown "$PHASE_USER:$PHASE_GROUP" "$secrets_dir"/*
log " Generated $(ls -1 "$secrets_dir" | wc -l) secrets for ShadowBin Marketplace"
}
# Configure Tor
configure_tor() {
log "Configuring Tor hidden service..."
# Backup original torrc
cp /etc/tor/torrc /etc/tor/torrc.backup
# Create Tor configuration
cat > /etc/tor/torrc << 'EOF'
# Phase Console Tor Configuration
# Created: 2025-09-14
# Basic configuration
User tor
DataDirectory /var/lib/tor
PidFile /var/run/tor/tor.pid
# Logging
Log notice file /var/log/tor/tor.log
Log warn file /var/log/tor/warn.log
# Hidden Service for Phase Console
HiddenServiceDir /var/lib/tor/phase-console/
HiddenServicePort 80 127.0.0.1:3000
HiddenServicePort 22 127.0.0.1:2222
# Security hardening
HiddenServiceVersion 3
HiddenServiceNumIntroductionPoints 10
HiddenServiceMaxStreams 65536
HiddenServiceMaxStreamsCloseCircuit 1
# Disable client functionality
SocksPort 0
ControlPort 0
CookieAuthentication 0
# Additional security
AvoidDiskWrites 1
DisableDebuggerAttachment 1
SafeLogging 1
WarnUnsafeSocks 1
# Circuit security
CircuitBuildTimeout 30
LearnCircuitBuildTimeout 0
MaxCircuitDirtiness 300
NewCircuitPeriod 15
MaxClientCircuitsPending 32
EOF
# Create log directory
mkdir -p /var/log/tor
chown tor:tor /var/log/tor
chmod 755 /var/log/tor
# Set SELinux context if enabled
if command -v setsebool &> /dev/null; then
setsebool -P tor_can_network_relay 1
setsebool -P tor_bind_all_unreserved_ports 1
fi
}
# Configure SSH
configure_ssh() {
log "Configuring SSH server..."
# Backup original sshd_config
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
# Create SSH configuration for Phase Console
cat >> /etc/ssh/sshd_config << EOF
# Phase Console SSH Configuration
# Added: 2025-09-14
Port $SSH_PORT
Protocol 2
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
# Security hardening
MaxAuthTries 3
MaxSessions 2
ClientAliveInterval 300
ClientAliveCountMax 2
TCPKeepAlive no
Compression no
# Restrict user access
AllowUsers $PHASE_USER
DenyUsers root
# Logging
LogLevel VERBOSE
SyslogFacility AUTH
# Disable unnecessary features
AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no
PermitTunnel no
GatewayPorts no
EOF
# Generate SSH host keys for custom port if needed
if [[ ! -f "/etc/ssh/ssh_host_rsa_key" ]]; then
ssh-keygen -A
fi
}
# Configure firewall
configure_firewall() {
log "Configuring firewall..."
# Start and enable firewalld
systemctl enable firewalld
systemctl start firewalld
# Allow SSH on custom port
firewall-cmd --permanent --add-port="$SSH_PORT/tcp"
# Allow Phase Console port (only from localhost)
firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='127.0.0.1' port protocol='tcp' port='$PHASE_CONSOLE_PORT' accept"
# Allow Tor
firewall-cmd --permanent --add-service=tor
# Reload firewall
firewall-cmd --reload
log "Firewall configured. Open ports: $SSH_PORT/tcp (SSH), $PHASE_CONSOLE_PORT/tcp (localhost only)"
}
# Create Docker Compose file
create_docker_compose() {
log "Creating Docker Compose configuration..."
cat > "$PHASE_CONSOLE_DIR/docker-compose.yml" << 'EOF'
# Phase Console Docker Compose
# Created: 2025-09-14
version: '3.8'
name: phase-console-server
networks:
phase-internal:
driver: bridge
ipam:
config:
- subnet: 172.30.0.0/16
volumes:
phase-postgres-data:
name: phase-postgres-data
phase-redis-data:
name: phase-redis-data
phase-app-data:
name: phase-app-data
services:
postgres:
image: postgres:15-alpine
container_name: phase-postgres
restart: unless-stopped
environment:
POSTGRES_DB: phase_console
POSTGRES_USER: phase_user
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
secrets:
- postgres_password
volumes:
- phase-postgres-data:/var/lib/postgresql/data
networks:
- phase-internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -U phase_user -d phase_console"]
interval: 30s
timeout: 10s
retries: 3
redis:
image: redis:7-alpine
container_name: phase-redis
restart: unless-stopped
command: ["redis-server", "--requirepass", "$(cat /run/secrets/redis_password)"]
secrets:
- redis_password
volumes:
- phase-redis-data:/data
networks:
- phase-internal
healthcheck:
test: ["CMD", "redis-cli", "--no-auth-warning", "-a", "$(cat /run/secrets/redis_password)", "ping"]
interval: 30s
timeout: 10s
retries: 3
phase-console:
image: phasehq/console:latest
container_name: phase-console-app
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
environment:
- DATABASE_URL=postgresql://phase_user:$(cat /run/secrets/postgres_password)@postgres:5432/phase_console
- REDIS_URL=redis://:$(cat /run/secrets/redis_password)@redis:6379
- JWT_SECRET_FILE=/run/secrets/jwt_secret
- ENCRYPTION_KEY_FILE=/run/secrets/encryption_key
- NODE_ENV=production
secrets:
- postgres_password
- redis_password
- jwt_secret
- encryption_key
volumes:
- phase-app-data:/app/data
networks:
- phase-internal
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
secrets:
postgres_password:
file: ./secrets/postgres_password
redis_password:
file: ./secrets/redis_password
jwt_secret:
file: ./secrets/jwt_secret
encryption_key:
file: ./secrets/encryption_key
EOF
chown "$PHASE_USER:$PHASE_GROUP" "$PHASE_CONSOLE_DIR/docker-compose.yml"
}
# Create systemd service for Phase Console
create_systemd_service() {
log "Creating systemd service for Phase Console..."
cat > /etc/systemd/system/phase-console.service << EOF
[Unit]
Description=Phase Console Server
Requires=docker.service tor.service
After=docker.service tor.service
StartLimitIntervalSec=0
[Service]
Type=oneshot
RemainAfterExit=yes
User=$PHASE_USER
Group=$PHASE_GROUP
WorkingDirectory=$PHASE_CONSOLE_DIR
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
ExecReload=/usr/bin/docker compose restart
TimeoutStartSec=300
TimeoutStopSec=120
Restart=on-failure
RestartSec=30
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable phase-console.service
}
# Create management scripts
create_management_scripts() {
log "Creating management scripts..."
# Phase Console control script
cat > "$PHASE_CONSOLE_DIR/phase-control.sh" << 'EOF'
#!/bin/bash
# Phase Console Control Script
# Created: 2025-09-14
set -euo pipefail
PHASE_CONSOLE_DIR="/opt/phase-console"
cd "$PHASE_CONSOLE_DIR"
case "${1:-}" in
start)
echo "Starting Phase Console..."
docker compose up -d
;;
stop)
echo "Stopping Phase Console..."
docker compose down
;;
restart)
echo "Restarting Phase Console..."
docker compose restart
;;
status)
echo "Phase Console Status:"
docker compose ps
;;
logs)
docker compose logs -f "${2:-}"
;;
update)
echo "Updating Phase Console..."
docker compose pull
docker compose up -d
;;
backup)
echo "Creating backup..."
backup_dir="/opt/phase-console/backups/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$backup_dir"
docker compose exec postgres pg_dump -U phase_user phase_console > "$backup_dir/database.sql"
cp -r ./secrets "$backup_dir/"
echo "Backup created at: $backup_dir"
;;
*)
echo "Usage: $0 {start|stop|restart|status|logs|update|backup}"
echo " logs [service] - Show logs for specific service or all"
exit 1
;;
esac
EOF
chmod +x "$PHASE_CONSOLE_DIR/phase-control.sh"
chown "$PHASE_USER:$PHASE_GROUP" "$PHASE_CONSOLE_DIR/phase-control.sh"
# Create symlink for easy access
ln -sf "$PHASE_CONSOLE_DIR/phase-control.sh" /usr/local/bin/phase-console
# SSH key management script
cat > "$PHASE_CONSOLE_DIR/manage-ssh-keys.sh" << 'EOF'
#!/bin/bash
# SSH Key Management Script for Phase Console
# Created: 2025-09-14
set -euo pipefail
PHASE_USER="phaseuser"
SSH_DIR="/home/$PHASE_USER/.ssh"
AUTHORIZED_KEYS="$SSH_DIR/authorized_keys"
case "${1:-}" in
add)
if [[ -z "${2:-}" ]]; then
echo "Usage: $0 add <public_key_file_or_string>"
exit 1
fi
if [[ -f "$2" ]]; then
# It's a file
cat "$2" >> "$AUTHORIZED_KEYS"
echo "Added public key from file: $2"
else
# It's a key string
echo "$2" >> "$AUTHORIZED_KEYS"
echo "Added public key string"
fi
# Set proper permissions
chmod 600 "$AUTHORIZED_KEYS"
chown "$PHASE_USER:$PHASE_USER" "$AUTHORIZED_KEYS"
;;
list)
echo "Authorized SSH keys:"
if [[ -f "$AUTHORIZED_KEYS" ]]; then
cat -n "$AUTHORIZED_KEYS"
else
echo "No authorized keys found"
fi
;;
remove)
if [[ -z "${2:-}" ]]; then
echo "Usage: $0 remove <line_number>"
exit 1
fi
if [[ -f "$AUTHORIZED_KEYS" ]]; then
sed -i "${2}d" "$AUTHORIZED_KEYS"
echo "Removed key at line $2"
else
echo "No authorized keys file found"
fi
;;
clear)
read -p "Are you sure you want to remove all SSH keys? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
> "$AUTHORIZED_KEYS"
echo "All SSH keys removed"
fi
;;
*)
echo "Usage: $0 {add|list|remove|clear}"
echo " add <key_file_or_string> - Add SSH public key"
echo " list - List all authorized keys"
echo " remove <line_number> - Remove key at specific line"
echo " clear - Remove all keys"
exit 1
;;
esac
EOF
chmod +x "$PHASE_CONSOLE_DIR/manage-ssh-keys.sh"
chown "$PHASE_USER:$PHASE_GROUP" "$PHASE_CONSOLE_DIR/manage-ssh-keys.sh"
# Create symlink for easy access
ln -sf "$PHASE_CONSOLE_DIR/manage-ssh-keys.sh" /usr/local/bin/phase-ssh-keys
}
# Start services
start_services() {
log "Starting services..."
# Start Tor first
systemctl start tor
sleep 5
# Start SSH
systemctl restart sshd
# Start Phase Console
systemctl start phase-console
# Wait for services to be ready
log "Waiting for services to start..."
sleep 30
}
# Get Tor onion address
get_onion_address() {
log "Retrieving Tor onion address..."
local onion_file="$TOR_DATA_DIR/phase-console/hostname"
local max_attempts=30
local attempt=1
while [[ $attempt -le $max_attempts ]]; do
if [[ -f "$onion_file" ]]; then
local onion_address=$(cat "$onion_file")
log "Phase Console .onion address: $onion_address"
# Save to easily accessible location
echo "$onion_address" > "$PHASE_CONSOLE_DIR/onion-address.txt"
chown "$PHASE_USER:$PHASE_GROUP" "$PHASE_CONSOLE_DIR/onion-address.txt"
return 0
fi
log "Waiting for Tor to generate onion address... (attempt $attempt/$max_attempts)"
sleep 2
((attempt++))
done
warn "Could not retrieve onion address. Check Tor logs: journalctl -u tor"
}
# Create installation summary
create_summary() {
log "Creating installation summary..."
local summary_file="$PHASE_CONSOLE_DIR/installation-summary.txt"
local onion_address="Unknown"
if [[ -f "$PHASE_CONSOLE_DIR/onion-address.txt" ]]; then
onion_address=$(cat "$PHASE_CONSOLE_DIR/onion-address.txt")
fi
cat > "$summary_file" << EOF
Phase Console Installation Summary
==================================
Installation Date: $(date)
Server: $(hostname)
OS: $(cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)
Services:
---------
Phase Console: http://127.0.0.1:$PHASE_CONSOLE_PORT
Tor Hidden Service: http://$onion_address
SSH Port: $SSH_PORT
Directories:
------------
Installation: $PHASE_CONSOLE_DIR
Secrets: $PHASE_CONSOLE_DIR/secrets
Logs: $PHASE_CONSOLE_DIR/logs
Tor Data: $TOR_DATA_DIR/phase-console
Management Commands:
-------------------
Control Phase Console: phase-console {start|stop|restart|status|logs|update|backup}
Manage SSH Keys: phase-ssh-keys {add|list|remove|clear}
Generated Secrets for ShadowBin Marketplace:
--------------------------------------------
Application Secrets:
- app_key (Laravel encryption key)
- session_encryption_key (Session encryption)
- sanctum_secret_key (API authentication)
- jwt_secret_app (JWT signing)
- encryption_master_key (Master encryption)
Database Secrets:
- db_password (MySQL application user)
- db_root_password (MySQL root user)
- redis_app_password (Redis authentication)
Monitoring Secrets:
- telescope_secret (Laravel Telescope)
- health_check_secret (Health endpoints)
- metrics_api_token (Metrics collection)
- log_encryption_key (Log encryption)
Backup & Recovery:
- backup_encryption_key (Backup encryption)
- disaster_recovery_key (Emergency access)
External API Placeholders (UPDATE WITH REAL VALUES):
- coinmarketcap_api_key
- coingecko_api_key
- kraken_api_key
- kraken_api_secret
- s3_access_key_id
- s3_secret_access_key
Service Status:
---------------
$(systemctl is-active docker) - Docker
$(systemctl is-active tor) - Tor
$(systemctl is-active sshd) - SSH
$(systemctl is-active phase-console) - Phase Console
Next Steps:
-----------
1. Add SSH public keys: phase-ssh-keys add <public_key_file>
2. UPDATE PLACEHOLDER API KEYS with real values in $PHASE_CONSOLE_DIR/secrets/
3. Configure ShadowBin Marketplace to connect to: $onion_address
4. Test SSH connection: ssh -p $SSH_PORT phaseuser@$onion_address
5. Access Phase Console: http://127.0.0.1:$PHASE_CONSOLE_PORT (local only)
6. Run post-install validation: phase-console-post-install.sh
Security Notes:
---------------
- Phase Console is only accessible via Tor hidden service
- SSH access requires public key authentication
- All secrets are stored in $PHASE_CONSOLE_DIR/secrets with restricted permissions
- Firewall is configured to allow only necessary ports
- Generated $(ls -1 $PHASE_CONSOLE_DIR/secrets | wc -l) secrets for ShadowBin Marketplace
EOF
chown "$PHASE_USER:$PHASE_GROUP" "$summary_file"
# Display summary
cat "$summary_file"
}
# Main installation function
main() {
log "Starting Phase Console installation on Rocky Linux/CentOS..."
check_root
check_os_version
update_system
install_packages
ensure_tor_user
configure_services
create_phase_user
create_directories
generate_secrets
configure_tor
configure_ssh
configure_firewall
create_docker_compose
create_systemd_service
create_management_scripts
start_services
get_onion_address
create_summary
log "Phase Console installation completed successfully!"
log "Summary saved to: $PHASE_CONSOLE_DIR/installation-summary.txt"
log "Onion address saved to: $PHASE_CONSOLE_DIR/onion-address.txt"
}
# Run main function if script is executed directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
 
Top