diff --git a/hunter/ansible/playbook.yml b/hunter/ansible/playbook.yml index 14c7e83..a19890d 100644 --- a/hunter/ansible/playbook.yml +++ b/hunter/ansible/playbook.yml @@ -19,6 +19,12 @@ state: directory mode: '0755' + - name: Create pickipedia backups directory (synced from maybelle) + file: + path: /opt/magenta/pickipedia-backups + state: directory + mode: '0755' + - name: Include Security role (SSH hardening, fail2ban, firewall) include_role: name: security diff --git a/hunter/ansible/roles/user-instance/templates/docker-compose.yml.j2 b/hunter/ansible/roles/user-instance/templates/docker-compose.yml.j2 index 070a7d0..410bae9 100644 --- a/hunter/ansible/roles/user-instance/templates/docker-compose.yml.j2 +++ b/hunter/ansible/roles/user-instance/templates/docker-compose.yml.j2 @@ -20,6 +20,8 @@ services: # SSH keys mounted separately (read-only for security) - {{ base_dir }}/{{ user.name }}/.ssh:/home/magent/.ssh:ro - /var/run/docker.sock:/var/run/docker.sock + # PickiPedia backups (synced from maybelle daily) + - /opt/magenta/pickipedia-backups:/opt/magenta/pickipedia-backups:ro networks: - magenta-net environment: diff --git a/hunter/container_startup.py b/hunter/container_startup.py index 0efe46a..f8fd727 100644 --- a/hunter/container_startup.py +++ b/hunter/container_startup.py @@ -131,6 +131,92 @@ def setup_workspace(): run_install=False ) + # Clone pickipedia (MediaWiki knowledge base) + ensure_repo_cloned( + "https://github.com/cryptograss/pickipedia.git", + workspace / "pickipedia", + user='magent', + run_install=False + ) + + # Create pickipedia config for local preview if it doesn't exist + pickipedia_dir = workspace / "pickipedia" + if pickipedia_dir.exists(): + # Create .env with user-specific settings + pickipedia_env = pickipedia_dir / ".env" + dev_name = os.environ.get('DEVELOPER_NAME', 'dev') + if not pickipedia_env.exists(): + pickipedia_env.write_text(f"""# PickiPedia local preview settings for {dev_name} +MEDIAWIKI_VERSION=1.43.0 +WIKI_PORT=4005 +COMPOSE_PROJECT_NAME=pickipedia-{dev_name} +DB_NAME=pickipedia +DB_USER=pickipedia +DB_PASSWORD=pickipedia_dev +DB_ROOT_PASSWORD=root_dev +""") + run_command(f"chown magent:magent {pickipedia_env}") + logger.info("✓ Created pickipedia .env for local preview") + + # Create LocalSettings.local.php for docker-compose preview + local_settings = pickipedia_dir / "LocalSettings.local.php" + if not local_settings.exists(): + local_settings.write_text("""/dev/null | head -1) + +if [ -z "$LATEST_BACKUP" ]; then + echo "No backup found in $BACKUP_DIR" + echo "Backups are created daily at 3:30am on maybelle" + exit 1 +fi + +echo "Loading backup: $LATEST_BACKUP" + +# Get container name from compose project +CONTAINER=$(docker ps --filter "name=pickipedia.*db" --format "{{.Names}}" | head -1) +if [ -z "$CONTAINER" ]; then + echo "MySQL container not running. Start with: docker-compose up -d" + exit 1 +fi + +# Wait for MySQL to be ready +echo "Waiting for MySQL..." +until docker exec "$CONTAINER" mysqladmin ping -h localhost --silent 2>/dev/null; do + sleep 1 +done + +# Load the backup +echo "Loading data (this may take a moment)..." +gunzip -c "$LATEST_BACKUP" | docker exec -i "$CONTAINER" mysql -u pickipedia -ppickipedia_dev pickipedia + +echo "Done! PickiPedia preview now has production data." +""") + run_command(f"chown magent:magent {load_backup_script}") + run_command(f"chmod +x {load_backup_script}") + logger.info("✓ Created pickipedia load-backup.sh script") + def setup_host_files(): """Set up SSH keys and other host-provided config.""" diff --git a/hunter/packages.txt b/hunter/packages.txt index bd63937..35ef2ea 100644 --- a/hunter/packages.txt +++ b/hunter/packages.txt @@ -39,3 +39,6 @@ tmux screen mosh imagemagick +jp2a +caca-utils +libaa-bin diff --git a/maybelle/ansible/maybelle.yml b/maybelle/ansible/maybelle.yml index b7e8ca2..462566d 100644 --- a/maybelle/ansible/maybelle.yml +++ b/maybelle/ansible/maybelle.yml @@ -339,6 +339,32 @@ owner: 1000 group: 1000 + - name: Create PickiPedia secrets directory + file: + path: "{{ jenkins_home }}/secrets/pickipedia" + state: directory + mode: '0755' + owner: 1000 + group: 1000 + + - name: Deploy PickiPedia LocalSettings.local.php from vault + copy: + content: | + > "$LOG_FILE" + + # Run mysqldump on NFS and pipe back + BACKUP_FILE="$BACKUP_DIR/pickipedia_$(date +%Y%m%d).sql.gz" + if ssh -i "$SSH_KEY" "${NFS_USER}@${NFS_HOST}" \ + "mysqldump -h pickipedia.db pickipedia" 2>> "$LOG_FILE" \ + | gzip > "$BACKUP_FILE"; then + echo "$(date): Backup successful: $BACKUP_FILE ($(stat -c%s "$BACKUP_FILE") bytes)" >> "$LOG_FILE" + # Keep last 7 days of backups + find "$BACKUP_DIR" -name "pickipedia_*.sql.gz" -mtime +7 -delete + + # Sync to hunter for preview environments + echo "$(date): Syncing to hunter..." >> "$LOG_FILE" + rsync -avz "$BACKUP_DIR"/ root@hunter.cryptograss.live:/opt/magenta/pickipedia-backups/ >> "$LOG_FILE" 2>&1 || \ + echo "$(date): WARNING - sync to hunter failed" >> "$LOG_FILE" + else + echo "$(date): Backup FAILED" >> "$LOG_FILE" + rm -f "$BACKUP_FILE" # Remove partial file + exit 1 + fi + + - name: Create pickipedia backup log file + file: + path: /var/log/pickipedia-backup.log + state: touch + mode: '0644' + + - name: Schedule daily pickipedia backup + ansible.builtin.cron: + name: "pickipedia backup from nfs" + hour: "3" + minute: "30" + job: "/usr/local/bin/backup-pickipedia.sh" + + - name: Add NFS pickipedia site to SSH config + blockinfile: + path: /root/.ssh/config + marker: "# {mark} PICKIPEDIA NFS" + block: | + Host nfs-pickipedia + HostName ssh.nyc1.nearlyfreespeech.net + IdentityFile ~/.ssh/id_ed25519_nfs + User jmyles_pickipedia + # Security: remove vault password after deploy completes # The encrypted vault.yml is in a public repo, so vault password on disk is a risk # Re-run chapter-1.sh to restore it if needed for future deploys diff --git a/maybelle/jobs/pickipedia.groovy b/maybelle/jobs/pickipedia.groovy new file mode 100644 index 0000000..1402e5f --- /dev/null +++ b/maybelle/jobs/pickipedia.groovy @@ -0,0 +1,20 @@ +pipelineJob('pickipedia') { + definition { + cpsScm { + scm { + git { + remote { + url('https://github.com/cryptograss/pickipedia.git') + credentials('github-token') + } + branch('*/main') + } + } + scriptPath('Jenkinsfile') + } + } + triggers { + // Poll every 5 minutes for changes + cron('*/5 * * * *') + } +}