Compare commits
No commits in common. "master" and "legacy" have entirely different histories.
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,12 +1,15 @@
|
||||
# Config files
|
||||
config.yaml
|
||||
build-config.yaml
|
||||
deploy-config.yaml
|
||||
config.ign
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
# Images
|
||||
*.iso
|
||||
|
||||
# Downloaded images
|
||||
fedora-coreos-*-hetzner.x86_64.raw.xz
|
||||
|
||||
# Environment
|
||||
.env
|
||||
114
README.md
114
README.md
@ -4,73 +4,85 @@
|
||||
<br>
|
||||
</div>
|
||||
|
||||
Secure AlmaLinux (RHEL) Server setup with LUKS encryption, Tang, TPM and RAID1 for Hetzner Dedicated Servers.
|
||||
Secure Fedora Server setup with LUKS encryption, TPM, and BTRFS RAID1 with focus on Hetzner Infra.
|
||||
|
||||
## Features
|
||||
|
||||
- AlmaLinux Server base
|
||||
- Fedora Server base
|
||||
- Full disk encryption with LUKS
|
||||
- Remote unlock via Tang server
|
||||
- TPM-based boot verification
|
||||
- mdadm RAID1 + XFS (RHEL standard)
|
||||
- SSH key-only access with early boot SSH via dropbear
|
||||
- Best-in-class terminal: zsh + powerlevel10k + evil tmux
|
||||
- BTRFS RAID1 for data redundancy
|
||||
- Dedicated database subvolume with `nodatacow` and `noatime`
|
||||
- Automated deployment to Hetzner
|
||||
|
||||
## Unlock Strategy
|
||||
If you need a dead man's switch to go along with it check out [raven](https://git.dominik-roth.eu/dodox/raven).
|
||||
|
||||
1. **Automatic unlock via Tang/TPM** (default):
|
||||
- Configure TPM2 and/or Tang servers in post-install.sh
|
||||
- System unlocks automatically if conditions are met
|
||||
- No manual intervention required
|
||||
## Security Model
|
||||
|
||||
2. **Manual unlock via SSH** (fallback):
|
||||
- SSH to server on port 22 (dropbear in early boot)
|
||||
- Enter LUKS passphrase when prompted (twice, once per disk)
|
||||
- Used when automatic unlock fails or is not configured
|
||||
### Unlock Methods
|
||||
The system uses multiple methods to unlock the LUKS volumes:
|
||||
1. **Primary Method**: TPM2 + Tang server
|
||||
- TPM2 verifies boot integrity
|
||||
- Tang server provides remote unlock capability
|
||||
- Both must succeed for automatic unlock
|
||||
2. **Fallback Method**: Manual passphrase
|
||||
- Available via SSH before LUKS unlock
|
||||
- Uses dropbear for early SSH access
|
||||
- Can be used for recovery or maintenance
|
||||
|
||||
## Install
|
||||
### TPM Updates
|
||||
After firmware updates (UEFI/BIOS), the TPM bindings need to be updated:
|
||||
(otherwise the system will not be able to boot without recovery phrase)
|
||||
1. Use the provided script: `sudo /root/update-tpm-bindings.sh`
|
||||
2. The script will:
|
||||
- Show current PCR values
|
||||
- Update TPM bindings to match new measurements
|
||||
- Verify all bindings are correct
|
||||
3. Manual passphrase is available in `/root/luks-passphrase.txt` if needed
|
||||
|
||||
Boot your Hetzner server into rescue mode and run:
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
wget -qO- https://git.dominik-roth.eu/dodox/nullpoint/raw/branch/master/get.sh | bash
|
||||
# Install tools
|
||||
curl -fsSL https://raw.githubusercontent.com/hetznercloud/cli/master/install.sh | bash
|
||||
go install github.com/hetznercloud/hcloud-upload-image@latest
|
||||
sudo dnf install -y jq python3-pyyaml libguestfs-tools cloud-image-utils curl
|
||||
|
||||
# Configure Hetzner
|
||||
export HCLOUD_TOKEN="your-token-here"
|
||||
hcloud ssh-key create --name "fedora-server-hetzner" --public-key "$(cat ~/.ssh/id_ed25519.pub)"
|
||||
```
|
||||
|
||||
The installer will:
|
||||
- Detect your SSH key from the current session
|
||||
- Ask for hostname and username
|
||||
- Generate a secure LUKS passphrase (SAVE IT!)
|
||||
- Download and configure everything
|
||||
- Run Hetzner's installimage automatically
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<img src='./icon_cluster.svg' width="150px">
|
||||
<h2>nullpoint cluster</h2>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
Encrypted network and storage pool using [Nebula](https://github.com/slackhq/nebula) mesh VPN and [GlusterFS](https://www.gluster.org/) distributed filesystem.
|
||||
|
||||
## Features
|
||||
|
||||
- **Encrypted mesh network** - All traffic encrypted via Nebula overlay (192.168.100.0/24)
|
||||
- **Distributed storage** - Data replicated across all storage nodes
|
||||
- **Simple joining** - Single preshared secret + lighthouse endpoint
|
||||
- **Flexible nodes** - Full nodes (replicate data) or remote nodes (no storage)
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
wget -qO- https://git.dominik-roth.eu/dodox/nullpoint/raw/branch/master/cluster-setup.sh | sudo bash
|
||||
```
|
||||
1. **Configure Build Settings**
|
||||
```bash
|
||||
cp build-config.yaml.example build-config.yaml
|
||||
vim build-config.yaml # Edit LUKS, storage, and image settings
|
||||
```
|
||||
|
||||
Choose your node type:
|
||||
- **Full node** - Runs GlusterFS server, contributes storage, acts as lighthouse
|
||||
- Use for servers in same datacenter/region with low latency
|
||||
- **Remote node** - GlusterFS client only, no storage contribution
|
||||
- Use for edge locations, different regions, or high-latency connections
|
||||
- Avoids replication delays since writes don't wait for this node
|
||||
2. **Build Base Image** (one-time setup)
|
||||
```bash
|
||||
python3 build.py # Creates and uploads Fedora Server image to Hetzner
|
||||
```
|
||||
|
||||
Storage mounted at `/data/storage/` on all nodes.
|
||||
3. **Configure Deployment Settings**
|
||||
```bash
|
||||
cp deploy-config.yaml.example deploy-config.yaml
|
||||
vim deploy-config.yaml # Edit server type, location, and hostname settings
|
||||
```
|
||||
|
||||
4. **Deploy Server**
|
||||
```bash
|
||||
python3 deploy.py # Creates new server from base image
|
||||
```
|
||||
|
||||
5. **Verify**
|
||||
```bash
|
||||
ssh admin@your-server
|
||||
systemctl status clevis-luks-askpass
|
||||
lsblk
|
||||
btrfs filesystem show # Check RAID1 status
|
||||
clevis-luks-list -d /dev/sda2
|
||||
```
|
||||
43
build-config.yaml.example
Normal file
43
build-config.yaml.example
Normal file
@ -0,0 +1,43 @@
|
||||
# Build Configuration
|
||||
image:
|
||||
name: nullpoint
|
||||
version: 39
|
||||
arch: x86_64
|
||||
hetzner_arch: x86
|
||||
|
||||
# System Configuration
|
||||
system:
|
||||
# LUKS Configuration
|
||||
luks:
|
||||
tang_servers:
|
||||
- url: https://tang1.example.com
|
||||
thumbprint: your-tang1-thumbprint
|
||||
- url: https://tang2.example.com
|
||||
thumbprint: your-tang2-thumbprint
|
||||
|
||||
# TPM Configuration
|
||||
tpm:
|
||||
pcr_bank: sha256 # PCR bank to use (sha1 or sha256)
|
||||
pcr_ids: [0,4,7,8,9] # PCRs to measure
|
||||
# PCR descriptions:
|
||||
# 0: Core System Firmware executable code (BIOS/UEFI) (RECOMMENDED)
|
||||
# 1: Core System Firmware data (BIOS/UEFI settings)
|
||||
# 2: Extended or pluggable executable code
|
||||
# 3: Extended or pluggable firmware data
|
||||
# 4: Boot Manager Code (bootloader) (RECOMMENDED)
|
||||
# 5: Boot Manager Configuration and Data
|
||||
# 6: Platform-specific code
|
||||
# 7: Platform-specific configuration (RECOMMENDED)
|
||||
# 8: UEFI driver and application code (RECOMMENDED)
|
||||
# 9: UEFI driver and application configuration (RECOMMENDED)
|
||||
# 10: UEFI Handoff Tables
|
||||
# 11: UEFI Boot Services Code
|
||||
# 12: UEFI Boot Services Data
|
||||
# 13: UEFI Runtime Services Code
|
||||
# 14: UEFI Runtime Services Data
|
||||
# 15: UEFI Secure Boot State
|
||||
|
||||
# System Settings
|
||||
timezone: UTC
|
||||
keyboard: us
|
||||
language: en_US.UTF-8
|
||||
547
build.py
Normal file
547
build.py
Normal file
@ -0,0 +1,547 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
import subprocess
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
|
||||
def load_config(config_file):
|
||||
"""Load and parse YAML config file."""
|
||||
with open(config_file, 'r') as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
def check_prerequisites():
|
||||
"""Check if required tools are installed."""
|
||||
required_tools = ['hcloud', 'hcloud-upload-image', 'jq', 'virt-customize', 'cloud-localds']
|
||||
for tool in required_tools:
|
||||
if not shutil.which(tool):
|
||||
print(f"Error: {tool} not found. Please install it first.")
|
||||
sys.exit(1)
|
||||
|
||||
def check_hetzner_token():
|
||||
"""Check if HCLOUD_TOKEN is set."""
|
||||
if not os.environ.get('HCLOUD_TOKEN'):
|
||||
print("Error: HCLOUD_TOKEN environment variable not set")
|
||||
sys.exit(1)
|
||||
|
||||
def generate_cloud_init(config):
|
||||
"""Generate cloud-init configuration."""
|
||||
system = config['system']
|
||||
cloud_init = system['cloud_init']
|
||||
tpm_config = system['luks']['tpm']
|
||||
|
||||
# Generate TPM config
|
||||
tpm_json = {
|
||||
'pcr_bank': tpm_config['pcr_bank'],
|
||||
'pcr_ids': ','.join(map(str, tpm_config['pcr_ids']))
|
||||
}
|
||||
|
||||
# Base cloud-init config
|
||||
cloud_config = {
|
||||
'timezone': cloud_init['timezone'],
|
||||
'users': cloud_init['users'],
|
||||
'package_update': True,
|
||||
'package_upgrade': True,
|
||||
'packages': cloud_init['packages'] + ['mdadm'], # Add mdadm package
|
||||
'write_files': [
|
||||
{
|
||||
'path': '/etc/clevis/tang.conf',
|
||||
'content': f"URL={system['luks']['tang_url']}\nThumbprint={system['luks']['tang_thumbprint']}\n",
|
||||
'permissions': '0644'
|
||||
},
|
||||
{
|
||||
'path': '/etc/clevis/tpm2.conf',
|
||||
'content': json.dumps(tpm_json),
|
||||
'permissions': '0644'
|
||||
},
|
||||
{
|
||||
'path': '/root/update-tpm-bindings.sh',
|
||||
'content': '''#!/bin/bash
|
||||
# Script to update TPM bindings after firmware updates
|
||||
# Usage: sudo ./update-tpm-bindings.sh
|
||||
|
||||
set -e
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if TPM is available
|
||||
if ! tpm2_getcap properties-fixed > /dev/null; then
|
||||
echo "Error: TPM not available"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if at least one Tang server is accessible
|
||||
TANG_AVAILABLE=0
|
||||
for tang_url in $(grep -h "url" /etc/clevis/sss.conf | grep -o '"url": "[^"]*"' | cut -d'"' -f4); do
|
||||
if curl -s -f "$tang_url/adv" > /dev/null; then
|
||||
echo "Tang server $tang_url is accessible"
|
||||
TANG_AVAILABLE=1
|
||||
break
|
||||
else
|
||||
echo "Tang server $tang_url is not accessible"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $TANG_AVAILABLE -eq 0 ]; then
|
||||
echo "Error: No Tang servers are accessible"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get current PCR values
|
||||
echo "Current PCR values:"
|
||||
PCR_IDS=$(grep -h "pcr_ids" /etc/clevis/sss.conf | grep -o '"pcr_ids": "[^"]*"' | cut -d'"' -f4)
|
||||
tpm2_pcrread sha256:$(echo $PCR_IDS | tr ',' ' ')
|
||||
|
||||
# Ask for confirmation
|
||||
read -p "Have you updated firmware? Continue with TPM binding update? [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Aborted"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get LUKS passphrase
|
||||
if [ -f /root/luks-passphrase.txt ]; then
|
||||
LUKS_PASSPHRASE=$(cat /root/luks-passphrase.txt)
|
||||
else
|
||||
echo "LUKS passphrase file not found. Please enter your LUKS passphrase:"
|
||||
read -s LUKS_PASSPHRASE
|
||||
echo
|
||||
fi
|
||||
|
||||
# Update TPM bindings
|
||||
echo "Updating TPM bindings..."
|
||||
for dev in /dev/sda2 /dev/sdb2; do
|
||||
echo "Processing $dev..."
|
||||
|
||||
# Unbind old SSS binding
|
||||
SLOT=$(clevis luks list -d "$dev" | grep -n "sss" | cut -d: -f1)
|
||||
if [ -n "$SLOT" ]; then
|
||||
echo "Removing old SSS binding from slot $SLOT"
|
||||
clevis luks unbind -d "$dev" -s "$SLOT" || true
|
||||
fi
|
||||
|
||||
# Create new binding with SSS using the same config
|
||||
echo "Adding new SSS binding"
|
||||
echo -n "$LUKS_PASSPHRASE" | clevis luks bind -d "$dev" sss -c /etc/clevis/sss.conf -k-
|
||||
|
||||
# Verify binding was successful
|
||||
if ! clevis luks list -d "$dev" | grep -q "sss"; then
|
||||
echo "Error: Failed to create SSS binding for $dev"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "TPM bindings updated successfully!"
|
||||
echo "Please reboot to verify the changes."
|
||||
''',
|
||||
'permissions': '0700'
|
||||
}
|
||||
],
|
||||
# Use cloud-init's native disk setup for partitioning only
|
||||
'disk_setup': {
|
||||
'/dev/sda': {
|
||||
'table_type': 'gpt',
|
||||
'layout': [
|
||||
[1024, 'boot'], # 1GB for boot
|
||||
['auto', 'data'] # Rest for LUKS1
|
||||
],
|
||||
'overwrite': True
|
||||
},
|
||||
'/dev/sdb': {
|
||||
'table_type': 'gpt',
|
||||
'layout': [
|
||||
[1024, 'boot'], # 1GB for boot mirror
|
||||
['auto', 'data'] # Rest for LUKS2
|
||||
],
|
||||
'overwrite': True
|
||||
}
|
||||
},
|
||||
'runcmd': [
|
||||
# Check if passphrase is set
|
||||
'[ -n "$LUKS_PASSPHRASE" ] || { echo "Error: LUKS passphrase not found"; exit 1; }',
|
||||
# Verify Tang server is accessible
|
||||
f'curl -s -f {system["luks"]["tang_url"]}/adv > /dev/null || {{ echo "Tang server not accessible"; exit 1; }}',
|
||||
# Verify TPM is available
|
||||
'tpm2_getcap properties-fixed > /dev/null || { echo "TPM not available"; exit 1; }',
|
||||
# Create boot RAID1
|
||||
'mdadm --create --verbose /dev/md0 --level=1 --raid-devices=2 /dev/sda1 /dev/sdb1 --metadata=1.2',
|
||||
# Create filesystem on RAID array
|
||||
'mkfs.ext4 -L boot /dev/md0',
|
||||
# Mount boot RAID
|
||||
'mkdir -p /mnt/boot',
|
||||
'mount /dev/md0 /mnt/boot',
|
||||
# Setup LUKS on data partitions
|
||||
'echo -n "${LUKS_PASSPHRASE}" | tr -d "\n" | cryptsetup luksFormat /dev/sda2 --type luks2 --key-file -',
|
||||
'echo -n "${LUKS_PASSPHRASE}" | tr -d "\n" | cryptsetup luksFormat /dev/sdb2 --type luks2 --key-file -',
|
||||
# Setup Clevis with error handling
|
||||
'clevis luks bind -d /dev/sda2 tpm2 -c /etc/clevis/tpm2.conf || { echo "TPM bind failed"; exit 1; }',
|
||||
'clevis luks bind -d /dev/sda2 tang -c /etc/clevis/tang.conf || { echo "Tang bind failed"; exit 1; }',
|
||||
'clevis luks bind -d /dev/sdb2 tpm2 -c /etc/clevis/tpm2.conf || { echo "TPM bind failed"; exit 1; }',
|
||||
'clevis luks bind -d /dev/sdb2 tang -c /etc/clevis/tang.conf || { echo "Tang bind failed"; exit 1; }',
|
||||
# Open LUKS volumes
|
||||
'echo -n "${LUKS_PASSPHRASE}" | tr -d "\n" | cryptsetup luksOpen /dev/sda2 root_a --key-file -',
|
||||
'echo -n "${LUKS_PASSPHRASE}" | tr -d "\n" | cryptsetup luksOpen /dev/sdb2 root_b --key-file -',
|
||||
# Create BTRFS on decrypted devices
|
||||
'mkfs.btrfs -f -d raid1 -m raid1 /dev/mapper/root_a /dev/mapper/root_b',
|
||||
'mount /dev/mapper/root_a /mnt',
|
||||
# Create subvolumes
|
||||
'btrfs subvolume create /mnt/@home',
|
||||
'btrfs subvolume create /mnt/@db',
|
||||
'chattr +C /mnt/@db',
|
||||
# Setup fstab
|
||||
'echo "/dev/md0 /boot ext4 defaults 0 0" >> /etc/fstab',
|
||||
'echo "/dev/mapper/root_a / btrfs compress=zstd 0 0" >> /etc/fstab',
|
||||
'echo "/dev/mapper/root_a /home btrfs subvol=@home,compress=zstd 0 0" >> /etc/fstab',
|
||||
'echo "/dev/mapper/root_a /db btrfs subvol=@db,nodatacow,noatime,compress=zstd 0 0" >> /etc/fstab',
|
||||
# Save RAID configuration
|
||||
'mdadm --detail --scan > /etc/mdadm.conf',
|
||||
# Enable services
|
||||
'systemctl enable clevis-luks-askpass.service',
|
||||
'systemctl enable clevis-luks-askpass.path'
|
||||
]
|
||||
}
|
||||
|
||||
return yaml.dump(cloud_config)
|
||||
|
||||
def download_fedora_image(config):
|
||||
"""Download Fedora Server netinstall image."""
|
||||
image = config['image']
|
||||
url = f"https://download.fedoraproject.org/pub/fedora/linux/releases/{image['version']}/Server/x86_64/iso/Fedora-Server-netinst-x86_64-{image['version']}-1.6.iso"
|
||||
|
||||
cmd = ['curl', '-L', '-o', 'fedora-server.iso', url]
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
def generate_kickstart(config):
|
||||
"""Generate kickstart configuration for automated installation."""
|
||||
system = config['system']
|
||||
|
||||
# TPM update script content
|
||||
tpm_update_script = '''#!/bin/bash
|
||||
# Script to update TPM bindings after firmware updates
|
||||
# Usage: sudo ./update-tpm-bindings.sh
|
||||
|
||||
set -e
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if TPM is available
|
||||
if ! tpm2_getcap properties-fixed > /dev/null; then
|
||||
echo "Error: TPM not available"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if at least one Tang server is accessible
|
||||
TANG_AVAILABLE=0
|
||||
for tang_url in $(grep -h "url" /etc/clevis/sss.conf | grep -o '"url": "[^"]*"' | cut -d'"' -f4); do
|
||||
if curl -s -f "$tang_url/adv" > /dev/null; then
|
||||
echo "Tang server $tang_url is accessible"
|
||||
TANG_AVAILABLE=1
|
||||
break
|
||||
else
|
||||
echo "Tang server $tang_url is not accessible"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $TANG_AVAILABLE -eq 0 ]; then
|
||||
echo "Error: No Tang servers are accessible"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get current PCR values
|
||||
echo "Current PCR values:"
|
||||
PCR_IDS=$(grep -h "pcr_ids" /etc/clevis/sss.conf | grep -o '"pcr_ids": "[^"]*"' | cut -d'"' -f4)
|
||||
tpm2_pcrread sha256:$(echo $PCR_IDS | tr ',' ' ')
|
||||
|
||||
# Ask for confirmation
|
||||
read -p "Have you updated firmware? Continue with TPM binding update? [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Aborted"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get LUKS passphrase
|
||||
if [ -f /root/luks-passphrase.txt ]; then
|
||||
LUKS_PASSPHRASE=$(cat /root/luks-passphrase.txt)
|
||||
else
|
||||
echo "LUKS passphrase file not found. Please enter your LUKS passphrase:"
|
||||
read -s LUKS_PASSPHRASE
|
||||
echo
|
||||
fi
|
||||
|
||||
# Update TPM bindings
|
||||
echo "Updating TPM bindings..."
|
||||
for dev in /dev/sda2 /dev/sdb2; do
|
||||
echo "Processing $dev..."
|
||||
|
||||
# Unbind old SSS binding
|
||||
SLOT=$(clevis luks list -d "$dev" | grep -n "sss" | cut -d: -f1)
|
||||
if [ -n "$SLOT" ]; then
|
||||
echo "Removing old SSS binding from slot $SLOT"
|
||||
clevis luks unbind -d "$dev" -s "$SLOT" || true
|
||||
fi
|
||||
|
||||
# Create new binding with SSS using the same config
|
||||
echo "Adding new SSS binding"
|
||||
echo -n "$LUKS_PASSPHRASE" | clevis luks bind -d "$dev" sss -c /etc/clevis/sss.conf -k-
|
||||
|
||||
# Verify binding was successful
|
||||
if ! clevis luks list -d "$dev" | grep -q "sss"; then
|
||||
echo "Error: Failed to create SSS binding for $dev"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "TPM bindings updated successfully!"
|
||||
echo "Please reboot to verify the changes."
|
||||
'''
|
||||
|
||||
# Create SSS config for TPM + any Tang server
|
||||
tang_servers = []
|
||||
for server in system['luks']['tang_servers']:
|
||||
tang_servers.append({"url": server['url']})
|
||||
|
||||
# Convert Tang servers to JSON for SSS config
|
||||
tang_servers_json = json.dumps(tang_servers)
|
||||
|
||||
# Create SSS policy: Require TPM AND at least one Tang server
|
||||
sss_config = {
|
||||
"t": 2, # Threshold: Both pins must succeed
|
||||
"pins": {
|
||||
"tpm2": {
|
||||
"pcr_bank": system['luks']['tpm']['pcr_bank'],
|
||||
"pcr_ids": ','.join(map(str, system['luks']['tpm']['pcr_ids']))
|
||||
},
|
||||
"tang": {"t": 1, "tang": tang_servers} # Only one Tang server needed from the list
|
||||
}
|
||||
}
|
||||
|
||||
# Convert config to JSON string
|
||||
sss_config_json = json.dumps(sss_config)
|
||||
|
||||
kickstart = f"""# Kickstart configuration for Fedora Server
|
||||
# Generated for Nullpoint
|
||||
|
||||
# System language
|
||||
lang en_US.UTF-8
|
||||
|
||||
# Keyboard layouts
|
||||
keyboard --vckeymap=us --xlayouts='us'
|
||||
|
||||
# Network information
|
||||
network --bootproto=dhcp --device=link --activate
|
||||
|
||||
# Root password
|
||||
rootpw --lock
|
||||
|
||||
# System timezone
|
||||
timezone {system['timezone']} --utc
|
||||
|
||||
# Installation type
|
||||
text
|
||||
|
||||
# Wipe all disk
|
||||
zerombr
|
||||
clearpart --all --initlabel
|
||||
|
||||
# Disk partitioning information
|
||||
# Boot partitions (5GB each)
|
||||
part /boot --fstype=btrfs --size=5120 --ondisk=sda
|
||||
part /boot --fstype=btrfs --size=5120 --ondisk=sdb
|
||||
|
||||
# Main data partitions with LUKS
|
||||
part / --fstype=btrfs --encrypted --cipher=aes-xts-plain64 --luks-version=luks2 --grow --ondisk=sda
|
||||
part / --fstype=btrfs --encrypted --cipher=aes-xts-plain64 --luks-version=luks2 --grow --ondisk=sdb
|
||||
|
||||
# Package source
|
||||
url --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-$releasever&arch=$basearch
|
||||
repo --name=fedora
|
||||
repo --name=updates
|
||||
|
||||
# Make sure initial-setup runs
|
||||
firstboot --reconfig
|
||||
|
||||
# Package selection
|
||||
%packages
|
||||
@^server-product
|
||||
@system-tools
|
||||
btrfs-progs
|
||||
clevis
|
||||
clevis-luks
|
||||
clevis-tang
|
||||
clevis-tpm2
|
||||
tpm2-tools
|
||||
tpm2-tss
|
||||
cryptsetup
|
||||
systemd
|
||||
curl
|
||||
shim-x64
|
||||
grub2-efi-x64
|
||||
dropbear
|
||||
%end
|
||||
|
||||
# Pre-installation script
|
||||
%pre
|
||||
# Create TPM and Tang config files
|
||||
mkdir -p /etc/clevis
|
||||
|
||||
# Save SSS config for TPM + Tang servers
|
||||
cat > /etc/clevis/sss.conf << EOF
|
||||
{sss_config_json}
|
||||
EOF
|
||||
%end
|
||||
|
||||
# Post-installation script
|
||||
%post
|
||||
# https://unix.stackexchange.com/a/351755 for handling TTY in anaconda
|
||||
printf "\n=== Nullpoint Installation Progress ===\r\n" > /dev/tty1
|
||||
printf "Press Alt+F3 to view detailed installation logs\r\n" > /dev/tty1
|
||||
printf "Press Alt+F1 to return to main installation screen\r\n" > /dev/tty1
|
||||
printf "Current step: Setting up TPM and Tang...\r\n\n" > /dev/tty1
|
||||
{{
|
||||
# Get the LUKS passphrase that was used during installation
|
||||
LUKS_PASSPHRASE=$(cat /tmp/luks-passphrase.txt)
|
||||
echo "$LUKS_PASSPHRASE" > /root/luks-passphrase.txt
|
||||
chmod 600 /root/luks-passphrase.txt
|
||||
|
||||
# Setup Clevis with SSS policy (TPM + at least one Tang server)
|
||||
printf "Configuring TPM and Tang with SSS policy...\r\n" > /dev/tty1
|
||||
echo "Using SSS policy: TPM verification AND (at least one Tang server)" > /dev/tty1
|
||||
echo -n "$LUKS_PASSPHRASE" | clevis luks bind -d /dev/sda2 sss -c /etc/clevis/sss.conf -k-
|
||||
echo -n "$LUKS_PASSPHRASE" | clevis luks bind -d /dev/sdb2 sss -c /etc/clevis/sss.conf -k-
|
||||
|
||||
# Get BTRFS UUID (same for all devices in the filesystem)
|
||||
BTRFS_UUID=$(blkid -s UUID -o value /dev/mapper/luks-$(blkid -s UUID -o value /dev/sda2))
|
||||
|
||||
# Create subvolumes
|
||||
printf "Creating BTRFS subvolumes...\r\n" > /dev/tty1
|
||||
# Mount both devices for RAID1
|
||||
mount -t btrfs -o raid1 UUID=$BTRFS_UUID /mnt
|
||||
btrfs subvolume create /mnt/@root
|
||||
btrfs subvolume create /mnt/@home
|
||||
btrfs subvolume create /mnt/@db
|
||||
chattr +C /mnt/@db
|
||||
|
||||
# Setup fstab
|
||||
printf "Configuring system mount points...\r\n" > /dev/tty1
|
||||
cat > /etc/fstab << EOF
|
||||
UUID=$BTRFS_UUID / btrfs subvol=@root,compress=zstd,raid1 0 0
|
||||
UUID=$BTRFS_UUID /home btrfs subvol=@home,compress=zstd,raid1 0 0
|
||||
UUID=$BTRFS_UUID /db btrfs subvol=@db,nodatacow,noatime,compress=zstd,raid1 0 0
|
||||
EOF
|
||||
|
||||
# Configure dropbear for early SSH access
|
||||
printf "Configuring early SSH access...\r\n" > /dev/tty1
|
||||
mkdir -p /etc/dropbear
|
||||
echo "{config['admin_ssh_key']}" > /etc/dropbear/authorized_keys
|
||||
chmod 600 /etc/dropbear/authorized_keys
|
||||
|
||||
# Enable dropbear for early SSH
|
||||
systemctl enable dropbear
|
||||
systemctl enable dropbear.socket
|
||||
|
||||
# Enable services
|
||||
printf "Enabling system services...\r\n" > /dev/tty1
|
||||
systemctl enable clevis-luks-askpass.service
|
||||
systemctl enable clevis-luks-askpass.path
|
||||
|
||||
# Create TPM update script
|
||||
printf "Creating TPM update script...\r\n" > /dev/tty1
|
||||
cat > /root/update-tpm-bindings.sh << 'EOF'
|
||||
{tpm_update_script}
|
||||
EOF
|
||||
|
||||
chmod +x /root/update-tpm-bindings.sh
|
||||
printf "\nInstallation complete! The system will reboot shortly.\r\n" > /dev/tty1
|
||||
printf "IMPORTANT: LUKS passphrase has been saved to /root/luks-passphrase.txt\r\n" > /dev/tty1
|
||||
}} 2>&1 | tee -a /root/postinstall.log > /dev/tty3
|
||||
%end
|
||||
"""
|
||||
return kickstart
|
||||
|
||||
def customize_image(config):
|
||||
"""Customize the Fedora Server image."""
|
||||
# Generate kickstart config
|
||||
kickstart = generate_kickstart(config)
|
||||
|
||||
# Create kickstart file
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.ks') as f:
|
||||
f.write(kickstart)
|
||||
f.flush()
|
||||
|
||||
# Create custom ISO with kickstart
|
||||
cmd = [
|
||||
'mkisofs',
|
||||
'-o', 'fedora-server-custom.iso',
|
||||
'-b', 'isolinux/isolinux.bin',
|
||||
'-c', 'isolinux/boot.cat',
|
||||
'-boot-info-table',
|
||||
'-no-emul-boot',
|
||||
'-boot-load-size', '4',
|
||||
'-R',
|
||||
'-J',
|
||||
'-v',
|
||||
'-T',
|
||||
'-V', 'Fedora-S-custom',
|
||||
'-A', 'Fedora-S-custom',
|
||||
'fedora-server.iso',
|
||||
f.name
|
||||
]
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
def create_snapshot(config):
|
||||
"""Create new snapshot from image."""
|
||||
image = config['image']
|
||||
cmd = [
|
||||
'hcloud-upload-image', 'upload',
|
||||
'--architecture', image['hetzner_arch'],
|
||||
'--compression', 'xz',
|
||||
'--image-path', 'fedora-server-custom.iso',
|
||||
'--name', image['name'],
|
||||
'--labels', f'os=fedora-server,version={image["version"]}',
|
||||
'--description', f'Fedora Server {image["version"]} for Nullpoint'
|
||||
]
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
# Load config
|
||||
if not os.path.exists('build-config.yaml'):
|
||||
print("Error: build-config.yaml not found")
|
||||
sys.exit(1)
|
||||
|
||||
config = load_config('build-config.yaml')
|
||||
|
||||
# Check prerequisites
|
||||
check_prerequisites()
|
||||
check_hetzner_token()
|
||||
|
||||
# Download Fedora Server image
|
||||
print("Downloading Fedora Server image...")
|
||||
download_fedora_image(config)
|
||||
|
||||
# Customize image
|
||||
print("Customizing image...")
|
||||
customize_image(config)
|
||||
|
||||
# Create snapshot
|
||||
print("Creating snapshot...")
|
||||
create_snapshot(config)
|
||||
|
||||
print("Image build complete!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
564
cluster-setup.sh
564
cluster-setup.sh
@ -1,564 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
NEBULA_NETWORK="192.168.100.0/24"
|
||||
NEBULA_PORT=4242
|
||||
NEBULA_CONFIG="/etc/nebula"
|
||||
GLUSTER_BRICK_PATH="/gluster/cluster"
|
||||
GLUSTER_MOUNT_PATH="/data/storage"
|
||||
GLUSTER_VOLUME="cluster-volume"
|
||||
NEBULA_VERSION="v1.9.6"
|
||||
|
||||
CLUSTER_LOGO=$(cat << "EOF"
|
||||
==
|
||||
.@@@@@
|
||||
=@@@@@%
|
||||
+@@@@@
|
||||
:@@@@@
|
||||
*@@@@: -*#+:
|
||||
*@@@- %@@@@ .%@@@@@@@@@@@@*
|
||||
@@@@@ %@@@@ *@@@@@@@@@@@@@@@@
|
||||
+@@@@* #@@@@. %@@@@@@@.
|
||||
@@@@@@ -@@@@%:@@@@@@:
|
||||
*@@@@@@. #@@@@@@@@@:
|
||||
.@@@@@@@@%=.#@@@@@@@@@@@@@#.
|
||||
*@@@@@@@@@@@@@@@@@@@@@@@@@@+
|
||||
=@@@@@@@@@@@@@@%=#@@@@@@@@%
|
||||
:@@@@@@@@@+ -@@@@@@+
|
||||
*@@@@@-%@@@@: .@@@@@#
|
||||
=@@@@@@@ .@@@@# #@@@@+
|
||||
%@@@@@@@@@@@@@@@@. @@@@@ @@@@@
|
||||
#@@@@@@@@@@@@@= @@@@@ =@@@%
|
||||
:#@@@@- .@@@@#
|
||||
#@@@@-
|
||||
#@@@@%
|
||||
+@@@@@*
|
||||
@@@@@-
|
||||
.#%.
|
||||
|
||||
[nullpoint cluster]
|
||||
EOF
|
||||
)
|
||||
|
||||
echo -e "${GREEN}${CLUSTER_LOGO}${NC}\n"
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo -e "${RED}Please run as root${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install base packages
|
||||
echo -e "${YELLOW}[+] Installing base packages...${NC}"
|
||||
dnf install -y curl tar || exit 1
|
||||
|
||||
# Download and install Nebula
|
||||
echo -e "${YELLOW}[+] Downloading Nebula ${NEBULA_VERSION}...${NC}"
|
||||
cd /tmp
|
||||
curl -LO "https://github.com/slackhq/nebula/releases/download/${NEBULA_VERSION}/nebula-linux-amd64.tar.gz"
|
||||
tar -zxf nebula-linux-amd64.tar.gz
|
||||
mv nebula nebula-cert /usr/local/bin/
|
||||
chmod +x /usr/local/bin/nebula /usr/local/bin/nebula-cert
|
||||
|
||||
# Create directories
|
||||
echo -e "${YELLOW}[+] Creating directories...${NC}"
|
||||
mkdir -p "$GLUSTER_MOUNT_PATH"
|
||||
mkdir -p /data
|
||||
mkdir -p "$NEBULA_CONFIG"
|
||||
|
||||
# Function to generate Nebula CA and certificates
|
||||
generate_nebula_ca() {
|
||||
echo -e "${YELLOW}[+] Generating Nebula CA...${NC}"
|
||||
cd "$NEBULA_CONFIG"
|
||||
/usr/local/bin/nebula-cert ca -name "Nullpoint Cluster CA"
|
||||
chmod 600 ca.key
|
||||
}
|
||||
|
||||
# Function to create host certificate
|
||||
create_host_cert() {
|
||||
local hostname="$1"
|
||||
local ip="$2"
|
||||
local groups="$3"
|
||||
|
||||
cd "$NEBULA_CONFIG"
|
||||
/usr/local/bin/nebula-cert sign -name "$hostname" -ip "$ip" -groups "$groups"
|
||||
chmod 600 "${hostname}.key"
|
||||
}
|
||||
|
||||
# Function to get next available IP
|
||||
get_next_ip() {
|
||||
local base_ip="192.168.100"
|
||||
local next_num=10
|
||||
|
||||
if [ -f "$NEBULA_CONFIG/cluster-registry.txt" ]; then
|
||||
# Find highest IP in use
|
||||
existing_ips=$(grep -oE "192\.168\.100\.[0-9]+" "$NEBULA_CONFIG/cluster-registry.txt" | cut -d. -f4 | sort -n | tail -1)
|
||||
if [ ! -z "$existing_ips" ]; then
|
||||
next_num=$((existing_ips + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "${base_ip}.${next_num}"
|
||||
}
|
||||
|
||||
# Function to setup firewall rules
|
||||
setup_firewall() {
|
||||
echo -e "${YELLOW}[+] Configuring firewall...${NC}"
|
||||
|
||||
# Nebula
|
||||
firewall-cmd --permanent --add-port=${NEBULA_PORT}/udp
|
||||
|
||||
# GlusterFS ports
|
||||
firewall-cmd --permanent --add-service=glusterfs
|
||||
firewall-cmd --permanent --add-port=24007-24008/tcp # GlusterFS Daemon
|
||||
firewall-cmd --permanent --add-port=49152-49200/tcp # Brick ports
|
||||
|
||||
# Allow traffic from Nebula network
|
||||
firewall-cmd --permanent --zone=trusted --add-source=${NEBULA_NETWORK}
|
||||
|
||||
firewall-cmd --reload
|
||||
}
|
||||
|
||||
# Create Nebula systemd service
|
||||
create_nebula_service() {
|
||||
cat > /etc/systemd/system/nebula.service <<EOF
|
||||
[Unit]
|
||||
Description=Nebula overlay networking tool
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Group=root
|
||||
ExecStart=/usr/local/bin/nebula -config ${NEBULA_CONFIG}/config.yaml
|
||||
ExecReload=/bin/kill -HUP \$MAINPID
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
}
|
||||
|
||||
# Create new cluster
|
||||
create_cluster() {
|
||||
echo -e "${GREEN}[*] Creating new cluster...${NC}\n"
|
||||
|
||||
local hostname=$(hostname)
|
||||
local node_ip="192.168.100.1"
|
||||
|
||||
# First cluster node must be full node
|
||||
echo -e "${YELLOW}First cluster node must be a full node (storage provider)${NC}"
|
||||
|
||||
# Install GlusterFS server packages
|
||||
echo -e "${YELLOW}[+] Installing GlusterFS server packages...${NC}"
|
||||
dnf install -y glusterfs-server || exit 1
|
||||
systemctl enable glusterd
|
||||
systemctl start glusterd
|
||||
|
||||
# Create brick directory
|
||||
mkdir -p "$GLUSTER_BRICK_PATH"
|
||||
|
||||
# Ask for lighthouse endpoints
|
||||
echo -e "${YELLOW}Enter lighthouse endpoints (DNS names or IPs).${NC}"
|
||||
echo -e "${YELLOW}Recommended: Use a DNS name with redundant backing for HA.${NC}"
|
||||
echo -e "${YELLOW}You can enter multiple endpoints separated by commas.${NC}"
|
||||
read -p "Lighthouse endpoints (e.g., cluster.example.com or 1.2.3.4,5.6.7.8): " lighthouse_endpoints
|
||||
if [ -z "$lighthouse_endpoints" ]; then
|
||||
echo -e "${RED}At least one lighthouse endpoint required!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
am_lighthouse="true"
|
||||
|
||||
# Generate Nebula CA
|
||||
generate_nebula_ca
|
||||
|
||||
# Create certificate for this node
|
||||
create_host_cert "$hostname" "${node_ip}/24" "cluster"
|
||||
|
||||
# Create Nebula config
|
||||
cat > "${NEBULA_CONFIG}/config.yaml" <<EOF
|
||||
pki:
|
||||
ca: ${NEBULA_CONFIG}/ca.crt
|
||||
cert: ${NEBULA_CONFIG}/${hostname}.crt
|
||||
key: ${NEBULA_CONFIG}/${hostname}.key
|
||||
|
||||
lighthouse:
|
||||
am_lighthouse: ${am_lighthouse}
|
||||
serve_dns: false
|
||||
interval: 60
|
||||
hosts:$(echo "$lighthouse_endpoints" | tr ',' '\n' | while read endpoint; do echo "
|
||||
- \"${endpoint}:${NEBULA_PORT}\""; done)
|
||||
|
||||
listen:
|
||||
host: 0.0.0.0
|
||||
port: ${NEBULA_PORT}
|
||||
|
||||
punchy:
|
||||
punch: true
|
||||
respond: true
|
||||
|
||||
tun:
|
||||
disabled: false
|
||||
dev: nebula1
|
||||
drop_local_broadcast: false
|
||||
drop_multicast: false
|
||||
tx_queue: 500
|
||||
mtu: 1300
|
||||
|
||||
logging:
|
||||
level: info
|
||||
format: text
|
||||
|
||||
firewall:
|
||||
conntrack:
|
||||
tcp_timeout: 12m
|
||||
udp_timeout: 3m
|
||||
default_timeout: 10m
|
||||
max_connections: 100000
|
||||
|
||||
outbound:
|
||||
- port: any
|
||||
proto: any
|
||||
host: any
|
||||
|
||||
inbound:
|
||||
- port: any
|
||||
proto: icmp
|
||||
host: any
|
||||
- port: any
|
||||
proto: any
|
||||
host: any
|
||||
EOF
|
||||
|
||||
# Start Nebula as systemd service
|
||||
create_nebula_service
|
||||
systemctl enable nebula
|
||||
systemctl start nebula
|
||||
|
||||
# Setup firewall
|
||||
setup_firewall
|
||||
|
||||
# Create cluster registry
|
||||
echo "${node_ip} ${hostname} full $(date)" > "${NEBULA_CONFIG}/cluster-registry.txt"
|
||||
|
||||
# Create GlusterFS volume
|
||||
echo -e "${YELLOW}[+] Creating GlusterFS volume...${NC}"
|
||||
mkdir -p "${GLUSTER_BRICK_PATH}/brick1"
|
||||
gluster volume create ${GLUSTER_VOLUME} $(hostname):${GLUSTER_BRICK_PATH}/brick1 force 2>/dev/null || true
|
||||
gluster volume start ${GLUSTER_VOLUME} 2>/dev/null || true
|
||||
|
||||
# Mount volume
|
||||
mount -t glusterfs localhost:/${GLUSTER_VOLUME} ${GLUSTER_MOUNT_PATH}
|
||||
grep -q "${GLUSTER_VOLUME}" /etc/fstab || echo "localhost:/${GLUSTER_VOLUME} ${GLUSTER_MOUNT_PATH} glusterfs defaults,_netdev 0 0" >> /etc/fstab
|
||||
|
||||
# Package CA certificate for sharing
|
||||
ca_cert_b64=$(base64 -w0 "${NEBULA_CONFIG}/ca.crt")
|
||||
|
||||
echo -e "\n${GREEN}════════════════════════════════════════${NC}"
|
||||
echo -e "${GREEN}Cluster created successfully!${NC}"
|
||||
echo -e "${GREEN}════════════════════════════════════════${NC}\n"
|
||||
echo -e "Share this cluster secret with joining nodes:\n"
|
||||
echo -e "${GREEN}${lighthouse_endpoints}:${NEBULA_PORT}:${ca_cert_b64}${NC}\n"
|
||||
echo -e "${YELLOW}Status:${NC}"
|
||||
echo " - Nebula IP: ${node_ip}"
|
||||
echo " - Lighthouse endpoints: ${lighthouse_endpoints}:${NEBULA_PORT}"
|
||||
echo " - This node is lighthouse: ${am_lighthouse}"
|
||||
echo " - GlusterFS volume: ${GLUSTER_VOLUME}"
|
||||
echo " - Mount point: ${GLUSTER_MOUNT_PATH}"
|
||||
}
|
||||
|
||||
# Join existing cluster
|
||||
join_cluster() {
|
||||
echo -e "${GREEN}[*] Joining existing cluster...${NC}\n"
|
||||
|
||||
local hostname=$(hostname)
|
||||
local my_ip=$(get_next_ip)
|
||||
|
||||
# Get cluster details
|
||||
read -p "Enter cluster secret (lighthouse_endpoints:port:ca_cert_base64): " cluster_secret
|
||||
|
||||
if [ -z "$cluster_secret" ]; then
|
||||
echo -e "${RED}Cluster secret required!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Parse secret
|
||||
lighthouse_endpoints=$(echo "$cluster_secret" | cut -d: -f1)
|
||||
nebula_port=$(echo "$cluster_secret" | cut -d: -f2)
|
||||
ca_cert_b64=$(echo "$cluster_secret" | cut -d: -f3-)
|
||||
|
||||
# Ask about node type
|
||||
echo -e "${YELLOW}Select node type:${NC}"
|
||||
echo " 1) Full node (contributes storage, lighthouse, read/write)"
|
||||
echo " 2) Remote node (client only, no storage contribution)"
|
||||
echo -e "${YELLOW}Note: Use remote nodes for locations with high latency to the cluster${NC}"
|
||||
read -p "Enter choice [1-2]: " node_type
|
||||
|
||||
if [ "$node_type" = "2" ]; then
|
||||
is_remote="true"
|
||||
am_lighthouse="false"
|
||||
echo -e "${YELLOW}Configuring as remote node (GlusterFS client only)${NC}"
|
||||
# Install only GlusterFS client packages
|
||||
echo -e "${YELLOW}[+] Installing GlusterFS client packages...${NC}"
|
||||
dnf install -y glusterfs glusterfs-fuse || exit 1
|
||||
else
|
||||
is_remote="false"
|
||||
am_lighthouse="true"
|
||||
echo -e "${YELLOW}Configuring as full node (GlusterFS server)${NC}"
|
||||
# Install GlusterFS server packages
|
||||
echo -e "${YELLOW}[+] Installing GlusterFS server packages...${NC}"
|
||||
dnf install -y glusterfs-server || exit 1
|
||||
systemctl enable glusterd
|
||||
systemctl start glusterd
|
||||
# Create brick directory for full nodes
|
||||
mkdir -p "$GLUSTER_BRICK_PATH"
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}[+] Configuring Nebula (IP: ${my_ip})...${NC}"
|
||||
|
||||
# Decode and save CA certificate
|
||||
echo "$ca_cert_b64" | base64 -d > "${NEBULA_CONFIG}/ca.crt"
|
||||
|
||||
echo -e "${RED}WARNING: Certificate signing not implemented in this simplified version.${NC}"
|
||||
echo -e "${YELLOW}On the lighthouse node, run this command to create a certificate for this node:${NC}"
|
||||
echo -e "${GREEN}cd ${NEBULA_CONFIG} && /usr/local/bin/nebula-cert sign -name \"${hostname}\" -ip \"${my_ip}/24\" -groups \"cluster\"${NC}"
|
||||
echo -e "${YELLOW}Then copy ${hostname}.crt and ${hostname}.key to ${NEBULA_CONFIG}/ on this node.${NC}"
|
||||
|
||||
read -p "Press enter once you've created and copied the certificate files..."
|
||||
|
||||
# Create Nebula config
|
||||
cat > "${NEBULA_CONFIG}/config.yaml" <<EOF
|
||||
pki:
|
||||
ca: ${NEBULA_CONFIG}/ca.crt
|
||||
cert: ${NEBULA_CONFIG}/${hostname}.crt
|
||||
key: ${NEBULA_CONFIG}/${hostname}.key
|
||||
|
||||
lighthouse:
|
||||
am_lighthouse: ${am_lighthouse}
|
||||
interval: 60
|
||||
hosts:$(echo "$lighthouse_endpoints" | tr ',' '\n' | while read endpoint; do echo "
|
||||
- \"${endpoint}:${nebula_port}\""; done)
|
||||
|
||||
listen:
|
||||
host: 0.0.0.0
|
||||
port: ${NEBULA_PORT}
|
||||
|
||||
punchy:
|
||||
punch: true
|
||||
respond: true
|
||||
|
||||
tun:
|
||||
disabled: false
|
||||
dev: nebula1
|
||||
drop_local_broadcast: false
|
||||
drop_multicast: false
|
||||
tx_queue: 500
|
||||
mtu: 1300
|
||||
|
||||
logging:
|
||||
level: info
|
||||
format: text
|
||||
|
||||
firewall:
|
||||
conntrack:
|
||||
tcp_timeout: 12m
|
||||
udp_timeout: 3m
|
||||
default_timeout: 10m
|
||||
max_connections: 100000
|
||||
|
||||
outbound:
|
||||
- port: any
|
||||
proto: any
|
||||
host: any
|
||||
|
||||
inbound:
|
||||
- port: any
|
||||
proto: icmp
|
||||
host: any
|
||||
- port: any
|
||||
proto: any
|
||||
host: any
|
||||
EOF
|
||||
|
||||
# Start Nebula
|
||||
create_nebula_service
|
||||
systemctl enable nebula
|
||||
systemctl start nebula
|
||||
|
||||
# Setup firewall
|
||||
setup_firewall
|
||||
|
||||
# Wait for Nebula connection
|
||||
echo -e "${YELLOW}[+] Waiting for Nebula connection...${NC}"
|
||||
sleep 5
|
||||
|
||||
# Test connection - try pinging the first node
|
||||
echo -e "${YELLOW}[+] Testing Nebula connection...${NC}"
|
||||
if ping -c 1 -W 3 192.168.100.1 > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}[✓] Connected to node at 192.168.100.1${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}[!] Could not reach 192.168.100.1 - this may be normal if it's the first node${NC}"
|
||||
fi
|
||||
|
||||
# Register with cluster
|
||||
node_type_str=$([ "$is_remote" = "true" ] && echo "remote" || echo "full")
|
||||
echo "${my_ip} ${hostname} ${node_type_str} $(date)" >> "${NEBULA_CONFIG}/cluster-registry.txt"
|
||||
|
||||
# Handle GlusterFS setup based on node type
|
||||
if [ "$is_remote" = "true" ]; then
|
||||
# Remote node - GlusterFS client only
|
||||
echo -e "${YELLOW}[+] Mounting GlusterFS as client...${NC}"
|
||||
|
||||
# Find a full node to connect to (try first few IPs)
|
||||
mount_successful=false
|
||||
for ip in 192.168.100.1 192.168.100.2 192.168.100.3; do
|
||||
if ping -c 1 -W 2 $ip > /dev/null 2>&1; then
|
||||
echo -e "${YELLOW}Attempting to mount from $ip...${NC}"
|
||||
if mount -t glusterfs ${ip}:/${GLUSTER_VOLUME} ${GLUSTER_MOUNT_PATH} 2>/dev/null; then
|
||||
mount_successful=true
|
||||
mount_ip=$ip
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$mount_successful" = "true" ]; then
|
||||
# Add to fstab
|
||||
grep -q "${GLUSTER_VOLUME}" /etc/fstab || echo "${mount_ip}:/${GLUSTER_VOLUME} ${GLUSTER_MOUNT_PATH} glusterfs defaults,_netdev,backup-volfile-servers=192.168.100.1:192.168.100.2:192.168.100.3 0 0" >> /etc/fstab
|
||||
echo -e "${GREEN}Remote node configured - mounted cluster storage as client${NC}"
|
||||
else
|
||||
echo -e "${RED}Failed to mount GlusterFS volume!${NC}"
|
||||
echo "Make sure at least one full node is running."
|
||||
fi
|
||||
else
|
||||
# Full node - GlusterFS server
|
||||
echo -e "${YELLOW}[+] Joining GlusterFS cluster as server...${NC}"
|
||||
|
||||
# Try to probe existing nodes
|
||||
echo -e "${YELLOW}[+] Looking for existing GlusterFS peers...${NC}"
|
||||
peer_found=false
|
||||
for ip in 192.168.100.1 192.168.100.2 192.168.100.3; do
|
||||
if [ "$ip" != "${my_ip}" ] && ping -c 1 -W 2 $ip > /dev/null 2>&1; then
|
||||
if gluster peer probe $ip 2>/dev/null; then
|
||||
echo "Connected to peer at $ip"
|
||||
peer_found=true
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$peer_found" = "false" ]; then
|
||||
echo -e "${YELLOW}No existing peers found - this might be normal for early nodes${NC}"
|
||||
fi
|
||||
|
||||
# Wait for peer connection
|
||||
sleep 3
|
||||
|
||||
# Create brick directory
|
||||
mkdir -p "${GLUSTER_BRICK_PATH}/brick1"
|
||||
|
||||
if [ "$peer_found" = "true" ]; then
|
||||
# Add brick to existing volume
|
||||
echo -e "${YELLOW}[+] Adding brick to GlusterFS volume...${NC}"
|
||||
|
||||
# Get current replica count
|
||||
replica_count=$(gluster volume info ${GLUSTER_VOLUME} 2>/dev/null | grep "Number of Bricks" | grep -oE "[0-9]+" | head -1)
|
||||
if [ ! -z "$replica_count" ]; then
|
||||
new_replica_count=$((replica_count + 1))
|
||||
gluster volume add-brick ${GLUSTER_VOLUME} replica ${new_replica_count} $(hostname):${GLUSTER_BRICK_PATH}/brick1 force
|
||||
fi
|
||||
fi
|
||||
|
||||
# Mount the volume locally
|
||||
mount -t glusterfs localhost:/${GLUSTER_VOLUME} ${GLUSTER_MOUNT_PATH} 2>/dev/null ||
|
||||
mount -t glusterfs 192.168.100.1:/${GLUSTER_VOLUME} ${GLUSTER_MOUNT_PATH} 2>/dev/null
|
||||
|
||||
# Add to fstab
|
||||
grep -q "${GLUSTER_VOLUME}" /etc/fstab || echo "localhost:/${GLUSTER_VOLUME} ${GLUSTER_MOUNT_PATH} glusterfs defaults,_netdev 0 0" >> /etc/fstab
|
||||
|
||||
echo -e "${GREEN}Full node configured - contributing storage to cluster${NC}"
|
||||
fi
|
||||
|
||||
echo -e "\n${GREEN}════════════════════════════════════════${NC}"
|
||||
echo -e "${GREEN}Successfully joined cluster!${NC}"
|
||||
echo -e "${GREEN}════════════════════════════════════════${NC}\n"
|
||||
echo -e "${YELLOW}Node details:${NC}"
|
||||
echo " - Nebula IP: ${my_ip}"
|
||||
echo " - Hostname: ${hostname}"
|
||||
echo " - Node type: $([ "$is_remote" = "true" ] && echo "Remote (no storage)" || echo "Full (contributes storage)")"
|
||||
echo " - GlusterFS mounted at: ${GLUSTER_MOUNT_PATH}"
|
||||
}
|
||||
|
||||
# Show cluster status
|
||||
show_status() {
|
||||
echo -e "\n${YELLOW}=== Cluster Status ===${NC}\n"
|
||||
|
||||
if [ -f "${NEBULA_CONFIG}/config.yaml" ]; then
|
||||
echo -e "${GREEN}Nebula Status:${NC}"
|
||||
systemctl is-active nebula && echo "Service: Active" || echo "Service: Inactive"
|
||||
|
||||
if ip addr show nebula1 >/dev/null 2>&1; then
|
||||
echo "Interface: nebula1 $(ip addr show nebula1 | grep 'inet ' | awk '{print $2}')"
|
||||
else
|
||||
echo "Interface: Not found"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
if [ -f "${NEBULA_CONFIG}/cluster-registry.txt" ]; then
|
||||
echo -e "${GREEN}Cluster Nodes:${NC}"
|
||||
cat "${NEBULA_CONFIG}/cluster-registry.txt"
|
||||
echo ""
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}Nebula not configured${NC}\n"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}GlusterFS Status:${NC}"
|
||||
gluster peer status 2>/dev/null || echo "Not connected to cluster"
|
||||
echo ""
|
||||
gluster volume status ${GLUSTER_VOLUME} 2>/dev/null || echo "Volume ${GLUSTER_VOLUME} not found"
|
||||
echo ""
|
||||
|
||||
echo -e "${GREEN}Mounted at:${NC} ${GLUSTER_MOUNT_PATH}"
|
||||
df -h ${GLUSTER_MOUNT_PATH} 2>/dev/null || echo "Not mounted"
|
||||
}
|
||||
|
||||
# Main menu
|
||||
echo "What would you like to do?"
|
||||
echo " 1) Create new cluster"
|
||||
echo " 2) Join existing cluster"
|
||||
echo " 3) Show cluster status"
|
||||
echo " 4) Exit"
|
||||
echo ""
|
||||
read -p "Enter choice [1-4]: " choice
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
create_cluster
|
||||
;;
|
||||
2)
|
||||
join_cluster
|
||||
;;
|
||||
3)
|
||||
show_status
|
||||
;;
|
||||
4)
|
||||
echo "Exiting..."
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Invalid choice${NC}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
13
deploy-config.yaml.example
Normal file
13
deploy-config.yaml.example
Normal file
@ -0,0 +1,13 @@
|
||||
# Deployment Configuration
|
||||
hetzner:
|
||||
datacenter: nbg1
|
||||
server_type: cx31
|
||||
ssh_key_name: fedora-server-hetzner
|
||||
|
||||
# Admin Configuration
|
||||
admin_ssh_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..." # Your SSH public key here
|
||||
|
||||
# Hostname Configuration
|
||||
hostname:
|
||||
prefix: nullpoint
|
||||
format: "{prefix}-{date}-{random}" # date format: YYMMDD, random: 000-999
|
||||
169
deploy.py
Normal file
169
deploy.py
Normal file
@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
import subprocess
|
||||
import json
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
import random
|
||||
import tempfile
|
||||
import secrets
|
||||
import string
|
||||
|
||||
# List of adjectives for hostname generation
|
||||
ADJECTIVES = [
|
||||
# Scientific/Sci-fi
|
||||
'quantum', 'atomic', 'plasma', 'fusion', 'ionic', 'magnetic', 'cosmic',
|
||||
'stellar', 'nebular', 'pulsar', 'quasar', 'warp', 'phaser', 'hyper',
|
||||
'temporal', 'spatial', 'dimensional', 'subspace', 'transwarp',
|
||||
# Cool/Interesting
|
||||
'abysmal', 'adamant', 'aerial', 'arcane', 'astral', 'azure', 'celestial',
|
||||
'crimson', 'cryptic', 'crystalline', 'dormant', 'eerie', 'eldritch',
|
||||
'ethereal', 'fractal', 'frozen', 'ghostly', 'gilded', 'singular',
|
||||
'hollow', 'infernal', 'lunar', 'mystic', 'nebulous', 'obsidian',
|
||||
'occult', 'prismatic', 'radiant', 'shadow', 'solar', 'spectral',
|
||||
'stellar', 'sublime', 'titanic', 'twilight', 'void', 'volcanic'
|
||||
]
|
||||
|
||||
NOUNS = [
|
||||
# Star Trek
|
||||
'enterprise', 'voyager', 'galaxy', 'intrepid', 'nova', 'warbird', 'falcon',
|
||||
'aegis', 'nemesis', 'equinox', 'stargazer', 'challenger', 'discovery',
|
||||
'prometheus', 'odyssey', 'daedalus', 'apollo', 'korolev', 'phoenix', 'orion',
|
||||
# Space
|
||||
'nebula', 'pulsar', 'quasar', 'nova', 'supernova', 'blackhole',
|
||||
'wormhole', 'singularity', 'galaxy', 'void', 'rift', 'nexus',
|
||||
# Cool Concepts
|
||||
'abyss', 'aether', 'anomaly', 'artifact', 'beacon', 'cipher', 'crystal',
|
||||
'echo', 'enigma', 'essence', 'fractal', 'horizon', 'infinity',
|
||||
'paradox', 'phoenix', 'prism', 'spectrum', 'tesseract',
|
||||
'vector', 'vertex', 'vortex', 'zenith'
|
||||
]
|
||||
|
||||
def load_config(config_file):
|
||||
"""Load and parse YAML config file."""
|
||||
with open(config_file, 'r') as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
def check_prerequisites():
|
||||
"""Check if required tools are installed."""
|
||||
required_tools = ['hcloud', 'jq']
|
||||
for tool in required_tools:
|
||||
if not shutil.which(tool):
|
||||
print(f"Error: {tool} not found. Please install it first.")
|
||||
sys.exit(1)
|
||||
|
||||
def check_hetzner_token():
|
||||
"""Check if HCLOUD_TOKEN is set."""
|
||||
if not os.environ.get('HCLOUD_TOKEN'):
|
||||
print("Error: HCLOUD_TOKEN environment variable not set")
|
||||
sys.exit(1)
|
||||
|
||||
def generate_hostname():
|
||||
"""Generate a unique hostname in adjective-noun-date-time format."""
|
||||
adjective = random.choice(ADJECTIVES)
|
||||
noun = random.choice(NOUNS)
|
||||
timestamp = datetime.now().strftime('%y%m%d-%H%M')
|
||||
return f"{adjective}-{noun}-{timestamp}"
|
||||
|
||||
def get_image_id():
|
||||
"""Get the base image ID."""
|
||||
cmd = [
|
||||
'hcloud', 'image', 'list',
|
||||
'--type=snapshot',
|
||||
'--selector=name=fedora-coreos-nullpoint',
|
||||
'--output', 'json'
|
||||
]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
images = json.loads(result.stdout)
|
||||
|
||||
if not images:
|
||||
print("Error: Base image not found. Run build.py first.")
|
||||
sys.exit(1)
|
||||
|
||||
return images[0]['id']
|
||||
|
||||
def generate_secure_passphrase(length=32):
|
||||
"""Generate a secure random passphrase."""
|
||||
alphabet = string.ascii_letters + string.digits + string.punctuation
|
||||
return ''.join(secrets.choice(alphabet) for _ in range(length))
|
||||
|
||||
def create_server(config, hostname, image_id):
|
||||
"""Create a new server."""
|
||||
hetzner = config['hetzner']
|
||||
|
||||
# Generate cloud-init config for this specific server
|
||||
cloud_init = {
|
||||
'hostname': hostname,
|
||||
'timezone': 'UTC',
|
||||
'users': [
|
||||
{
|
||||
'name': 'admin',
|
||||
'groups': ['wheel'],
|
||||
'sudo': 'ALL=(ALL) NOPASSWD:ALL',
|
||||
'ssh_authorized_keys': [config['admin_ssh_key']]
|
||||
}
|
||||
],
|
||||
'package_update': True,
|
||||
'package_upgrade': True
|
||||
}
|
||||
|
||||
# Create temporary cloud-init config
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml') as f:
|
||||
yaml.dump(cloud_init, f)
|
||||
f.flush()
|
||||
|
||||
cmd = [
|
||||
'hcloud', 'server', 'create',
|
||||
'--name', hostname,
|
||||
'--type', hetzner['server_type'],
|
||||
'--datacenter', hetzner['datacenter'],
|
||||
'--image', str(image_id),
|
||||
'--ssh-key', hetzner['ssh_key_name'],
|
||||
'--user-data-from-file', f.name
|
||||
]
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
print(f"\nServer '{hostname}' created successfully!")
|
||||
print("The LUKS passphrase has been saved to /root/luks-passphrase.txt on the server.")
|
||||
print("Please save this passphrase securely - it will be needed if TPM+Tang unlock fails.")
|
||||
print("\nYou can connect using: ssh admin@<server-ip>")
|
||||
|
||||
def get_server_ip(hostname):
|
||||
"""Get the server's IP address."""
|
||||
cmd = ['hcloud', 'server', 'ip', hostname]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
return result.stdout.strip()
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
# Load config
|
||||
if not os.path.exists('deploy-config.yaml'):
|
||||
print("Error: deploy-config.yaml not found")
|
||||
sys.exit(1)
|
||||
|
||||
config = load_config('deploy-config.yaml')
|
||||
|
||||
# Check prerequisites
|
||||
check_prerequisites()
|
||||
check_hetzner_token()
|
||||
|
||||
# Generate hostname
|
||||
hostname = generate_hostname()
|
||||
|
||||
# Get image ID
|
||||
image_id = get_image_id()
|
||||
|
||||
# Create server
|
||||
print(f"Creating server '{hostname}'...")
|
||||
create_server(config, hostname, image_id)
|
||||
|
||||
# Get server IP
|
||||
server_ip = get_server_ip(hostname)
|
||||
print(f"Server created! IP: {server_ip}")
|
||||
print(f"You can connect using: ssh core@{server_ip}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -1,271 +0,0 @@
|
||||
'builtin' 'local' '-a' 'p10k_config_opts'
|
||||
[[ ! -o 'aliases' ]] || p10k_config_opts+=('aliases')
|
||||
[[ ! -o 'sh_glob' ]] || p10k_config_opts+=('sh_glob')
|
||||
[[ ! -o 'no_brace_expand' ]] || p10k_config_opts+=('no_brace_expand')
|
||||
'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
|
||||
() {
|
||||
emulate -L zsh
|
||||
setopt no_unset extended_glob
|
||||
unset -m 'POWERLEVEL9K_*'
|
||||
autoload -Uz is-at-least && is-at-least 5.1 || return
|
||||
zmodload zsh/langinfo
|
||||
if [[ ${langinfo[CODESET]:-} != (utf|UTF)(-|)8 ]]; then
|
||||
local LC_ALL=${${(@M)$(locale -a):#*.(utf|UTF)(-|)8}[1]:-en_US.UTF-8}
|
||||
fi
|
||||
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(
|
||||
context
|
||||
dir
|
||||
vcs
|
||||
)
|
||||
typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(
|
||||
background_jobs
|
||||
direnv
|
||||
virtualenv
|
||||
pyenv
|
||||
disk_usage
|
||||
)
|
||||
typeset -g POWERLEVEL9K_VISUAL_IDENTIFIER_EXPANSION='${P9K_VISUAL_IDENTIFIER// }'
|
||||
typeset -g POWERLEVEL9K_MODE=nerdfont-complete
|
||||
typeset -g POWERLEVEL9K_ICON_BEFORE_CONTENT=
|
||||
typeset -g POWERLEVEL9K_PROMPT_ADD_NEWLINE=false
|
||||
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_PREFIX='%242F╭─'
|
||||
typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_PREFIX='%242F├─'
|
||||
typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_PREFIX='%242F╰─'
|
||||
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_SUFFIX='%242F─╮'
|
||||
typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_SUFFIX='%242F─┤'
|
||||
typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_SUFFIX='%242F─╯'
|
||||
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' '
|
||||
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_BACKGROUND=
|
||||
if [[ $POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR != ' ' ]]; then
|
||||
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_FOREGROUND=242
|
||||
typeset -g POWERLEVEL9K_EMPTY_LINE_LEFT_PROMPT_FIRST_SEGMENT_END_SYMBOL='%{%}'
|
||||
typeset -g POWERLEVEL9K_EMPTY_LINE_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL='%{%}'
|
||||
fi
|
||||
typeset -g POWERLEVEL9K_LEFT_SUBSEGMENT_SEPARATOR='\uE0B1'
|
||||
typeset -g POWERLEVEL9K_RIGHT_SUBSEGMENT_SEPARATOR='\uE0B3'
|
||||
typeset -g POWERLEVEL9K_LEFT_SEGMENT_SEPARATOR='\uE0B0'
|
||||
typeset -g POWERLEVEL9K_RIGHT_SEGMENT_SEPARATOR='\uE0B2'
|
||||
typeset -g POWERLEVEL9K_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL='\uE0B0'
|
||||
typeset -g POWERLEVEL9K_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL='\uE0B2'
|
||||
typeset -g POWERLEVEL9K_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL=''
|
||||
typeset -g POWERLEVEL9K_RIGHT_PROMPT_LAST_SEGMENT_END_SYMBOL=''
|
||||
typeset -g POWERLEVEL9K_EMPTY_LINE_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL=
|
||||
typeset -g POWERLEVEL9K_OS_ICON_FOREGROUND=232
|
||||
typeset -g POWERLEVEL9K_OS_ICON_BACKGROUND=7
|
||||
typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='%B${P9K_CONTENT// }'
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_BACKGROUND=
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS,VIOWR}_FOREGROUND=76
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS,VIOWR}_FOREGROUND=196
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIINS_CONTENT_EXPANSION=' '
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION=' '
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION=' '
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIOWR_CONTENT_EXPANSION=' '
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=true
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL=
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL=
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_{LEFT,RIGHT}_WHITESPACE=
|
||||
typeset -g POWERLEVEL9K_DIR_BACKGROUND=234
|
||||
typeset -g POWERLEVEL9K_DIR_FOREGROUND=39
|
||||
typeset -g POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_unique
|
||||
typeset -g POWERLEVEL9K_SHORTEN_DELIMITER=
|
||||
typeset -g POWERLEVEL9K_DIR_SHORTENED_FOREGROUND=103
|
||||
typeset -g POWERLEVEL9K_DIR_ANCHOR_FOREGROUND=39
|
||||
typeset -g POWERLEVEL9K_DIR_ANCHOR_BOLD=false
|
||||
local anchor_files=(
|
||||
.bzr
|
||||
.citc
|
||||
.git
|
||||
.hg
|
||||
.node-version
|
||||
.python-version
|
||||
.ruby-version
|
||||
.shorten_folder_marker
|
||||
.svn
|
||||
.terraform
|
||||
CVS
|
||||
Cargo.toml
|
||||
composer.json
|
||||
go.mod
|
||||
package.json
|
||||
)
|
||||
typeset -g POWERLEVEL9K_SHORTEN_FOLDER_MARKER="(${(j:|:)anchor_files})"
|
||||
typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1
|
||||
typeset -g POWERLEVEL9K_DIR_MAX_LENGTH=80
|
||||
typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS=40
|
||||
typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT=50
|
||||
typeset -g POWERLEVEL9K_DIR_HYPERLINK=false
|
||||
typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=true
|
||||
typeset -g POWERLEVEL9K_DIR_CLASSES=()
|
||||
typeset -g POWERLEVEL9K_VCS_CLEAN_FOREGROUND=10
|
||||
typeset -g POWERLEVEL9K_VCS_CLEAN_BACKGROUND=235
|
||||
typeset -g POWERLEVEL9K_VCS_MODIFIED_FOREGROUND=10
|
||||
typeset -g POWERLEVEL9K_VCS_MODIFIED_BACKGROUND=235
|
||||
typeset -g POWERLEVEL9K_VCS_UNTRACKED_FOREGROUND=10
|
||||
typeset -g POWERLEVEL9K_VCS_UNTRACKED_BACKGROUND=235
|
||||
typeset -g POWERLEVEL9K_VCS_CONFLICTED_FOREGROUND=9
|
||||
typeset -g POWERLEVEL9K_VCS_CONFLICTED_BACKGROUND=235
|
||||
typeset -g POWERLEVEL9K_VCS_LOADING_FOREGROUND=15
|
||||
typeset -g POWERLEVEL9K_VCS_LOADING_BACKGROUND=255
|
||||
typeset -g POWERLEVEL9K_VCS_BRANCH_ICON='\uF126 '
|
||||
POWERLEVEL9K_VCS_BRANCH_ICON=${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}
|
||||
typeset -g POWERLEVEL9K_VCS_UNTRACKED_ICON='?'
|
||||
POWERLEVEL9K_VCS_UNTRACKED_ICON=${(g::)POWERLEVEL9K_VCS_UNTRACKED_ICON}
|
||||
function my_git_formatter() {
|
||||
emulate -L zsh
|
||||
if [[ -n $P9K_CONTENT ]]; then
|
||||
typeset -g my_git_format=$P9K_CONTENT
|
||||
return
|
||||
fi
|
||||
local meta=''
|
||||
local clean=''
|
||||
local modified=''
|
||||
local untracked=''
|
||||
local conflicted=''
|
||||
local res
|
||||
local where
|
||||
if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then
|
||||
res+="${clean}${POWERLEVEL9K_VCS_BRANCH_ICON}"
|
||||
where=${(V)VCS_STATUS_LOCAL_BRANCH}
|
||||
elif [[ -n $VCS_STATUS_TAG ]]; then
|
||||
res+="${meta}#"
|
||||
where=${(V)VCS_STATUS_TAG}
|
||||
fi
|
||||
(( $#where > 32 )) && where[13,-13]="…"
|
||||
res+="${clean}${where//\%/%%}"
|
||||
[[ -z $where ]] && res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
|
||||
if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then
|
||||
res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}"
|
||||
fi
|
||||
(( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}⇣${VCS_STATUS_COMMITS_BEHIND}"
|
||||
(( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" "
|
||||
(( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}⇡${VCS_STATUS_COMMITS_AHEAD}"
|
||||
(( VCS_STATUS_STASHES )) && res+=" ${clean}*${VCS_STATUS_STASHES}"
|
||||
[[ -n $VCS_STATUS_ACTION ]] && res+=" ${conflicted}${VCS_STATUS_ACTION}"
|
||||
(( VCS_STATUS_NUM_CONFLICTED )) && res+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}"
|
||||
(( VCS_STATUS_NUM_STAGED )) && res+=" ${modified}+${VCS_STATUS_NUM_STAGED}"
|
||||
(( VCS_STATUS_NUM_UNSTAGED )) && res+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}"
|
||||
(( VCS_STATUS_NUM_UNTRACKED )) && res+=" ${untracked}${POWERLEVEL9K_VCS_UNTRACKED_ICON}${VCS_STATUS_NUM_UNTRACKED}"
|
||||
typeset -g my_git_format=$res
|
||||
}
|
||||
functions -M my_git_formatter 2>/dev/null
|
||||
typeset -g POWERLEVEL9K_VCS_DISABLE_GITSTATUS_FORMATTING=true
|
||||
typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${$((my_git_formatter()))+${my_git_format}}'
|
||||
typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED,CONFLICTED,COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=-1
|
||||
typeset -g POWERLEVEL9K_VCS_BACKENDS=(git)
|
||||
typeset -g POWERLEVEL9K_STATUS_EXTENDED_STATES=true
|
||||
typeset -g POWERLEVEL9K_STATUS_OK=true
|
||||
typeset -g POWERLEVEL9K_STATUS_OK_VISUAL_IDENTIFIER_EXPANSION='✔'
|
||||
typeset -g POWERLEVEL9K_STATUS_OK_PIPE=true
|
||||
typeset -g POWERLEVEL9K_STATUS_OK_PIPE_VISUAL_IDENTIFIER_EXPANSION='✔'
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR=true
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_VISUAL_IDENTIFIER_EXPANSION='✘'
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL=true
|
||||
typeset -g POWERLEVEL9K_STATUS_VERBOSE_SIGNAME=false
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_VISUAL_IDENTIFIER_EXPANSION='✘'
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE=true
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_VISUAL_IDENTIFIER_EXPANSION='✘'
|
||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_BACKGROUND=3
|
||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=3
|
||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0
|
||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FORMAT='d h m s'
|
||||
typeset -g POWERLEVEL9K_BACKGROUND_JOBS_VERBOSE=false
|
||||
typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_CONTENT_EXPANSION=
|
||||
typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_VISUAL_IDENTIFIER_EXPANSION=
|
||||
typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_LEVEL=95
|
||||
typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_LEVEL=98
|
||||
typeset -g POWERLEVEL9K_DISK_USAGE_ONLY_WARNING=true
|
||||
typeset -g POWERLEVEL9K_VI_MODE_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_VI_COMMAND_MODE_STRING=NORMAL
|
||||
typeset -g POWERLEVEL9K_VI_MODE_NORMAL_BACKGROUND=2
|
||||
typeset -g POWERLEVEL9K_VI_VISUAL_MODE_STRING=VISUAL
|
||||
typeset -g POWERLEVEL9K_VI_MODE_VISUAL_BACKGROUND=4
|
||||
typeset -g POWERLEVEL9K_VI_OVERWRITE_MODE_STRING=OVERTYPE
|
||||
typeset -g POWERLEVEL9K_VI_MODE_OVERWRITE_BACKGROUND=3
|
||||
typeset -g POWERLEVEL9K_VI_INSERT_MODE_STRING=
|
||||
typeset -g POWERLEVEL9K_VI_MODE_INSERT_FOREGROUND=8
|
||||
typeset -g POWERLEVEL9K_LOAD_WHICH=5
|
||||
typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_TOTAL=true
|
||||
typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_FILTERED=false
|
||||
typeset -g POWERLEVEL9K_TIMEWARRIOR_CONTENT_EXPANSION='${P9K_CONTENT:0:24}${${P9K_CONTENT:24}:+…}'
|
||||
typeset -g POWERLEVEL9K_CONTEXT_ROOT_FOREGROUND=1
|
||||
typeset -g POWERLEVEL9K_CONTEXT_ROOT_BACKGROUND=0
|
||||
typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_FOREGROUND=3
|
||||
typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_BACKGROUND=0
|
||||
typeset -g POWERLEVEL9K_CONTEXT_FOREGROUND=3
|
||||
typeset -g POWERLEVEL9K_CONTEXT_BACKGROUND=0
|
||||
typeset -g POWERLEVEL9K_CONTEXT_ROOT_TEMPLATE='%n@%m'
|
||||
typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_TEMPLATE='%n@%m'
|
||||
typeset -g POWERLEVEL9K_CONTEXT_TEMPLATE='%n'
|
||||
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false
|
||||
typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER=
|
||||
typeset -g POWERLEVEL9K_ANACONDA_SHOW_PYTHON_VERSION=false
|
||||
typeset -g POWERLEVEL9K_ANACONDA_{LEFT,RIGHT}_DELIMITER=
|
||||
typeset -g POWERLEVEL9K_PYENV_SOURCES=(shell local global)
|
||||
typeset -g POWERLEVEL9K_PYENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_GOENV_SOURCES=(shell local global)
|
||||
typeset -g POWERLEVEL9K_GOENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_NODENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_NODEENV_SHOW_NODE_VERSION=false
|
||||
typeset -g POWERLEVEL9K_NODEENV_{LEFT,RIGHT}_DELIMITER=
|
||||
typeset -g POWERLEVEL9K_NODE_VERSION_PROJECT_ONLY=true
|
||||
typeset -g POWERLEVEL9K_GO_VERSION_PROJECT_ONLY=true
|
||||
typeset -g POWERLEVEL9K_RUST_VERSION_PROJECT_ONLY=true
|
||||
typeset -g POWERLEVEL9K_DOTNET_VERSION_PROJECT_ONLY=true
|
||||
typeset -g POWERLEVEL9K_RBENV_SOURCES=(shell local global)
|
||||
typeset -g POWERLEVEL9K_RBENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_RVM_SHOW_GEMSET=false
|
||||
typeset -g POWERLEVEL9K_RVM_SHOW_PREFIX=false
|
||||
typeset -g POWERLEVEL9K_LUAENV_SOURCES=(shell local global)
|
||||
typeset -g POWERLEVEL9K_LUAENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_JENV_SOURCES=(shell local global)
|
||||
typeset -g POWERLEVEL9K_JENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_PLENV_SOURCES=(shell local global)
|
||||
typeset -g POWERLEVEL9K_PLENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
|
||||
'*' DEFAULT)
|
||||
typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_FOREGROUND=4
|
||||
typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_BACKGROUND=0
|
||||
typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc'
|
||||
typeset -g POWERLEVEL9K_KUBECONTEXT_CLASSES=(
|
||||
'*' DEFAULT)
|
||||
typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_FOREGROUND=7
|
||||
typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_BACKGROUND=5
|
||||
typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION=
|
||||
POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION+='${P9K_KUBECONTEXT_CLOUD_CLUSTER:-${P9K_KUBECONTEXT_NAME}}'
|
||||
POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION+='${${:-/$P9K_KUBECONTEXT_NAMESPACE}:#/default}'
|
||||
typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|terraform|pulumi'
|
||||
typeset -g POWERLEVEL9K_AWS_CLASSES=(
|
||||
'*' DEFAULT)
|
||||
typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi'
|
||||
typeset -g POWERLEVEL9K_GCLOUD_SHOW_ON_COMMAND='gcloud|gcs'
|
||||
typeset -g POWERLEVEL9K_GCLOUD_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT//\%/%%}'
|
||||
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi'
|
||||
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES=(
|
||||
'*' DEFAULT)
|
||||
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_CONTENT_EXPANSION='${P9K_GOOGLE_APP_CRED_PROJECT_ID//\%/%%}'
|
||||
typeset -g POWERLEVEL9K_VPN_IP_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_VPN_IP_BACKGROUND=6
|
||||
typeset -g POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION=
|
||||
typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(wg|(.*tun))[0-9]*'
|
||||
typeset -g POWERLEVEL9K_BATTERY_LOW_THRESHOLD=20
|
||||
typeset -g POWERLEVEL9K_BATTERY_LOW_FOREGROUND=1
|
||||
typeset -g POWERLEVEL9K_BATTERY_{CHARGING,CHARGED}_FOREGROUND=2
|
||||
typeset -g POWERLEVEL9K_BATTERY_DISCONNECTED_FOREGROUND=3
|
||||
typeset -g POWERLEVEL9K_BATTERY_STAGES=$'\uf58d\uf579\uf57a\uf57b\uf57c\uf57d\uf57e\uf57f\uf580\uf581\uf578'
|
||||
typeset -g POWERLEVEL9K_BATTERY_VERBOSE=false
|
||||
typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M:%S}'
|
||||
typeset -g POWERLEVEL9K_TIME_UPDATE_ON_COMMAND=false
|
||||
function prompt_example() {
|
||||
p10k segment -i "POWERLEVEL9K_LINUX_ICON" -r -f blue -t " "
|
||||
}
|
||||
function instant_prompt_example() {
|
||||
prompt_example
|
||||
}
|
||||
typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=off
|
||||
typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose
|
||||
typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true
|
||||
(( ! $+functions[p10k] )) || p10k reload
|
||||
}
|
||||
(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]}
|
||||
'builtin' 'unset' 'p10k_config_opts'
|
||||
1370
dotfiles/.tmux.conf
1370
dotfiles/.tmux.conf
File diff suppressed because it is too large
Load Diff
@ -1,404 +0,0 @@
|
||||
# : << EOF
|
||||
# https://github.com/gpakosz/.tmux
|
||||
# (‑●‑●)> dual licensed under the WTFPL v2 license and the MIT license,
|
||||
# without any warranty.
|
||||
# Copyright 2012— Gregory Pakosz (@gpakosz).
|
||||
|
||||
|
||||
# -- navigation ----------------------------------------------------------------
|
||||
|
||||
# if you're running tmux within iTerm2
|
||||
# - and tmux is 1.9 or 1.9a
|
||||
# - and iTerm2 is configured to let option key act as +Esc
|
||||
# - and iTerm2 is configured to send [1;9A -> [1;9D for option + arrow keys
|
||||
# then uncomment the following line to make Meta + arrow keys mapping work
|
||||
#set -ga terminal-overrides "*:kUP3=\e[1;9A,*:kDN3=\e[1;9B,*:kRIT3=\e[1;9C,*:kLFT3=\e[1;9D"
|
||||
|
||||
|
||||
# -- windows & pane creation ---------------------------------------------------
|
||||
|
||||
# new window retains current path, possible values are:
|
||||
# - true
|
||||
# - false (default)
|
||||
tmux_conf_new_window_retain_current_path=false
|
||||
|
||||
# new pane retains current path, possible values are:
|
||||
# - true (default)
|
||||
# - false
|
||||
tmux_conf_new_pane_retain_current_path=true
|
||||
|
||||
# new pane tries to reconnect ssh sessions (experimental), possible values are:
|
||||
# - true
|
||||
# - false (default)
|
||||
tmux_conf_new_pane_reconnect_ssh=true
|
||||
|
||||
# prompt for session name when creating a new session, possible values are:
|
||||
# - true
|
||||
# - false (default)
|
||||
tmux_conf_new_session_prompt=false
|
||||
|
||||
|
||||
# -- display -------------------------------------------------------------------
|
||||
|
||||
# RGB 24-bit colour support (tmux >= 2.2), possible values are:
|
||||
# - true
|
||||
# - false (default)
|
||||
tmux_conf_24b_colour=true
|
||||
|
||||
# default theme
|
||||
tmux_conf_theme_colour_1="#080808" # dark gray
|
||||
tmux_conf_theme_colour_2="#303030" # gray
|
||||
tmux_conf_theme_colour_3="#8a8a8a" # light gray
|
||||
tmux_conf_theme_colour_4="#00afff" # light blue
|
||||
tmux_conf_theme_colour_5="#ffff00" # yellow
|
||||
tmux_conf_theme_colour_6="#080808" # dark gray
|
||||
tmux_conf_theme_colour_7="#e4e4e4" # white
|
||||
tmux_conf_theme_colour_8="#080808" # dark gray
|
||||
tmux_conf_theme_colour_9="#ffff00" # yellow
|
||||
tmux_conf_theme_colour_10="#ff00af" # pink
|
||||
tmux_conf_theme_colour_11="#5fff00" # green
|
||||
tmux_conf_theme_colour_12="#8a8a8a" # light gray
|
||||
tmux_conf_theme_colour_13="#e4e4e4" # white
|
||||
tmux_conf_theme_colour_14="#080808" # dark gray
|
||||
tmux_conf_theme_colour_15="#080808" # dark gray
|
||||
tmux_conf_theme_colour_16="#d70000" # red
|
||||
tmux_conf_theme_colour_17="#e4e4e4" # white
|
||||
|
||||
# default theme (ansi)
|
||||
#tmux_conf_theme_colour_1="colour0"
|
||||
#tmux_conf_theme_colour_2="colour8"
|
||||
#tmux_conf_theme_colour_3="colour8"
|
||||
#tmux_conf_theme_colour_4="colour14"
|
||||
#tmux_conf_theme_colour_5="colour11"
|
||||
#tmux_conf_theme_colour_6="colour0"
|
||||
#tmux_conf_theme_colour_7="colour15"
|
||||
#tmux_conf_theme_colour_8="colour0"
|
||||
#tmux_conf_theme_colour_9="colour11"
|
||||
#tmux_conf_theme_colour_10="colour13"
|
||||
#tmux_conf_theme_colour_11="colour10"
|
||||
#tmux_conf_theme_colour_12="colour8"
|
||||
#tmux_conf_theme_colour_13="colour15"
|
||||
#tmux_conf_theme_colour_14="colour0"
|
||||
#tmux_conf_theme_colour_15="colour0"
|
||||
#tmux_conf_theme_colour_16="colour1"
|
||||
#tmux_conf_theme_colour_17="colour15"
|
||||
|
||||
# window style
|
||||
tmux_conf_theme_window_fg="default"
|
||||
tmux_conf_theme_window_bg="default"
|
||||
|
||||
# highlight focused pane (tmux >= 2.1), possible values are:
|
||||
# - true
|
||||
# - false (default)
|
||||
tmux_conf_theme_highlight_focused_pane=false
|
||||
|
||||
# focused pane colours:
|
||||
tmux_conf_theme_focused_pane_bg="$tmux_conf_theme_colour_2"
|
||||
|
||||
# pane border style, possible values are:
|
||||
# - thin (default)
|
||||
# - fat
|
||||
tmux_conf_theme_pane_border_style=thin
|
||||
|
||||
# pane borders colours:
|
||||
tmux_conf_theme_pane_border="$tmux_conf_theme_colour_2"
|
||||
tmux_conf_theme_pane_active_border="$tmux_conf_theme_colour_4"
|
||||
|
||||
# pane indicator colours (when you hit <prefix> + q)
|
||||
tmux_conf_theme_pane_indicator="$tmux_conf_theme_colour_4"
|
||||
tmux_conf_theme_pane_active_indicator="$tmux_conf_theme_colour_4"
|
||||
|
||||
# status line style
|
||||
tmux_conf_theme_message_fg="$tmux_conf_theme_colour_1"
|
||||
tmux_conf_theme_message_bg="$tmux_conf_theme_colour_5"
|
||||
tmux_conf_theme_message_attr="bold"
|
||||
|
||||
# status line command style (<prefix> : Escape)
|
||||
tmux_conf_theme_message_command_fg="$tmux_conf_theme_colour_5"
|
||||
tmux_conf_theme_message_command_bg="$tmux_conf_theme_colour_1"
|
||||
tmux_conf_theme_message_command_attr="bold"
|
||||
|
||||
# window modes style
|
||||
tmux_conf_theme_mode_fg="$tmux_conf_theme_colour_1"
|
||||
tmux_conf_theme_mode_bg="$tmux_conf_theme_colour_5"
|
||||
tmux_conf_theme_mode_attr="bold"
|
||||
|
||||
# status line style
|
||||
tmux_conf_theme_status_fg="$tmux_conf_theme_colour_3"
|
||||
tmux_conf_theme_status_bg="$tmux_conf_theme_colour_1"
|
||||
tmux_conf_theme_status_attr="none"
|
||||
|
||||
# terminal title
|
||||
# - built-in variables are:
|
||||
# - #{circled_window_index}
|
||||
# - #{circled_session_name}
|
||||
# - #{hostname}
|
||||
# - #{hostname_ssh}
|
||||
# - #{username}
|
||||
# - #{username_ssh}
|
||||
#tmux_conf_theme_terminal_title="#{username}@#{hostname}: #S - #I - #W"
|
||||
tmux_conf_theme_terminal_title=" "
|
||||
|
||||
# window status style
|
||||
# - built-in variables are:
|
||||
# - #{circled_window_index}
|
||||
# - #{circled_session_name}
|
||||
# - #{hostname}
|
||||
# - #{hostname_ssh}
|
||||
# - #{username}
|
||||
# - #{username_ssh}
|
||||
tmux_conf_theme_window_status_fg="$tmux_conf_theme_colour_3"
|
||||
tmux_conf_theme_window_status_bg="$tmux_conf_theme_colour_1"
|
||||
tmux_conf_theme_window_status_attr="none"
|
||||
tmux_conf_theme_window_status_format="#I #W"
|
||||
#tmux_conf_theme_window_status_format="#{circled_window_index} #W"
|
||||
#tmux_conf_theme_window_status_format="#I #W#{?window_bell_flag,🔔,}#{?window_zoomed_flag,🔍,}"
|
||||
|
||||
# window current status style
|
||||
# - built-in variables are:
|
||||
# - #{circled_window_index}
|
||||
# - #{circled_session_name}
|
||||
# - #{hostname}
|
||||
# - #{hostname_ssh}
|
||||
# - #{username}
|
||||
# - #{username_ssh}
|
||||
tmux_conf_theme_window_status_current_fg="$tmux_conf_theme_colour_1"
|
||||
tmux_conf_theme_window_status_current_bg="$tmux_conf_theme_colour_4"
|
||||
tmux_conf_theme_window_status_current_attr="bold"
|
||||
tmux_conf_theme_window_status_current_format="#I #W"
|
||||
#tmux_conf_theme_window_status_current_format="#{circled_window_index} #W"
|
||||
#tmux_conf_theme_window_status_current_format="#I #W#{?window_zoomed_flag,🔍,}"
|
||||
|
||||
# window activity status style
|
||||
tmux_conf_theme_window_status_activity_fg="default"
|
||||
tmux_conf_theme_window_status_activity_bg="default"
|
||||
tmux_conf_theme_window_status_activity_attr="underscore"
|
||||
|
||||
# window bell status style
|
||||
tmux_conf_theme_window_status_bell_fg="$tmux_conf_theme_colour_5"
|
||||
tmux_conf_theme_window_status_bell_bg="default"
|
||||
tmux_conf_theme_window_status_bell_attr="blink,bold"
|
||||
|
||||
# window last status style
|
||||
tmux_conf_theme_window_status_last_fg="$tmux_conf_theme_colour_4"
|
||||
tmux_conf_theme_window_status_last_bg="$tmux_conf_theme_colour_2"
|
||||
tmux_conf_theme_window_status_last_attr="none"
|
||||
|
||||
# status left/right sections separators
|
||||
#tmux_conf_theme_left_separator_main=""
|
||||
#tmux_conf_theme_left_separator_sub="|"
|
||||
#tmux_conf_theme_right_separator_main=""
|
||||
#tmux_conf_theme_right_separator_sub="|"
|
||||
tmux_conf_theme_left_separator_main="\uE0B0" # /!\ you don't need to install Powerline
|
||||
tmux_conf_theme_left_separator_sub="\uE0B1" # you only need fonts patched with
|
||||
tmux_conf_theme_right_separator_main="\uE0B2" # Powerline symbols or the standalone
|
||||
tmux_conf_theme_right_separator_sub="\uE0B3" # PowerlineSymbols.otf font, see README.md
|
||||
|
||||
# status left/right content:
|
||||
# - separate main sections with "|"
|
||||
# - separate subsections with ","
|
||||
# - built-in variables are:
|
||||
# - #{battery_bar}
|
||||
# - #{battery_hbar}
|
||||
# - #{battery_percentage}
|
||||
# - #{battery_status}
|
||||
# - #{battery_vbar}
|
||||
# - #{circled_session_name}
|
||||
# - #{hostname_ssh}
|
||||
# - #{hostname}
|
||||
# - #{loadavg}
|
||||
# - #{pairing}
|
||||
# - #{prefix}
|
||||
# - #{root}
|
||||
# - #{synchronized}
|
||||
# - #{uptime_y}
|
||||
# - #{uptime_d} (modulo 365 when #{uptime_y} is used)
|
||||
# - #{uptime_h}
|
||||
# - #{uptime_m}
|
||||
# - #{uptime_s}
|
||||
# - #{username}
|
||||
# - #{username_ssh}
|
||||
#tmux_conf_theme_status_left=" ❐ #S | ↑#{?uptime_y, #{uptime_y}y,}#{?uptime_d, #{uptime_d}d,}#{?uptime_h, #{uptime_h}h,}#{?uptime_m, #{uptime_m}m,} "
|
||||
tmux_conf_theme_status_left=" [#S] | #{username}@#{hostname} ,"
|
||||
#tmux_conf_theme_status_left=" [#S] "
|
||||
#tmux_conf_theme_status_right=" #{prefix}#{pairing}#{synchronized}#{?battery_status,#{battery_status},}#{?battery_bar, #{battery_bar},}#{?battery_percentage, #{battery_percentage},} , %R , %d %b | #{username}#{root} | #{hostname} "
|
||||
tmux_conf_theme_status_right=", %R , %d %b | #{battery_percentage} "
|
||||
|
||||
# status left style
|
||||
tmux_conf_theme_status_left_fg="$tmux_conf_theme_colour_1,$tmux_conf_theme_colour_12"
|
||||
tmux_conf_theme_status_left_bg="$tmux_conf_theme_colour_3,$tmux_conf_theme_colour_1"
|
||||
tmux_conf_theme_status_left_attr="bold,none,none"
|
||||
|
||||
# status right style
|
||||
tmux_conf_theme_status_right_fg="$tmux_conf_theme_colour_12,$tmux_conf_theme_colour_1"
|
||||
tmux_conf_theme_status_right_bg="$tmux_conf_theme_colour_1,$tmux_conf_theme_colour_3"
|
||||
tmux_conf_theme_status_right_attr="none,none,none"
|
||||
|
||||
# pairing indicator
|
||||
tmux_conf_theme_pairing="⚇" # U+2687
|
||||
tmux_conf_theme_pairing_fg="none"
|
||||
tmux_conf_theme_pairing_bg="none"
|
||||
tmux_conf_theme_pairing_attr="none"
|
||||
|
||||
# prefix indicator
|
||||
tmux_conf_theme_prefix="⌨" # U+2328
|
||||
tmux_conf_theme_prefix_fg="none"
|
||||
tmux_conf_theme_prefix_bg="none"
|
||||
tmux_conf_theme_prefix_attr="none"
|
||||
|
||||
# root indicator
|
||||
tmux_conf_theme_root="!"
|
||||
tmux_conf_theme_root_fg="none"
|
||||
tmux_conf_theme_root_bg="none"
|
||||
tmux_conf_theme_root_attr="bold,blink"
|
||||
|
||||
# synchronized indicator
|
||||
tmux_conf_theme_synchronized="⚏" # U+268F
|
||||
tmux_conf_theme_synchronized_fg="none"
|
||||
tmux_conf_theme_synchronized_bg="none"
|
||||
tmux_conf_theme_synchronized_attr="none"
|
||||
|
||||
# battery bar symbols
|
||||
tmux_conf_battery_bar_symbol_full="◼"
|
||||
tmux_conf_battery_bar_symbol_empty="◻"
|
||||
#tmux_conf_battery_bar_symbol_full="♥"
|
||||
#tmux_conf_battery_bar_symbol_empty="·"
|
||||
|
||||
# battery bar length (in number of symbols), possible values are:
|
||||
# - auto
|
||||
# - a number, e.g. 5
|
||||
tmux_conf_battery_bar_length="auto"
|
||||
|
||||
# battery bar palette, possible values are:
|
||||
# - gradient (default)
|
||||
# - heat
|
||||
# - "colour_full_fg,colour_empty_fg,colour_bg"
|
||||
tmux_conf_battery_bar_palette="gradient"
|
||||
#tmux_conf_battery_bar_palette="#d70000,#e4e4e4,#000000" # red, white, black
|
||||
|
||||
# battery hbar palette, possible values are:
|
||||
# - gradient (default)
|
||||
# - heat
|
||||
# - "colour_low,colour_half,colour_full"
|
||||
tmux_conf_battery_hbar_palette="gradient"
|
||||
#tmux_conf_battery_hbar_palette="#d70000,#ff5f00,#5fff00" # red, orange, green
|
||||
|
||||
# battery vbar palette, possible values are:
|
||||
# - gradient (default)
|
||||
# - heat
|
||||
# - "colour_low,colour_half,colour_full"
|
||||
tmux_conf_battery_vbar_palette="gradient"
|
||||
#tmux_conf_battery_vbar_palette="#d70000,#ff5f00,#5fff00" # red, orange, green
|
||||
|
||||
# symbols used to indicate whether battery is charging or discharging
|
||||
tmux_conf_battery_status_charging="↑" # U+2191
|
||||
tmux_conf_battery_status_discharging="↓" # U+2193
|
||||
#tmux_conf_battery_status_charging="🔌" # U+1F50C
|
||||
#tmux_conf_battery_status_discharging="🔋" # U+1F50B
|
||||
|
||||
# clock style (when you hit <prefix> + t)
|
||||
# you may want to use %I:%M %p in place of %R in tmux_conf_theme_status_right
|
||||
tmux_conf_theme_clock_colour="$tmux_conf_theme_colour_4"
|
||||
tmux_conf_theme_clock_style="24"
|
||||
|
||||
|
||||
# -- clipboard -----------------------------------------------------------------
|
||||
|
||||
# in copy mode, copying selection also copies to the OS clipboard
|
||||
# - true
|
||||
# - false (default)
|
||||
# on macOS, this requires installing reattach-to-user-namespace, see README.md
|
||||
# on Linux, this requires xsel or xclip
|
||||
tmux_conf_copy_to_os_clipboard=true
|
||||
|
||||
|
||||
# -- user customizations -------------------------------------------------------
|
||||
# this is the place to override or undo settings
|
||||
|
||||
# increase history size
|
||||
set -g history-limit 2048
|
||||
|
||||
# start with mouse mode enabled
|
||||
set -g mouse on
|
||||
|
||||
# force Vi mode
|
||||
# really you should export VISUAL or EDITOR environment variable, see manual
|
||||
#set -g status-keys vi
|
||||
#set -g mode-keys vi
|
||||
|
||||
# replace C-b by C-a instead of using both prefixes
|
||||
#set -gu prefix2
|
||||
#unbind C-a
|
||||
#unbind C-b
|
||||
#set -g prefix C-a
|
||||
#bind C-a send-prefix
|
||||
|
||||
# move status line to top
|
||||
#set -g status-position top
|
||||
|
||||
####### FULL CUSTOM ########
|
||||
|
||||
#bind '+' split-window -h
|
||||
#bind '#' split-window -v
|
||||
|
||||
bind -r '+' split-window -p 50 -h -c "#{pane_current_path}"# \; set -u pane-active-border-style
|
||||
bind -r '#' split-window -p 50 -v -c "#{pane_current_path}"# \; set -u pane-active-border-style
|
||||
|
||||
bind -n C-Left select-pane -L
|
||||
bind -n C-Right select-pane -R
|
||||
bind -n C-Up select-pane -U
|
||||
bind -n C-Down select-pane -D
|
||||
|
||||
bind -r Tab next-window
|
||||
#bind c new-window -c '#{pane_current_path}'
|
||||
bind Z resize-pane -Z
|
||||
|
||||
set -g mouse on
|
||||
|
||||
setw -g monitor-activity on
|
||||
set -g visual-activity on
|
||||
|
||||
set -g history-limit 2048
|
||||
set -s escape-time 50
|
||||
#set -g default-terminal "screen-256color"
|
||||
|
||||
set -g status-bg black
|
||||
set -g status-fg white
|
||||
|
||||
set -g window-status-current-style bg=white,fg=black,bold
|
||||
|
||||
#set -g status-justify centre
|
||||
set -g status-interval 60
|
||||
set -g status-left-length 30
|
||||
set -g status-left '#[fg=green][#S] #(whoami)@#(hostname) \uE0B0 #[bg=black] '
|
||||
set -g status-right '#[fg=yellow]#(cut -d " " -f 1-3 /proc/loadavg)#[default] #[fg=white]%H:%M#[default]'
|
||||
|
||||
#set-option -ga terminal-overrides ",xterm-256color:Tc"
|
||||
|
||||
###bind y run-shell "tmux show-buffer | xclip -sel clip -i" \; display-message "Copied tmux buffer to system clipboard"
|
||||
#bind -T copy-mode y send-keys -X copy-pipe-and-cancel "xsel -i -p && xsel -o -p | xsel -i -b"
|
||||
#bind C-y run "xsel -o | tmux load-buffer - ; tmux paste-buffer"
|
||||
|
||||
# -- custom variables ----------------------------------------------------------
|
||||
# to define a custom #{foo} variable, define a POSIX shell function between the
|
||||
# '# EOF' and the '# "$@"' lines. Please note that the opening brace { character
|
||||
# must be on the same line as the function name otherwise the parse won't detect
|
||||
# it.
|
||||
#
|
||||
# then, use #{foo} in e.g. the 'tmux_conf_theme_status_left' or the
|
||||
# 'tmux_conf_theme_status_right' variables.
|
||||
|
||||
# # /!\ do not remove the following line
|
||||
# EOF
|
||||
#
|
||||
# weather() {
|
||||
# curl -m 1 wttr.in?format=3 2>/dev/null
|
||||
# sleep 900 # sleep for 15 minutes, throttle network requests whatever the value of status-interval
|
||||
# }
|
||||
#
|
||||
# online() {
|
||||
# ping -t 1 -c 1 1.1.1.1 >/dev/null 2>&1 && printf '✔' || printf '✘'
|
||||
# }
|
||||
#
|
||||
# "$@"
|
||||
# # /!\ do not remove the previous line
|
||||
142
dotfiles/.zshrc
142
dotfiles/.zshrc
@ -1,142 +0,0 @@
|
||||
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
|
||||
source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
|
||||
fi
|
||||
export ZSH="$HOME/.oh-my-zsh"
|
||||
ZSH_THEME="powerlevel10k/powerlevel10k"
|
||||
HIST_STAMPS="dd.mm.yyyy"
|
||||
plugins=(git)
|
||||
source $ZSH/oh-my-zsh.sh
|
||||
autoload -U +X bashcompinit && bashcompinit
|
||||
[ -f /usr/share/bash-completion/completions/dnf ] && source /usr/share/bash-completion/completions/dnf
|
||||
|
||||
######################
|
||||
# User configuration #
|
||||
######################
|
||||
|
||||
export EDITOR='nvim'
|
||||
|
||||
# nullpoint custom fastfetch
|
||||
alias fastfetch='fastfetch --file-raw /etc/nullpoint-logo'
|
||||
|
||||
if [ $TILIX_ID ] || [ $VTE_VERSION ]; then
|
||||
[ -f /etc/profile.d/vte.sh ] && source /etc/profile.d/vte.sh
|
||||
fi
|
||||
|
||||
#export LS_COLORS='rs=0:no=00:mi=00:mh=00:ln=01;36:or=01;31:di=01;34:ow=04;01;34:st=34:tw=04;34:pi=01;33:so=01;33:do=01;33:bd=01;33:cd=01;33:su=01;35:sg=01;35:ca=01;35:ex=01;32:'
|
||||
|
||||
##########
|
||||
|
||||
# lang
|
||||
alias python='python3.13'
|
||||
alias pip='python -m pip'
|
||||
alias ptpython="python -m ptpython"
|
||||
alias ptpy="python -m ptpython"
|
||||
alias py13='python3.13'
|
||||
alias py9='python3.9'
|
||||
alias bpytop="python -m bpytop"
|
||||
|
||||
# python venv
|
||||
alias pvcreate="python -m venv .venv"
|
||||
alias pvactivate="source .venv/bin/activate"
|
||||
alias pvinstall="echo 'Creating Python-venv' && pvcreate && echo 'Entering venv' && pvactivate && echo 'Upgrading pip' && pip install pip --upgrade && echo 'Installing Dependencies' && pip install -r requirements.txt && echo 'Done.'"
|
||||
|
||||
# qol
|
||||
alias cls="clear"
|
||||
alias root="sudo su --shell /bin/zsh"
|
||||
alias open="xdg-open"
|
||||
alias filenum="du -a | cut -d/ -f2 | sort | uniq -c | sort -nr"
|
||||
alias pg="progress"
|
||||
alias hist="history"
|
||||
alias untar="tar -zxvf"
|
||||
alias neofetch=fastfetch
|
||||
|
||||
# grep
|
||||
alias antigrep="grep -v"
|
||||
alias grep2="grep -A 2 -B 2"
|
||||
alias grep4="grep -A 4 -B 4"
|
||||
alias grep8="grep -A 8 -B 8"
|
||||
alias grep16="grep -A 16 -B 16"
|
||||
alias grep32="grep -A 32 -B 32"
|
||||
alias ag="alias | grep"
|
||||
|
||||
# packages
|
||||
alias get="sudo dnf install"
|
||||
alias upd="sudo dnf check-upgrade"
|
||||
alias upg="sudo dnf upgrade"
|
||||
|
||||
# chars
|
||||
alias c="clear"
|
||||
alias t="bpytop"
|
||||
alias p="ptpy"
|
||||
alias h="history | grep"
|
||||
alias v="nvim"
|
||||
alias n="nano"
|
||||
alias o="open"
|
||||
alias e="\d"
|
||||
alias ":q"="exit"
|
||||
|
||||
# git
|
||||
alias gs="git status"
|
||||
alias gt="git log --all --graph --decorate --oneline --abbrev-commit"
|
||||
alias gnoc='git shortlog -sn | cat' # Number Of Commit by user
|
||||
alias gloc='git ls-files | while read f; do git blame -w -M -C -C --line-porcelain "$f" | grep -I "^author "; done | sort -f | uniq -ic | sort -n --reverse' # Lines Of Code by user (in final version)
|
||||
alias gpa='gaa && gc -m "." && gp -4'
|
||||
|
||||
# dirs
|
||||
alias tmp="cd ~/.tmp"
|
||||
|
||||
# extensions
|
||||
alias sudo="sudo "
|
||||
alias watch="watch "
|
||||
alias watch1="watch -n 1 "
|
||||
|
||||
# better ls via lsd
|
||||
alias ls="lsd"
|
||||
alias l="ls -1"
|
||||
alias la="ls -A"
|
||||
alias laa="ls -a"
|
||||
alias lsa="ls -lA"
|
||||
alias lla="ls -lA"
|
||||
alias ltree="ls --tree"
|
||||
alias d0="ls --tree"
|
||||
alias d="ls --depth 1 --tree"
|
||||
alias d1="ls --depth 1 --tree"
|
||||
alias d2="ls --depth 2 --tree"
|
||||
alias d3="ls --depth 3 --tree"
|
||||
alias d4="ls --depth 4 --tree"
|
||||
alias d5="ls --depth 5 --tree"
|
||||
alias d6="ls --depth 6 --tree"
|
||||
|
||||
# more ls
|
||||
alias lo="\ls --color=tty"
|
||||
alias dtree="tree --du -h"
|
||||
alias gtree="tree --du -h -F -C | grep 'G] ' --color=never"
|
||||
|
||||
# better cat via batcat
|
||||
alias bat="bat --pager ''"
|
||||
alias pat="bat --pager '' --plain"
|
||||
alias lat="bat"
|
||||
|
||||
# more fancy batcat stuff
|
||||
alias man="batman"
|
||||
|
||||
# tmux
|
||||
alias s="tmux"
|
||||
alias sa="tmux attach"
|
||||
alias sn="tmux new -s"
|
||||
alias san="tmux attach -t"
|
||||
alias sl="tmux ls"
|
||||
alias sk="tmux kill-session -t"
|
||||
alias ska="tmux kill-session -a"
|
||||
|
||||
#####
|
||||
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
# Enable bash completion compatibility in zsh
|
||||
autoload -U +X bashcompinit && bashcompinit
|
||||
autoload -U +X compinit && compinit
|
||||
|
||||
# To customize prompt, run `p10k configure` or edit ~/.p10k.zsh.
|
||||
[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
|
||||
|
||||
14
get.sh
14
get.sh
@ -1,14 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Simple installer for nullpoint - just downloads and runs
|
||||
# Usage: wget -qO- https://git.dominik-roth.eu/dodox/nullpoint/raw/branch/master/get.sh | bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Clean up any existing directory
|
||||
[ -d "/tmp/nullpoint_installer" ] && rm -rf /tmp/nullpoint_installer
|
||||
|
||||
# Clone and run
|
||||
echo "[+] Downloading nullpoint installer..."
|
||||
git clone https://git.dominik-roth.eu/dodox/nullpoint.git /tmp/nullpoint_installer
|
||||
cd /tmp/nullpoint_installer
|
||||
exec bash install.sh
|
||||
@ -1,51 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
height="200px"
|
||||
width="200px"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
sodipodi:docname="icon_cluster.svg"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<sodipodi:namedview
|
||||
id="namedview3"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="5.59"
|
||||
inkscape:cx="100"
|
||||
inkscape:cy="100"
|
||||
inkscape:window-width="3440"
|
||||
inkscape:window-height="1371"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg3" />
|
||||
<path
|
||||
d="M12 3c-1.333 1 -2 2.5 -2 4.5c0 3 2 4.5 2 4.5s2 1.5 2 4.5c0 2 -.667 3.5 -2 4.5"
|
||||
id="path1"
|
||||
style="stroke:#ffffff;stroke-opacity:1;fill:none;fill-opacity:1" />
|
||||
<path
|
||||
d="M19.794 16.5c-.2 -1.655 -1.165 -2.982 -2.897 -3.982c-2.597 -1.5 -4.897 -.518 -4.897 -.518s-2.299 .982 -4.897 -.518c-1.732 -1 -2.698 -2.327 -2.897 -3.982"
|
||||
id="path2"
|
||||
style="stroke:#ffffff;stroke-opacity:1" />
|
||||
<path
|
||||
d="M19.794 7.5c-1.532 -.655 -3.165 -.482 -4.897 .518c-2.597 1.5 -2.897 3.982 -2.897 3.982s-.299 2.482 -2.897 3.982c-1.732 1 -3.365 1.173 -4.897 .518"
|
||||
id="path3"
|
||||
style="stroke:#ffffff;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
13
install.conf
13
install.conf
@ -1,13 +0,0 @@
|
||||
DRIVE1 /dev/nvme0n1
|
||||
DRIVE2 /dev/nvme1n1
|
||||
HOSTNAME nullpoint
|
||||
BOOTLOADER grub
|
||||
SWRAID 1
|
||||
SWRAIDLEVEL 1
|
||||
FILESYSTEM xfs
|
||||
IMAGE /root/images/Alma-9-latest-amd64-base.tar.gz
|
||||
CRYPTPASSWORD changeme
|
||||
|
||||
PART /boot/efi esp 256M
|
||||
PART /boot ext4 2G
|
||||
PART / xfs all crypt
|
||||
252
install.sh
252
install.sh
@ -1,252 +0,0 @@
|
||||
#!/bin/bash
|
||||
# nullpoint installer - run this from Hetzner rescue mode
|
||||
# Use get.sh for wget piping: wget -qO- https://git.dominik-roth.eu/dodox/nullpoint/raw/branch/master/get.sh | bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BANNER=$(cat << "EOF"
|
||||
__.,,.__
|
||||
:^7J5GB##&&##GPY?~:
|
||||
^75B&@@@@@@&&&@@@@@@@#GJ~:
|
||||
5&@@@&B5?7~^^^^^~!7YP#@@@@#!
|
||||
Y##P7^ :~JB#B!
|
||||
:: :
|
||||
7PP?: :^~!!~^: :?PP7
|
||||
:B@@B: !5B&@@@@&B5! :#@@B:
|
||||
:!!: ^G@@@&BPPB@@@@G^ :!!:
|
||||
:B@@@5^ ^5@@@B:
|
||||
:7J7: !@@@# :&@@@~ :?J7:
|
||||
J@@@5 :#@@@Y: :Y@@@B: 5@@@J
|
||||
!@@@&^ ~B@@@&G55G&@@@B~ ~&@@@~
|
||||
5@@@G: :7P#@@@@@@#P7: :B@@@Y
|
||||
:P@@@B~ :~!77!~: ~B@@@P
|
||||
Y@@@&Y^ ^5@@@@J
|
||||
!G@@@&P7^ ^7P&@@@G~
|
||||
!P&@@@&B? :: ?B&@@@&P!
|
||||
^75#&&Y :P&&5: 5&&B57^
|
||||
:^^ :P&&5: ^^:
|
||||
^^
|
||||
|
||||
[nullpoint]
|
||||
EOF
|
||||
)
|
||||
|
||||
clear
|
||||
echo -e "\n$BANNER"
|
||||
echo -e "\n[+] nullpoint installer starting..."
|
||||
|
||||
# Check if we're in Hetzner rescue mode
|
||||
if [ ! -f /etc/hetzner-build ]; then
|
||||
echo "ERROR: This script must be run from Hetzner rescue mode!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get SSH key from current session
|
||||
echo "[+] Detecting SSH key from current session..."
|
||||
SSH_KEY=$(grep "^ssh-" ~/.ssh/authorized_keys | head -1)
|
||||
if [ -z "$SSH_KEY" ]; then
|
||||
echo "ERROR: No SSH key found in authorized_keys!"
|
||||
echo "Please enter your SSH public key:"
|
||||
read -r SSH_KEY
|
||||
if [ -z "$SSH_KEY" ]; then
|
||||
echo "SSH key is required!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo "Found SSH key: ${SSH_KEY:0:50}..."
|
||||
|
||||
# Ask for hostname
|
||||
echo -e "\n[+] Server configuration"
|
||||
read -r -p "Enter hostname [nullpoint]: " HOSTNAME < /dev/tty
|
||||
HOSTNAME=${HOSTNAME:-nullpoint}
|
||||
|
||||
# Ask for username
|
||||
read -r -p "Enter username for admin account [null]: " USERNAME < /dev/tty
|
||||
USERNAME=${USERNAME:-null}
|
||||
|
||||
# Generate secure LUKS passphrase
|
||||
echo -e "\n[+] Generating secure LUKS passphrase..."
|
||||
LUKS_PASS=$(openssl rand -base64 30)
|
||||
|
||||
echo -e "\n================================================"
|
||||
echo "LUKS PASSPHRASE (SAVE THIS!):"
|
||||
echo "$LUKS_PASS"
|
||||
echo "================================================"
|
||||
echo -e "\nPress Enter when you've saved the passphrase..."
|
||||
read -r < /dev/tty
|
||||
|
||||
# Check if we're already in the nullpoint directory or need to use it
|
||||
if [ -f "install.conf" ] && [ -f "post-install.sh" ]; then
|
||||
echo "[+] Using local nullpoint files..."
|
||||
else
|
||||
# Clean up any existing directory
|
||||
if [ -d "/tmp/nullpoint" ]; then
|
||||
rm -rf /tmp/nullpoint
|
||||
fi
|
||||
echo "[+] Downloading nullpoint configuration..."
|
||||
git clone https://git.dominik-roth.eu/dodox/nullpoint.git /tmp/nullpoint
|
||||
cd /tmp/nullpoint
|
||||
fi
|
||||
|
||||
echo "[+] Configuring installation..."
|
||||
|
||||
# Auto-detect drives
|
||||
echo "[+] Detecting drives..."
|
||||
DRIVES=($(lsblk -dno NAME,TYPE | grep disk | awk '{print "/dev/"$1}' | sort))
|
||||
if [ ${#DRIVES[@]} -lt 2 ]; then
|
||||
echo "ERROR: Need at least 2 drives for RAID1, found ${#DRIVES[@]}"
|
||||
exit 1
|
||||
fi
|
||||
DRIVE1="${DRIVES[0]}"
|
||||
DRIVE2="${DRIVES[1]}"
|
||||
echo " Found drives: $DRIVE1 and $DRIVE2"
|
||||
# Update install.conf with detected drives
|
||||
if ! sed -i "s|^DRIVE1 .*|DRIVE1 $DRIVE1|" install.conf; then
|
||||
echo "ERROR: Failed to update DRIVE1 in install.conf"
|
||||
exit 1
|
||||
fi
|
||||
if ! sed -i "s|^DRIVE2 .*|DRIVE2 $DRIVE2|" install.conf; then
|
||||
echo "ERROR: Failed to update DRIVE2 in install.conf"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update hostname
|
||||
if ! sed -i "s/^HOSTNAME .*/HOSTNAME $HOSTNAME/" install.conf; then
|
||||
echo "ERROR: Failed to update HOSTNAME in install.conf"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use awk for CRYPTPASSWORD to handle special characters
|
||||
if ! awk -v pass="$LUKS_PASS" '
|
||||
/^CRYPTPASSWORD / { print "CRYPTPASSWORD " pass; next }
|
||||
{ print }
|
||||
' install.conf > install.conf.tmp; then
|
||||
echo "ERROR: Failed to update CRYPTPASSWORD in install.conf"
|
||||
exit 1
|
||||
fi
|
||||
mv install.conf.tmp install.conf
|
||||
|
||||
# Update post-install.sh
|
||||
if ! sed -i "s/^ALMA_USER=.*/ALMA_USER=\"$USERNAME\"/" post-install.sh; then
|
||||
echo "ERROR: Failed to update ALMA_USER in post-install.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use awk to replace SSH_KEY line to avoid sed issues with special characters
|
||||
if ! awk -v key="$SSH_KEY" '
|
||||
/^SSH_KEY=/ { print "SSH_KEY=\"" key "\""; next }
|
||||
{ print }
|
||||
' post-install.sh > post-install.sh.tmp; then
|
||||
echo "ERROR: Failed to update SSH_KEY in post-install.sh"
|
||||
exit 1
|
||||
fi
|
||||
mv post-install.sh.tmp post-install.sh
|
||||
|
||||
# Copy to root directory where installimage expects them
|
||||
if ! cp install.conf /root/; then
|
||||
echo "ERROR: Failed to copy install.conf to /root/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! cp post-install.sh /root/; then
|
||||
echo "ERROR: Failed to copy post-install.sh to /root/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod +x /root/post-install.sh
|
||||
|
||||
# Ask for optional features
|
||||
echo -e "\n[+] Optional features:"
|
||||
read -r -p "Do you have a TPM and want to use it? [y/N]: " USE_TPM < /dev/tty
|
||||
if [[ "$USE_TPM" =~ ^[Yy]$ ]]; then
|
||||
echo "TPM will be configured if available."
|
||||
else
|
||||
sed -i 's/^TPM_ENABLED=.*/TPM_ENABLED=false/' /root/post-install.sh
|
||||
fi
|
||||
|
||||
read -r -p "Do you want to configure remote unlock Tang servers? [y/N]: " USE_TANG < /dev/tty
|
||||
if [[ "$USE_TANG" =~ ^[Yy]$ ]]; then
|
||||
echo "Configuring Tang servers..."
|
||||
TANG_CONFIG=""
|
||||
|
||||
while true; do
|
||||
read -r -p "Enter Tang server URL (or press Enter to finish): " TANG_URL < /dev/tty
|
||||
if [ -z "$TANG_URL" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
read -r -p "Enter thumbprint for $TANG_URL: " TANG_THUMBPRINT < /dev/tty
|
||||
if [ -n "$TANG_THUMBPRINT" ]; then
|
||||
TANG_CONFIG+=" \"$TANG_URL $TANG_THUMBPRINT\"\n"
|
||||
echo "Added Tang server: $TANG_URL"
|
||||
else
|
||||
echo "Skipping server (no thumbprint provided)"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$TANG_CONFIG" ]; then
|
||||
# Update the TANG_SERVERS array in post-install.sh
|
||||
sed -i '/^TANG_SERVERS=(/,/^)/ {
|
||||
/^TANG_SERVERS=(/ {
|
||||
r /dev/stdin
|
||||
d
|
||||
}
|
||||
/^)/ !d
|
||||
}' /root/post-install.sh << EOF
|
||||
TANG_SERVERS=(
|
||||
$TANG_CONFIG)
|
||||
EOF
|
||||
echo "Configured Tang servers in post-install script."
|
||||
else
|
||||
echo "No Tang servers configured."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Final confirmation
|
||||
echo -e "\n[+] Ready to install with these settings:"
|
||||
echo " Hostname: $HOSTNAME"
|
||||
echo " Username: $USERNAME"
|
||||
echo " SSH Key: ${SSH_KEY:0:50}..."
|
||||
echo " LUKS Passphrase: $LUKS_PASS"
|
||||
echo ""
|
||||
read -r -p "Proceed with installation? [Y/n]: " CONFIRM < /dev/tty
|
||||
if [[ "$CONFIRM" =~ ^[Nn]$ ]]; then
|
||||
echo "Installation cancelled."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if installimage exists
|
||||
if ! command -v installimage &> /dev/null; then
|
||||
echo "ERROR: installimage command not found!"
|
||||
echo "This might not be a Hetzner rescue system, or installimage is not in PATH."
|
||||
echo "On Hetzner rescue, installimage is usually at /root/.oldroot/nfs/install/installimage"
|
||||
|
||||
# Try common locations
|
||||
if [ -x "/root/.oldroot/nfs/install/installimage" ]; then
|
||||
echo "Found installimage at /root/.oldroot/nfs/install/installimage"
|
||||
INSTALLIMAGE_CMD="/root/.oldroot/nfs/install/installimage"
|
||||
else
|
||||
echo "Could not find installimage. Please check your Hetzner rescue environment."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
INSTALLIMAGE_CMD="installimage"
|
||||
fi
|
||||
|
||||
# Run the installer
|
||||
echo -e "\n[+] Starting Hetzner installimage..."
|
||||
echo "The installer will now run. Follow any prompts if needed."
|
||||
echo ""
|
||||
if ! $INSTALLIMAGE_CMD -a -c /root/install.conf -x /root/post-install.sh; then
|
||||
echo -e "\nERROR: Installation failed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "\n[+] Installation complete!"
|
||||
echo ""
|
||||
echo "IMPORTANT REMINDERS:"
|
||||
echo "1. Save your LUKS passphrase securely!"
|
||||
echo "2. After reboot, you'll need to enter it twice (once per disk)"
|
||||
echo "3. SSH to the server as user '$USERNAME'"
|
||||
echo ""
|
||||
echo "The system is ready for use!"
|
||||
554
post-install.sh
554
post-install.sh
@ -1,554 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
BANNER=$(cat << "EOF"
|
||||
__.,,.__
|
||||
:^7J5GB##&&##GPY?~:
|
||||
^75B&@@@@@@&&&@@@@@@@#GJ~:
|
||||
5&@@@&B5?7~^^^^^~!7YP#@@@@#!
|
||||
Y##P7^ :~JB#B!
|
||||
:: :
|
||||
7PP?: :^~!!~^: :?PP7
|
||||
:B@@B: !5B&@@@@&B5! :#@@B:
|
||||
:!!: ^G@@@&BPPB@@@@G^ :!!:
|
||||
:B@@@5^ ^5@@@B:
|
||||
:7J7: !@@@# :&@@@~ :?J7:
|
||||
J@@@5 :#@@@Y: :Y@@@B: 5@@@J
|
||||
!@@@&^ ~B@@@&G55G&@@@B~ ~&@@@~
|
||||
5@@@G: :7P#@@@@@@#P7: :B@@@Y
|
||||
:P@@@B~ :~!77!~: ~B@@@P
|
||||
Y@@@&Y^ ^5@@@@J
|
||||
!G@@@&P7^ ^7P&@@@G~
|
||||
!P&@@@&B? :: ?B&@@@&P!
|
||||
^75#&&Y :P&&5: 5&&B57^
|
||||
:^^ :P&&5: ^^:
|
||||
^^
|
||||
|
||||
[nullpoint]
|
||||
EOF
|
||||
)
|
||||
|
||||
TANG_SERVERS=(
|
||||
# "https://tang1.example.com your-thumbprint-1"
|
||||
# "https://tang2.example.com your-thumbprint-2"
|
||||
)
|
||||
TPM_ENABLED=true
|
||||
TPM_PCR_BANK="sha256"
|
||||
TPM_PCR_IDS="0,1,2,3,4,5,6,7,8"
|
||||
ALMA_USER="null"
|
||||
ENABLE_MOTD=true
|
||||
# REQUIRED: Set your SSH public key here - installation will fail without it!
|
||||
SSH_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOkoTn2NreAXMriOUqzyj3YoFW6jMo9B5B+3R5k8yrMi dodox@dodox-ProArt"
|
||||
|
||||
########################################################
|
||||
# Config End
|
||||
########################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo -e "\n$BANNER"
|
||||
echo -e "\n[+] Starting post-installation configuration..."
|
||||
|
||||
# Check for SSH key
|
||||
if [ -z "${SSH_KEY:-}" ]; then
|
||||
echo "ERROR: No SSH key configured!"
|
||||
echo "You must set SSH_KEY variable at the top of this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for TPM
|
||||
echo "[+] Checking for TPM..."
|
||||
if [ ! -d "/sys/class/tpm/tpm0" ]; then
|
||||
echo "WARNING: No TPM detected!"
|
||||
TPM_ENABLED=false
|
||||
else
|
||||
echo "TPM detected."
|
||||
TPM_ENABLED=true
|
||||
fi
|
||||
|
||||
# Upgrade system packages first
|
||||
echo "[+] Upgrading system packages..."
|
||||
dnf upgrade -y || echo "WARNING: System upgrade failed"
|
||||
|
||||
# Install basic packages
|
||||
echo "[+] Installing basic packages..."
|
||||
dnf install -y epel-release || exit 1
|
||||
dnf config-manager --set-enabled crb || exit 1
|
||||
dnf install -y zsh git wget curl || exit 1
|
||||
|
||||
# Create user and add SSH key
|
||||
echo "[+] Creating user ${ALMA_USER}..."
|
||||
useradd -m -G wheel -s /bin/zsh ${ALMA_USER} || exit 1
|
||||
mkdir -p /home/${ALMA_USER}/.ssh
|
||||
echo "${SSH_KEY}" > /home/${ALMA_USER}/.ssh/authorized_keys
|
||||
chmod 700 /home/${ALMA_USER}/.ssh
|
||||
chmod 600 /home/${ALMA_USER}/.ssh/authorized_keys
|
||||
chown -R ${ALMA_USER}:${ALMA_USER} /home/${ALMA_USER}/.ssh
|
||||
|
||||
# Configure passwordless sudo
|
||||
echo "[+] Configuring passwordless sudo for ${ALMA_USER}..."
|
||||
echo "${ALMA_USER} ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/99-${ALMA_USER}
|
||||
chmod 440 /etc/sudoers.d/99-${ALMA_USER}
|
||||
|
||||
# Setup terminal environment for both user and root
|
||||
echo "[+] Setting up terminal environment for ${ALMA_USER} and root..."
|
||||
|
||||
for user_account in "${ALMA_USER}" "root"; do
|
||||
echo " - Setting up $user_account..."
|
||||
|
||||
if [ "$user_account" = "root" ]; then
|
||||
home_dir="/root"
|
||||
user_prefix=""
|
||||
else
|
||||
home_dir="/home/${user_account}"
|
||||
user_prefix="su - ${user_account} -c"
|
||||
fi
|
||||
|
||||
# Install oh-my-zsh
|
||||
if [ "$user_account" = "root" ]; then
|
||||
export RUNZSH=no CHSH=no KEEP_ZSHRC=yes && bash -c "$(wget -O- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" 2>/dev/null || echo "WARNING: $user_account oh-my-zsh installation failed"
|
||||
else
|
||||
su - ${user_account} -c 'export RUNZSH=no CHSH=no KEEP_ZSHRC=yes && bash -c "$(wget -O- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"' 2>/dev/null || echo "WARNING: $user_account oh-my-zsh installation failed"
|
||||
fi
|
||||
|
||||
# Install powerlevel10k
|
||||
if [ "$user_account" = "root" ]; then
|
||||
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${home_dir}/.oh-my-zsh/custom/themes/powerlevel10k 2>/dev/null || echo "WARNING: $user_account powerlevel10k installation failed"
|
||||
else
|
||||
su - ${user_account} -c 'git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/.oh-my-zsh/custom/themes/powerlevel10k' 2>/dev/null || echo "WARNING: $user_account powerlevel10k installation failed"
|
||||
fi
|
||||
|
||||
# Install dotfiles
|
||||
if [ "$user_account" = "root" ]; then
|
||||
cd ${home_dir} &&
|
||||
git clone https://git.dominik-roth.eu/dodox/nullpoint.git /tmp/nullpoint-dotfiles-${user_account} &&
|
||||
cd /tmp/nullpoint-dotfiles-${user_account}/dotfiles &&
|
||||
for file in .*; do
|
||||
if [ -f "$file" ] && [ "$file" != "." ] && [ "$file" != ".." ]; then
|
||||
cp "$file" ${home_dir}/ 2>/dev/null || true
|
||||
fi
|
||||
done &&
|
||||
cd ${home_dir} && rm -rf /tmp/nullpoint-dotfiles-${user_account} || echo "WARNING: $user_account dotfiles installation failed"
|
||||
else
|
||||
su - ${user_account} -c "
|
||||
cd &&
|
||||
git clone https://git.dominik-roth.eu/dodox/nullpoint.git /tmp/nullpoint-dotfiles-${user_account} &&
|
||||
cd /tmp/nullpoint-dotfiles-${user_account}/dotfiles &&
|
||||
for file in .*; do
|
||||
if [ -f \"\$file\" ] && [ \"\$file\" != \".\" ] && [ \"\$file\" != \"..\" ]; then
|
||||
cp \"\$file\" ~/ 2>/dev/null || true
|
||||
fi
|
||||
done &&
|
||||
cd && rm -rf /tmp/nullpoint-dotfiles-${user_account}
|
||||
" || echo "WARNING: $user_account dotfiles installation failed"
|
||||
fi
|
||||
|
||||
# Set zsh as default shell
|
||||
if [ "$user_account" = "root" ]; then
|
||||
sed -i 's|^root:.*:/bin/bash$|root:x:0:0:root:/root:/bin/zsh|' /etc/passwd
|
||||
else
|
||||
sed -i "s|^${user_account}:.*:/bin/bash$|${user_account}:x:$(id -u ${user_account}):$(id -g ${user_account})::/home/${user_account}:/bin/zsh|" /etc/passwd
|
||||
fi
|
||||
done
|
||||
|
||||
# Set up MOTD
|
||||
if [ "$ENABLE_MOTD" = true ]; then
|
||||
echo "[+] Setting up MOTD..."
|
||||
cat > /etc/motd << MOTD
|
||||
|
||||
$BANNER
|
||||
MOTD
|
||||
fi
|
||||
|
||||
# Modify /etc/os-release to show nullpoint branding
|
||||
echo "[+] Updating /etc/os-release with nullpoint branding..."
|
||||
if [ -f /etc/os-release ]; then
|
||||
# Backup original
|
||||
cp /etc/os-release /etc/os-release.bak
|
||||
|
||||
# Get original PRETTY_NAME value
|
||||
ORIG_PRETTY_NAME=$(grep '^PRETTY_NAME=' /etc/os-release | cut -d'"' -f2)
|
||||
|
||||
# Update PRETTY_NAME to show nullpoint with base OS info
|
||||
sed -i "s/^PRETTY_NAME=.*/PRETTY_NAME=\"nullpoint (base: ${ORIG_PRETTY_NAME})\"/" /etc/os-release
|
||||
|
||||
echo " - Updated PRETTY_NAME to 'nullpoint (base: ${ORIG_PRETTY_NAME})'"
|
||||
fi
|
||||
|
||||
# Install additional packages
|
||||
echo "[+] Installing additional packages..."
|
||||
dnf install -y \
|
||||
clevis clevis-luks tpm2-tools tpm2-tss \
|
||||
tmux neovim python3-pip python3.13 python3.13-pip \
|
||||
tree gcc make autoconf automake tar bzip2 \
|
||||
bash-completion || exit 1
|
||||
|
||||
|
||||
# Install dropbear for early boot SSH
|
||||
echo "[+] Installing dropbear for early boot SSH..."
|
||||
dnf install -y dropbear dracut-network || exit 1
|
||||
|
||||
# Install modern CLI tools
|
||||
echo "[+] Installing lsd, bat, and fastfetch..."
|
||||
|
||||
# Try to install fastfetch from repos first
|
||||
dnf install -y fastfetch || echo "fastfetch not available in repos, skipping"
|
||||
|
||||
# Create nullpoint logo file for fastfetch
|
||||
echo "[+] Creating nullpoint logo file..."
|
||||
cat > /etc/nullpoint-logo << 'EOF'
|
||||
__.,,.__
|
||||
:^7J5GB##&&##GPY?~:
|
||||
^75B&@@@@@@&&&@@@@@@@#GJ~:
|
||||
5&@@@&B5?7~^^^^^~!7YP#@@@@#!
|
||||
Y##P7^ :~JB#B!
|
||||
:: :
|
||||
7PP?: :^~!!~^: :?PP7
|
||||
:B@@B: !5B&@@@@&B5! :#@@B:
|
||||
:!!: ^G@@@&BPPB@@@@G^ :!!:
|
||||
:B@@@5^ ^5@@@B:
|
||||
:7J7: !@@@# :&@@@~ :?J7:
|
||||
J@@@5 :#@@@Y: :Y@@@B: 5@@@J
|
||||
!@@@&^ ~B@@@&G55G&@@@B~ ~&@@@~
|
||||
5@@@G: :7P#@@@@@@#P7: :B@@@Y
|
||||
:P@@@B~ :~!77!~: ~B@@@P
|
||||
Y@@@&Y^ ^5@@@@J
|
||||
!G@@@&P7^ ^7P&@@@G~
|
||||
!P&@@@&B? :: ?B&@@@&P!
|
||||
^75#&&Y :P&&5: 5&&B57^
|
||||
:^^ :P&&5: ^^:
|
||||
^^
|
||||
EOF
|
||||
|
||||
# Install user-specific tools for both user and root
|
||||
echo "[+] Installing user-specific tools..."
|
||||
# Install for user
|
||||
echo " - Installing for ${ALMA_USER}..."
|
||||
su - ${ALMA_USER} -c 'curl -fsSL https://claude.ai/install.sh | bash' || echo "WARNING: ${ALMA_USER} Claude Code installation failed"
|
||||
su - ${ALMA_USER} -c 'python3.13 -m pip install --user bpytop' || echo "WARNING: ${ALMA_USER} bpytop installation failed"
|
||||
|
||||
# Install for root
|
||||
echo " - Installing for root..."
|
||||
curl -fsSL https://claude.ai/install.sh | bash || echo "WARNING: root Claude Code installation failed"
|
||||
python3.13 -m pip install bpytop || echo "WARNING: root bpytop installation failed"
|
||||
|
||||
# Install Docker from official repository
|
||||
echo "[+] Installing Docker..."
|
||||
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo || echo "WARNING: Docker repo setup failed"
|
||||
dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin || echo "WARNING: Docker installation failed"
|
||||
systemctl enable docker || echo "WARNING: Docker enable failed"
|
||||
usermod -aG docker ${ALMA_USER} || echo "WARNING: Adding user to docker group failed"
|
||||
|
||||
# Install using fixed versions that should work
|
||||
LSD_VERSION="1.0.0"
|
||||
BAT_VERSION="0.24.0"
|
||||
|
||||
# Download and install lsd
|
||||
if curl -sL "https://github.com/lsd-rs/lsd/releases/download/v${LSD_VERSION}/lsd-v${LSD_VERSION}-x86_64-unknown-linux-musl.tar.gz" | tar xz -C /tmp; then
|
||||
mv /tmp/lsd-*/lsd /usr/local/bin/ 2>/dev/null || true
|
||||
chmod +x /usr/local/bin/lsd 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Download and install bat
|
||||
if curl -sL "https://github.com/sharkdp/bat/releases/download/v${BAT_VERSION}/bat-v${BAT_VERSION}-x86_64-unknown-linux-musl.tar.gz" | tar xz -C /tmp; then
|
||||
mv /tmp/bat-*/bat /usr/local/bin/ 2>/dev/null || true
|
||||
chmod +x /usr/local/bin/bat 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Create batman script for fancy man pages
|
||||
cat > /usr/local/bin/batman << 'BATMAN'
|
||||
#!/bin/bash
|
||||
export MANPAGER="sh -c 'col -bx | bat -l man -p'"
|
||||
export MANROFFOPT="-c"
|
||||
man "$@"
|
||||
BATMAN
|
||||
chmod +x /usr/local/bin/batman
|
||||
|
||||
# Create .tmp directory for user and root
|
||||
mkdir -p /home/${ALMA_USER}/.tmp
|
||||
chown ${ALMA_USER}:${ALMA_USER} /home/${ALMA_USER}/.tmp
|
||||
mkdir -p /root/.tmp
|
||||
|
||||
# Configure Clevis for automatic unlock
|
||||
if [ ${#TANG_SERVERS[@]} -gt 0 ] || [ "$TPM_ENABLED" = true ]; then
|
||||
echo "[+] Configuring Clevis for automatic unlock..."
|
||||
|
||||
# Find LUKS devices
|
||||
LUKS_DEVICES=$(lsblk -o NAME,FSTYPE -nr | grep crypto_LUKS | cut -d' ' -f1)
|
||||
|
||||
for device in $LUKS_DEVICES; do
|
||||
DEVICE_PATH="/dev/${device}"
|
||||
echo "Configuring Clevis for ${DEVICE_PATH}..."
|
||||
|
||||
if [ "$TPM_ENABLED" = true ] && [ ${#TANG_SERVERS[@]} -eq 0 ]; then
|
||||
# TPM only
|
||||
clevis luks bind -d "$DEVICE_PATH" tpm2 "{\"pcr_bank\":\"$TPM_PCR_BANK\",\"pcr_ids\":\"$TPM_PCR_IDS\"}"
|
||||
elif [ "$TPM_ENABLED" = false ] && [ ${#TANG_SERVERS[@]} -gt 0 ]; then
|
||||
# Tang only
|
||||
for server in "${TANG_SERVERS[@]}"; do
|
||||
read -r url thumbprint <<< "$server"
|
||||
clevis luks bind -d "$DEVICE_PATH" tang "{\"url\":\"$url\",\"thp\":\"$thumbprint\"}"
|
||||
done
|
||||
elif [ "$TPM_ENABLED" = true ] && [ ${#TANG_SERVERS[@]} -gt 0 ]; then
|
||||
# Both TPM and Tang (require both)
|
||||
CONFIG="{\"t\":2,\"pins\":{"
|
||||
CONFIG+="\"tpm2\":{\"pcr_bank\":\"$TPM_PCR_BANK\",\"pcr_ids\":\"$TPM_PCR_IDS\"},"
|
||||
CONFIG+="\"tang\":{\"t\":1,\"tang\":["
|
||||
for server in "${TANG_SERVERS[@]}"; do
|
||||
read -r url thumbprint <<< "$server"
|
||||
CONFIG+="{\"url\":\"$url\",\"thp\":\"$thumbprint\"},"
|
||||
done
|
||||
CONFIG="${CONFIG%,}]}}}"
|
||||
clevis luks bind -d "$DEVICE_PATH" sss "$CONFIG"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Enable Clevis for early boot (only needed for AlmaLinux < 8.7)
|
||||
echo "[+] Configuring Clevis for early boot..."
|
||||
OS_VERSION=$(cat /etc/redhat-release | grep -oE '[0-9]+\.[0-9]+' | head -1)
|
||||
if [[ "$(echo "$OS_VERSION < 8.7" | bc)" -eq 1 ]]; then
|
||||
echo " - Enabling clevis-luks-askpass.path for AlmaLinux $OS_VERSION"
|
||||
systemctl enable clevis-luks-askpass.path || true
|
||||
else
|
||||
echo " - AlmaLinux $OS_VERSION: clevis-luks-askpass.path not needed"
|
||||
fi
|
||||
|
||||
# Configure dropbear for remote unlock
|
||||
echo "[+] Configuring dropbear SSH for remote unlock..."
|
||||
|
||||
# Create custom dracut module for dropbear SSH
|
||||
mkdir -p /usr/lib/dracut/modules.d/60dropbear-ssh
|
||||
|
||||
# Create the module setup script
|
||||
cat > /usr/lib/dracut/modules.d/60dropbear-ssh/module-setup.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
check() {
|
||||
require_binaries dropbear dbclient dropbearkey dropbearconvert || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
depends() {
|
||||
echo network
|
||||
}
|
||||
|
||||
install() {
|
||||
inst_multiple dropbear dbclient dropbearkey dropbearconvert
|
||||
|
||||
# Create directories
|
||||
inst_dir /etc/dropbear
|
||||
inst_dir /var/log
|
||||
inst_dir /root/.ssh
|
||||
|
||||
# Copy authorized keys if they exist
|
||||
if [ -f /etc/dropbear/authorized_keys ]; then
|
||||
inst /etc/dropbear/authorized_keys /root/.ssh/authorized_keys
|
||||
fi
|
||||
|
||||
# Install ED25519 host key only
|
||||
keyfile="/etc/dropbear/dropbear_ed25519_host_key"
|
||||
if [ ! -f "$keyfile" ]; then
|
||||
dropbearkey -t ed25519 -f "$keyfile" 2>/dev/null
|
||||
fi
|
||||
[ -f "$keyfile" ] && inst "$keyfile"
|
||||
|
||||
# Install the service
|
||||
inst_simple "$moddir/dropbear.service" /etc/systemd/system/dropbear.service
|
||||
systemctl -q --root "$initdir" enable dropbear.service
|
||||
|
||||
# Install unlock helper
|
||||
inst_simple "$moddir/unlock-luks.sh" /usr/bin/unlock-luks
|
||||
chmod 755 "$initdir/usr/bin/unlock-luks"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create systemd service for dropbear
|
||||
cat > /usr/lib/dracut/modules.d/60dropbear-ssh/dropbear.service << 'EOF'
|
||||
[Unit]
|
||||
Description=Dropbear SSH Server
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
ExecStart=/usr/sbin/dropbear -R -E -p 22
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
KillMode=process
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=sysinit.target
|
||||
EOF
|
||||
|
||||
# Create unlock helper script
|
||||
cat > /usr/lib/dracut/modules.d/60dropbear-ssh/unlock-luks.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
echo "=== LUKS Remote Unlock Helper ==="
|
||||
echo ""
|
||||
echo "Checking for encrypted devices..."
|
||||
|
||||
# Show block devices if available
|
||||
if command -v lsblk >/dev/null 2>&1; then
|
||||
echo "Block devices:"
|
||||
lsblk -o NAME,SIZE,TYPE,FSTYPE 2>/dev/null || echo " (lsblk not available)"
|
||||
else
|
||||
echo "Block devices: (listing /dev/sd* and /dev/md*)"
|
||||
ls -la /dev/sd* /dev/md* 2>/dev/null || echo " No standard devices found"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Encrypted devices status:"
|
||||
# Check for LUKS devices waiting to be unlocked
|
||||
for dev in /dev/mapper/luks-*; do
|
||||
if [ -e "$dev" ]; then
|
||||
echo " Found: $dev"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check systemd-ask-password files directly
|
||||
if [ -d /run/systemd/ask-password ]; then
|
||||
echo ""
|
||||
echo "Password prompts waiting:"
|
||||
ls -la /run/systemd/ask-password/ 2>/dev/null
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Starting unlock process..."
|
||||
echo "Enter your LUKS passphrase when prompted:"
|
||||
echo ""
|
||||
|
||||
# Run the password agent
|
||||
if command -v systemd-tty-ask-password-agent >/dev/null 2>&1; then
|
||||
systemd-tty-ask-password-agent
|
||||
else
|
||||
echo "ERROR: systemd-tty-ask-password-agent not found!"
|
||||
echo "Try running: /lib/systemd/systemd-tty-ask-password-agent"
|
||||
fi
|
||||
EOF
|
||||
|
||||
chmod +x /usr/lib/dracut/modules.d/60dropbear-ssh/*.sh
|
||||
|
||||
# Setup dropbear authorized keys
|
||||
mkdir -p /etc/dropbear
|
||||
echo "${SSH_KEY}" > /etc/dropbear/authorized_keys
|
||||
chmod 600 /etc/dropbear/authorized_keys
|
||||
|
||||
# Generate ED25519 host key only (most secure)
|
||||
echo "[+] Generating ED25519 SSH host key..."
|
||||
|
||||
# Use system SSH key if available, otherwise generate dropbear key
|
||||
openssh_key="/etc/ssh/ssh_host_ed25519_key"
|
||||
dropbear_key="/etc/dropbear/dropbear_ed25519_host_key"
|
||||
|
||||
if [ -f "$openssh_key" ] && command -v dropbearconvert >/dev/null 2>&1; then
|
||||
echo " Converting existing OpenSSH ED25519 key to dropbear format..."
|
||||
dropbearconvert openssh dropbear "$openssh_key" "$dropbear_key" 2>/dev/null || {
|
||||
echo " Conversion failed, generating new dropbear key..."
|
||||
dropbearkey -t ed25519 -f "$dropbear_key" | grep -v "Generating" || true
|
||||
}
|
||||
elif [ ! -f "$dropbear_key" ]; then
|
||||
echo " Generating new ED25519 key..."
|
||||
dropbearkey -t ed25519 -f "$dropbear_key" | grep -v "Generating" || true
|
||||
|
||||
# Also generate OpenSSH format to prevent key mismatch after boot
|
||||
if command -v ssh-keygen >/dev/null 2>&1; then
|
||||
echo " Generating matching OpenSSH key..."
|
||||
mkdir -p /etc/ssh
|
||||
# Extract public key and generate OpenSSH private key
|
||||
dropbearkey -y -f "$dropbear_key" | grep "^ssh-" > "${openssh_key}.pub"
|
||||
# Note: Direct conversion from dropbear to openssh private key requires dropbearconvert
|
||||
# For now, we'll have different keys but document the solution
|
||||
fi
|
||||
fi
|
||||
|
||||
# Display SHA256 fingerprint
|
||||
if command -v ssh-keygen >/dev/null 2>&1; then
|
||||
fingerprint=$(dropbearkey -y -f "$dropbear_key" | ssh-keygen -lf - -E sha256 2>/dev/null | awk '{print $2}')
|
||||
if [ -n "$fingerprint" ]; then
|
||||
echo " SHA256 fingerprint: $fingerprint"
|
||||
echo " Note: This is the initramfs (rescue) SSH fingerprint."
|
||||
echo " The normal system SSH may have a different fingerprint."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Configure dracut
|
||||
cat > /etc/dracut.conf.d/60-dropbear-ssh.conf << 'EOF'
|
||||
# Enable network and dropbear SSH
|
||||
add_dracutmodules+=" network dropbear-ssh "
|
||||
# Network configuration
|
||||
kernel_cmdline="rd.neednet=1"
|
||||
EOF
|
||||
|
||||
# Regenerate initramfs
|
||||
echo "[+] Regenerating initramfs..."
|
||||
dracut -f --regenerate-all
|
||||
|
||||
# Enable required services
|
||||
echo "[+] Enabling services..."
|
||||
# systemctl enable stratisd # Not needed without Stratis
|
||||
systemctl enable sshd
|
||||
|
||||
# Secure SSH configuration
|
||||
echo "[+] Securing SSH..."
|
||||
{
|
||||
# Disable root login
|
||||
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
|
||||
|
||||
# Only allow SSH key authentication
|
||||
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
|
||||
sed -i 's/^#*ChallengeResponseAuthentication.*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
|
||||
sed -i 's/^#*UsePAM.*/UsePAM no/' /etc/ssh/sshd_config
|
||||
|
||||
# Only allow specific user
|
||||
echo "AllowUsers ${ALMA_USER}" >> /etc/ssh/sshd_config
|
||||
|
||||
echo " - Root login disabled"
|
||||
echo " - Password authentication disabled"
|
||||
echo " - Only user '${ALMA_USER}' allowed"
|
||||
}
|
||||
|
||||
# Set SELinux to enforcing
|
||||
echo "[+] Setting SELinux to enforcing..."
|
||||
sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
|
||||
|
||||
echo "✅ Post-installation complete!"
|
||||
echo ""
|
||||
|
||||
# Display SSH host key fingerprints
|
||||
echo "SSH Host Key Fingerprints:"
|
||||
ed25519_fp=""
|
||||
dropbear_fp=""
|
||||
|
||||
if [ -f "/etc/ssh/ssh_host_ed25519_key.pub" ] && command -v ssh-keygen >/dev/null 2>&1; then
|
||||
ed25519_fp=$(ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub -E sha256 2>/dev/null | awk '{print $2}')
|
||||
fi
|
||||
|
||||
if [ -f "/etc/dropbear/dropbear_ed25519_host_key" ] && command -v ssh-keygen >/dev/null 2>&1; then
|
||||
dropbear_fp=$(dropbearkey -y -f /etc/dropbear/dropbear_ed25519_host_key 2>/dev/null | ssh-keygen -lf - -E sha256 2>/dev/null | awk '{print $2}')
|
||||
fi
|
||||
|
||||
if [ -n "$ed25519_fp" ] && [ -n "$dropbear_fp" ] && [ "$ed25519_fp" = "$dropbear_fp" ]; then
|
||||
echo " SSH (ED25519): $ed25519_fp (same for both rescue and normal)"
|
||||
elif [ -n "$ed25519_fp" ] && [ -n "$dropbear_fp" ]; then
|
||||
echo " Normal SSH (ED25519): $ed25519_fp"
|
||||
echo " Rescue SSH (ED25519): $dropbear_fp"
|
||||
elif [ -n "$ed25519_fp" ]; then
|
||||
echo " Normal SSH (ED25519): $ed25519_fp"
|
||||
elif [ -n "$dropbear_fp" ]; then
|
||||
echo " Rescue SSH (ED25519): $dropbear_fp"
|
||||
else
|
||||
echo " No ED25519 keys found"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "IMPORTANT: Save the LUKS passphrase from install.conf securely!"
|
||||
echo ""
|
||||
echo "Next Steps:"
|
||||
echo "1. Manually reboot the system when ready"
|
||||
echo "2. SSH root@<server-ip> → run 'unlock-luks' → enter passphrase"
|
||||
echo "3. System finalizes setup and reboots automatically"
|
||||
echo "4. SSH root@<server-ip> → run 'unlock-luks' → enter passphrase again"
|
||||
echo "5. System is ready → SSH as user '${ALMA_USER}'"
|
||||
echo ""
|
||||
echo "Future boots: Only one unlock needed (or automatic if Tang/Clevis configured)"
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
pyyaml>=6.0.1
|
||||
Loading…
Reference in New Issue
Block a user