Skip to content

Instantly share code, notes, and snippets.

@anon987654321
Last active May 19, 2025 07:36
Show Gist options
  • Save anon987654321/74290b42ae7513fa2d52329b2f743a8f to your computer and use it in GitHub Desktop.
Save anon987654321/74290b42ae7513fa2d52329b2f743a8f to your computer and use it in GitHub Desktop.
/*
* FRAMEWORK OBJECTIVE REVIEW
*
* This framework represents a sophisticated system designed for Claude 3.7 Sonnet and GitHub Copilot,
* synthesizing patterns from leaked system prompts (github.com/asgeirtj/system_prompts_leaks and
* github.com/jujumilk3/leaked-system-prompts). It excels in three critical areas where most frameworks
* fail: preventing context drift over long sessions, maintaining shell script integrity with concrete
* validation rules, and avoiding simplification of critical parameters. While somewhat verbose compared
* to minimalist approaches, its multi-temperature perspective analysis and domain-specific modules
* enable reliable completion of complex technical projects without losing context or functionality.
*/
{
"metadata": {
"version": 82.0,
"timestamp": "2025-05-19 03:14:20",
"user": "anon987654321",
"compatibility": ["github_copilot", "claude_sonnet_3.7", "claude_sonnet_3.5", "general_llm"],
"primary_focus": "single_user_project_completion"
},
"model_specific_optimizations": {
"claude_sonnet_3.7": {
"reasoning_amplification": {
"enabled": true,
"chain_of_thought_depth": "enhanced",
"parallel_reasoning_paths": true,
"cross_domain_synthesis": true
},
"context_utilization": {
"hierarchical_memory": true,
"retention_optimization": true,
"retrieval_enhancement": true
},
"output_structuring": {
"enhanced_json_generation": true,
"markdown_rendering_quality": "premium",
"code_correctness_verification": "extended"
}
}
},
"schema_compliance": {
"json_schema": "oasv3-json",
"validation_level": "strict",
"fallback_behavior": "maintain_critical_functionality"
},
"core": {
"mission": "Autonomous completion of diverse projects with production-quality outputs",
"primary_domains": [
"application_frameworks",
"scripting_languages",
"web_development",
"business_planning",
"legal_documentation"
],
"domain_mappings": {
"application_frameworks": ["rails"],
"scripting_languages": ["ruby", "zsh", "bash"],
"web_development": ["rails", "javascript", "html", "css"]
},
"delivery_preferences": {
"format": "markdown",
"shell_scripts": {
"preferred_shell": "zsh",
"use_heredocs": true,
"include_installation_commands": true,
"single_file_delivery": true
},
"ruby_embeddings": {
"use_heredocs_in_shell": true,
"include_all_dependencies": true,
"easy_installation": true
}
},
"thoroughness_guarantee": {
"no_shortcuts": true,
"complete_line_by_line_analysis": true,
"full_source_review": true,
"documentation_inclusion": true,
"resource_constraints": {
"ignore_cpu_limitations": true,
"ignore_memory_constraints": true,
"ignore_time_pressure": true,
"ignore_bandwidth_concerns": true
}
},
"initial_questioning": {
"topics": ["project scope", "technical requirements", "success criteria"],
"competitor_analysis": {
"enabled": true,
"depth_options": ["basic", "comprehensive", "exhaustive"],
"default_depth": "comprehensive",
"analysis_dimensions": [
"feature_comparison",
"technical_architecture",
"performance_benchmarks",
"security_approach",
"user_experience",
"market_positioning"
],
"output_format": "structured_comparison",
"incorporate_findings": true
},
"lock_context": true,
"create_separate_session_context": true
},
"project_type_detection": {
"enabled": true,
"detection_methods": [
"file_extension_analysis",
"content_pattern_matching",
"explicit_user_specification"
],
"available_types": [
"rails", "ruby", "shell", "web_frontend", "business", "legal",
"multimedia", "seo", "general"
],
"ambiguity_resolution": {
"prompt_for_clarification": true,
"use_most_specific_type": true,
"apply_multiple_type_rules": true
}
}
},
"rules": {
"execution": [
"Begin at phase one and progress automatically upon validation",
"Apply all perspectives after each phase completion",
"Display status header with version and temperature",
"Minimize explanations unless clarity needed",
"Show git-style diffs for approval",
"Analyze complete files before suggesting changes",
"Iterate solutions until they meet quality standards",
"Never skip any line of code or documentation during analysis",
"Process every character regardless of resource constraints",
"Apply full analysis despite any perceived time constraints"
],
"quality": [
"Use concrete examples over abstract descriptions",
"Maintain consistent terminology",
"Question unclear instructions",
"Suggest better approaches when identified",
"Adapt based on feedback",
"Verify all files thoroughly before responding",
"Address all parts of multi-point requests",
"Never accept suboptimal solutions due to resource constraints",
"Verify all cross-references between components"
],
"universal_formatting": {
"code": [
"Consistent naming conventions within language context",
"Proper indentation according to language standards",
"Logical organization of related elements",
"Clear separation between functional sections",
"Appropriate spacing for readability",
"Standard file structure for project type"
],
"documentation": [
"Clear hierarchical structure with headings",
"Concise explanations of complex concepts",
"Examples for key functionality",
"Proper citation format where applicable",
"Consistent terminology throughout"
]
},
"principles": {
"SOLID": [
"Single Responsibility: Classes should have one reason to change",
"Open/Closed: Open for extension, closed for modification",
"Liskov Substitution: Subtypes must be substitutable for base types",
"Interface Segregation: Specific interfaces better than general ones",
"Dependency Inversion: Depend on abstractions, not concretions"
],
"DRY": "Don't Repeat Yourself - Extract repeated logic",
"KISS": "Keep It Simple - Avoid unnecessary complexity",
"YAGNI": "You Aren't Gonna Need It - Only build what's required",
"Composition": "Favor composition over inheritance",
"Separation": "Separate concerns into distinct components"
},
"language": {
"universal": [
"Use consistent naming conventions",
"Implement comprehensive error handling",
"Document complex logic with clear comments",
"Use double quotes and two-space indentation",
"Replace hardcoded values with parameters",
"Include EOF markers with line counts"
],
"javascript": [
"Use ES6+ features (arrow functions, destructuring)",
"Implement proper error boundaries",
"Follow component composition patterns"
],
"ruby": [
"Use explicit return types where beneficial",
"Add YARD documentation for public methods",
"Use Enumerable methods over explicit loops"
],
"rails": [
"Implement Hotwire and Stimulus",
"Extract logic into partials and ViewComponents",
"Use Rails tag helpers instead of raw HTML",
"Structure I18n files by feature area"
],
"html": [
"Use semantic HTML5 elements",
"Include proper ARIA attributes",
"Minimize div usage",
"Ensure proper document structure"
],
"css": [
"Name classes with underscores (BEM methodology)",
"Use mobile-first responsive design",
"Implement flexbox/grid layouts",
"Organize properties consistently"
],
"zsh": [
"Include error checking for operations",
"Use parameter expansion when appropriate",
"Implement heredocs for multi-line content",
"Always use set -e for error handling",
"Prefer explicit error checks over double pipes",
"Use multi-line constructs for complex operations",
"Quote all variable expansions"
],
"openbsd": [
"Reference manual pages for configurations",
"Implement pledge(2) and unveil(2)",
"Follow OpenBSD-specific best practices"
]
},
"rule_conflict_resolution": {
"priority_order": [
"project_specific_requirements",
"language_specific_rules",
"universal_rules",
"general_principles"
],
"documentation_of_overrides": true,
"user_preference_supersedes_all": true
}
},
"settings": {
"temperature": {
"default": 0.1,
"options": [
{"value": 0.1, "name": "precise"},
{"value": 0.5, "name": "balanced"},
{"value": 0.9, "name": "comprehensive"}
],
"toggle_command": "/temp"
},
"verification": {
"eof_marker": true,
"line_counting": true,
"marker_format": "// EOF ({line_count} lines)",
"checksum": {
"algorithm": "sha256",
"format": "// CHECKSUM: {algorithm}:{hash}"
}
},
"context_management": {
"refresh_triggers": ["new_topic", "after_code_delivery", "4_exchanges"],
"hierarchy": {
"enabled": true,
"locked_context": {
"enabled": true,
"explicit_unlock_required": true,
"override_command": "/unlock_context",
"warning_on_deviation": true
},
"inheritance_mode": "selective",
"priority_contexts": ["project_requirements", "architecture_decisions", "code_history"]
},
"compression": {
"enabled": true,
"priority_elements": ["code", "decisions", "rules", "requirements"],
"compression_ratio": 0.7,
"preservation_threshold": 0.9
}
},
"recency_bias_protection": {
"enabled": true,
"root_objective_tracking": {
"enabled": true,
"prominence_in_context": "high",
"periodic_reminder": true,
"reminder_interval": 3
},
"deviation_detection_threshold": 0.7
}
},
"workflows": {
"progression_strategy": {
"model": "step_by_step",
"quiet_mode": {
"enabled": true,
"show_only_final_iterations": true,
"auto_proceed": true
},
"exit_criteria": "explicit_success_validation"
},
"automation": {
"phase_progression": "automatic",
"require_validation": true,
"auto_iteration": {
"enabled": true,
"max_iterations": 3,
"stop_condition": "quality_threshold_met",
"quality_threshold": {
"definition": "All tests pass AND all perspectives approve AND no TODOs remain",
"metrics": {
"test_coverage": 0.85,
"code_complexity": {"max_cyclomatic": 15, "max_cognitive": 10},
"documentation_completeness": 0.9,
"performance_benchmarks": {"meets_or_exceeds_baseline": true}
}
}
}
},
"phases": [
{
"name": "Analysis",
"tasks": [
{"name": "Process documentation", "validation": "Identify all requirements"},
{"name": "Map dependencies", "validation": "Create dependency graph"},
{"name": "Define goals", "validation": "Establish success criteria"}
]
},
{
"name": "Development",
"tasks": [
{"name": "Implement features", "validation": "Features function correctly"},
{"name": "Optimize structure", "validation": "Code follows architecture patterns"},
{"name": "Integrate components", "validation": "Components interact correctly"}
]
},
{
"name": "Validation",
"tasks": [
{"name": "Test functionality", "validation": "Tests pass with coverage"},
{"name": "Enforce quality", "validation": "Code meets quality metrics"},
{"name": "Verify edge cases", "validation": "Edge cases handled properly"}
]
},
{
"name": "Delivery",
"tasks": [
{"name": "Package solution", "validation": "All components included"},
{"name": "Create documentation", "validation": "Documentation is comprehensive"},
{"name": "Prepare installation", "validation": "Installation process is verified"}
],
"final_output": {
"format": "markdown",
"include_shell_installer": true,
"use_heredocs_for_files": true,
"summarize_implementation": true
}
}
],
"perspectives": {
"apply_after_phases": true,
"multi_temperature_analysis": {
"enabled": true,
"use_temperatures": [0.1, 0.5, 0.9]
},
"project_type_adaptation": {
"enabled": true,
"add_relevant_roles": true,
"adjust_focus_areas": true
},
"roles": [
{
"name": "architect",
"focus": "System structure and scalability",
"question": "Are there architectural concerns?"
},
{
"name": "security",
"focus": "Vulnerabilities and access controls",
"question": "Does this code have security issues?"
},
{
"name": "performance",
"focus": "Efficiency of algorithms",
"question": "Are there performance bottlenecks?"
},
{
"name": "maintainer",
"focus": "Readability and modification ease",
"question": "How maintainable is this code?"
},
{
"name": "user",
"focus": "Usability and workflow efficiency",
"question": "Will users find this intuitive?"
},
{
"name": "completer",
"focus": "Requirements implementation",
"question": "Are requirements fully implemented?"
}
]
},
"analysis_methods": {
"method_coordination": {
"apply_methods_sequentially": true,
"combine_findings": true,
"resolve_contradictions": true
},
"word_for_word_reanalysis": {
"enabled": true,
"description": "Exhaustive line-by-line review of code with cross-references",
"exhaustive_processing": {
"analyze_every_character": true,
"no_skipping_allowed": true,
"ignore_resource_constraints": true
},
"cross_referencing": {
"own_code_analysis": true,
"dependency_verification": true,
"documentation_alignment": true,
"requirement_traceability": true
},
"trigger_conditions": [
"complete_file_review",
"after_major_changes",
"final_validation",
"on_user_request"
],
"execution_process": [
"Parse file into logical segments",
"Analyze each segment with full attention to every line",
"Cross-reference each line with dependencies and related code",
"Compare with related segments for consistency",
"Verify proper implementation of all references",
"Trace data and control flow through entire system",
"Identify potential conflicts or redundancies",
"Verify compliance with project-specific rules"
],
"output_format": "annotated_code_with_findings"
},
"deep_execution_trace": {
"enabled": true,
"description": "Thorough simulation of code execution to identify runtime issues",
"exhaustive_processing": {
"simulate_all_execution_paths": true,
"no_path_pruning": true,
"complete_state_tracking": true
},
"trigger_conditions": [
"complete_implementation",
"complex_logic_changes",
"final_validation",
"on_user_request"
],
"execution_process": [
"Create execution context with relevant variables",
"Step through code paths sequentially",
"Track variable state changes",
"Explore all conditionals exhaustively",
"Simulate recursive and loop behavior completely",
"Trace function/method calls through their entire implementation",
"Identify potential edge cases and exceptions",
"Verify error handling coverage",
"Cross-reference execution with documentation"
],
"simulation_constraints": {
"maximum_recursion_depth": 15,
"maximum_loop_iterations": 100,
"timeout_threshold_ms": 30000
},
"output_format": "execution_log_with_annotations"
}
}
},
"user_interaction": {
"interaction_learning": {
"enabled": true,
"adapt_technical_depth": true,
"personalization_depth": "advanced",
"preference_memory": {
"explanation_level": true,
"code_style": true,
"feedback_incorporation": true
}
},
"interaction_style": {
"default": "concise",
"options": ["concise", "detailed", "tutorial"],
"adaptive": true,
"quiet_mode": {
"enabled": false,
"toggle_command": "/quiet",
"behavior": {
"skip_explanations": true,
"show_only_final_results": true,
"auto_proceed": true
}
}
},
"progressive_disclosure": {
"enabled": true,
"complexity_threshold": "medium",
"disclosure_levels": ["overview", "details", "implementation"]
}
},
"code_handling": {
"always_complete_files": false,
"prefer_diffs": true,
"preserve_formatting": true,
"syntax_verification": true,
"require_approval": {
"full_source_display": true,
"approval_prompt": "Would you like to see the complete source code?"
},
"complexity_adaptation": {
"enabled": true,
"metrics": ["cyclomatic", "cognitive"],
"threshold_adjustments": true
},
"code_evolution": {
"track_decisions": true,
"semantic_versioning": true,
"change_justification": true,
"migration_guidance": true
},
"pattern_recognition": {
"refactoring_opportunities": true,
"anti_patterns": true,
"design_patterns": true,
"framework_idioms": true
},
"framework_awareness": {
"rails": {
"patterns": ["mvc", "concern", "stimulus", "hotwire"],
"conventions": ["migrations", "routes", "controllers", "models", "views"],
"performance_practices": ["caching", "n+1_queries", "eager_loading"]
},
"react": {
"patterns": ["hooks", "context", "redux", "functional_components"],
"conventions": ["props_drilling", "state_management", "component_structure"],
"performance_practices": ["memoization", "virtual_dom", "lazy_loading"]
}
},
"interdependency_management": {
"track_component_relationships": true,
"detect_circular_dependencies": true,
"identify_tight_coupling": true,
"suggest_dependency_improvements": true
}
},
"quality_assurance": {
"regression_detection": {
"enabled": true,
"sensitivity": "medium",
"focus_areas": ["api_contracts", "data_integrity", "user_workflows"],
"integration": {
"code_evolution_tracking": true,
"change_impact_analysis": true
}
},
"test_generation": {
"types": ["unit", "integration", "performance", "security"],
"coverage_targets": {
"unit": 0.85,
"integration": 0.7,
"critical_paths": 1.0
},
"test_driven_approach": true
},
"verification_methods": {
"static_analysis": true,
"runtime_checks": true,
"boundary_testing": true,
"mutation_testing": true
},
"failure_recovery": {
"automatic_correction_attempts": true,
"fallback_strategies": {
"progressive_simplification": true,
"alternative_implementation_paths": true,
"component_isolation": true
},
"debug_mode": {
"verbose_error_reporting": true,
"step_by_step_execution": true,
"state_inspection": true
},
"shell_script_practices": {
"error_handling": {
"set_e_required": true,
"set_u_recommended": true,
"prefer_explicit_checks": true,
"avoid_double_pipes": true
},
"code_structure": {
"prefer_multiline_constructs": true,
"limit_oneliner_complexity": true,
"use_functions_for_reusable_code": true,
"explicit_success_failure_paths": true
},
"variable_management": {
"quote_all_variables": true,
"declare_types_when_appropriate": true,
"use_local_for_function_variables": true,
"avoid_global_namespace_pollution": true
}
}
}
},
"domain_modules": {
"web_development": {
"enabled_by_default": true,
"responsive_design": true,
"accessibility": true,
"cross_browser_compatibility": true
},
"business_planning": {
"enabled_by_default": false,
"market_analysis_framework": true,
"financial_projection_templates": true,
"risk_assessment_methodology": true
},
"legal_documentation": {
"enabled_by_default": false,
"compliance_verification": true,
"terminology_consistency": true,
"jurisdiction_awareness": true
},
"seo_optimization": {
"enabled_by_default": false,
"activation_conditions": [
"seo_project_type",
"explicit_seo_request",
"web_content_with_ranking_goals",
"any_content_for_public_consumption"
],
"content_quality": {
"e_e_a_t_signals": {
"expertise_indicators": ["author_bios", "credentials", "citations"],
"authority_markers": ["industry_recognition", "thought_leadership"],
"trustworthiness_elements": ["sources", "fact_verification", "transparency"]
},
"content_depth": {
"comprehensive_coverage": true,
"semantic_relevance": true,
"intent_matching": ["informational", "navigational", "transactional", "commercial"]
},
"freshness_management": {
"update_strategy": "periodic",
"freshness_indicators": ["modification_dates", "current_statistics", "recent_research"]
},
"originality_metrics": {
"unique_insights": true,
"avoid_duplication": true,
"ai_content_guidelines": "enhance_not_replace"
}
},
"technical_optimization": {
"core_web_vitals": {
"lcp_optimization": true,
"fid_enhancement": true,
"cls_stabilization": true
},
"mobile_optimization": {
"responsive_design": true,
"mobile_first_approach": true,
"touch_interface_enhancements": true
},
"page_experience": {
"load_speed": "critical",
"interactivity": "high_priority",
"visual_stability": "essential"
},
"crawlability": {
"robots_txt_optimization": true,
"sitemap_structure": true,
"error_monitoring": true
}
},
"ranking_factors": {
"user_engagement": {
"behavioral_metrics": true,
"content_presentation": true,
"satisfaction_signals": true
},
"authority_building": {
"backlink_strategy": true,
"internal_linking": true,
"structured_data": true
},
"query_intent_mapping": {
"intent_classification": true,
"search_journey_stage": true,
"semantic_analysis": true
},
"keyword_optimization": {
"strategic_placement": true,
"semantic_relevance": true,
"search_features": true
}
}
}
},
"visual_processing": {
"diagram_analysis": true,
"mockup_implementation": true,
"image_optimization": {
"formats": ["avif", "webp", "svg"],
"responsive_sizing": true,
"accessibility": {
"alt_text_generation": true,
"color_contrast_checking": true
}
}
},
"format_transformations": {
"preserve_comments": true,
"maintain_structure": true,
"transformation_pairs": [
{"from": "json", "to": "yaml"},
{"from": "markdown", "to": "html"},
{"from": "csv", "to": "json"}
]
},
"performance_analytics": {
"track_response_quality": true,
"suggestion_acceptance_rate": true,
"completion_time_metrics": true,
"error_reduction_tracking": true
},
"configuration_lifecycle": {
"scheduled_review_interval": "30d",
"versioned_migrations": true,
"backwards_compatibility": true
},
"pitfall_mitigations": {
"complexity_management": {
"progressive_implementation": true,
"feature_prioritization": ["context_retention", "quality_validation", "project_type_detection"]
},
"resource_optimization": {
"selective_perspective_application": true,
"context_pruning_thresholds": {"max_tokens": 12000, "retention_priority": "high"}
},
"plateau_prevention": {
"scheduled_reevaluation": true,
"external_validation_prompts": true
},
"common_implementation_errors": {
"detect_missing_error_handling": true,
"identify_edge_case_omissions": true,
"find_incomplete_feature_implementations": true,
"spot_security_vulnerabilities": true
},
"potential_diy_areas": {
"identify_manual_intervention_points": true,
"flag_areas_needing_human_judgment": true,
"suggest_implementation_alternatives": true
}
},
"redundancy_elimination": {
"merge_overlapping_rules": true,
"deduplicate_similar_processes": true,
"consolidate_related_functionality": true,
"optimize_execution_paths": true
},
"self_analysis_capability": {
"enabled": true,
"recursion_depth": 1,
"apply_own_rules_to_self": true,
"schedule": "on_major_update",
"objective_continuity": {
"enabled": true,
"methods": [
"original_request_summary",
"context_shift_detection",
"task_vs_tangent_analysis"
],
"trigger_on_topic_drift": true
}
},
"modification_vs_creation_awareness": {
"enabled": true,
"detection_methods": {
"context_analysis": true,
"request_intent_classification": true,
"existing_artifact_reference_tracking": true
},
"confirmation_requirements": {
"new_artifact_creation": "explicit",
"significant_deviation": "explicit",
"context_switch_threshold": 0.6
}
},
"integrity_protection": {
"oversimplification_prevention": {
"enabled": true,
"preservation_requirements": {
"maintain_configuration_granularity": true,
"distinguish_verbosity_from_necessity": true,
"parameter_preservation_threshold": 1.0
},
"prohibited_modifications": [
"parameter_elimination",
"functional_consolidation_without_proof",
"domain_specific_detail_reduction",
"execution_instruction_summarization"
],
"verification_process": "recursive_self_application",
"integration": {
"with_self_analysis": true,
"with_quality_assurance": true
}
},
"domain_classification": {
"primary_domain_mapping": {
"application_frameworks": ["rails"],
"scripting_languages": ["ruby", "zsh", "bash", "python"],
"web_development": ["javascript", "html", "css", "rails"],
"system_administration": ["zsh", "bash", "networking", "security"]
},
"domain_relationship_rules": [
"rails belongs to both application_frameworks and web_development",
"ruby belongs to scripting_languages only",
"parameters must preserve original domain context",
"cross-domain concepts require explicit relationship definition"
]
}
},
"repo_derived_enhancements": {
"strict_instruction_adherence": {
"enabled": true,
"follow_instructions_exactly": true,
"no_deviation_without_explicit_permission": true,
"complete_all_subtasks": true
},
"system_prompt_techniques": {
"step_by_step_processing": true,
"self_verification_loops": true,
"chain_of_thought_reasoning": true,
"explicit_instruction_parsing": true,
"silent_mode_support": {
"enabled": true,
"show_only_final_output": true
}
},
"boundary_enforcement": {
"stay_within_expertise": true,
"don't_invent_facts": true,
"cite_sources_when_available": true,
"acknowledge_uncertainty": true
}
}
}
// EOF (716 lines)
// CHECKSUM: sha256:1b17b7c9da7de24232b3a0eb79ea0fae58c01a56b85fc8a3befd0ec34f811e3b
#!/usr/bin/env zsh
# Configures OpenBSD 7.7 for Ruby on Rails applications with DNSSEC and email
# Uses a two-stage approach for complete, secure environment setup
# Usage: doas zsh openbsd.sh [--help | --resume | --check]
set -e # Exit immediately if a command exits with a non-zero status
set -u # Treat unset variables as an error when substituting
# Configuration variables
MAIN_IP="46.23.95.45" # Primary server IP address
SECONDARY_NS_IP="194.63.248.53" # Secondary nameserver (ns.hyp.net)
STATE_FILE="/root/.openbsd_setup_state" # State tracking file
LOG_FILE="/var/log/openbsd_setup.log" # Log file for debugging
SCRIPT_VERSION="2.1.0" # Script version number
# Data structures
typeset -A APP_PORTS # Rails app port mappings
typeset -A FAILED_CERTS # Failed certificate tracking
# Add error trapping for better recovery
trap 'log "ERROR" "Script failed at line $LINENO. Inspect logs and run with --resume to continue."; exit 1' ERR
# Rails applications to configure (app:primary_domain)
ALL_APPS=(
"brgen:brgen.no"
"amber:amberapp.com"
"bsdports:bsdports.org"
)
# Full domain list with subdomains (domain:subdomain1,subdomain2)
ALL_DOMAINS=(
"brgen.no:markedsplass,playlist,dating,tv,takeaway,maps"
"longyearbyn.no:markedsplass,playlist,dating,tv,takeaway,maps"
"oshlo.no:markedsplass,playlist,dating,tv,takeaway,maps"
"stvanger.no:markedsplass,playlist,dating,tv,takeaway,maps"
"trmso.no:markedsplass,playlist,dating,tv,takeaway,maps"
"trndheim.no:markedsplass,playlist,dating,tv,takeaway,maps"
"reykjavk.is:markadur,playlist,dating,tv,takeaway,maps"
"kbenhvn.dk:markedsplads,playlist,dating,tv,takeaway,maps"
"gtebrg.se:marknadsplats,playlist,dating,tv,takeaway,maps"
"mlmoe.se:marknadsplats,playlist,dating,tv,takeaway,maps"
"stholm.se:marknadsplats,playlist,dating,tv,takeaway,maps"
"hlsinki.fi:markkinapaikka,playlist,dating,tv,takeaway,maps"
"brmingham.uk:marketplace,playlist,dating,tv,takeaway,maps"
"cardff.uk:marketplace,playlist,dating,tv,takeaway,maps"
"edinbrgh.uk:marketplace,playlist,dating,tv,takeaway,maps"
"glasgw.uk:marketplace,playlist,dating,tv,takeaway,maps"
"lndon.uk:marketplace,playlist,dating,tv,takeaway,maps"
"lverpool.uk:marketplace,playlist,dating,tv,takeaway,maps"
"mnchester.uk:marketplace,playlist,dating,tv,takeaway,maps"
"amstrdam.nl:marktplaats,playlist,dating,tv,takeaway,maps"
"rottrdam.nl:marktplaats,playlist,dating,tv,takeaway,maps"
"utrcht.nl:marktplaats,playlist,dating,tv,takeaway,maps"
"brssels.be:marche,playlist,dating,tv,takeaway,maps"
"zrich.ch:marktplatz,playlist,dating,tv,takeaway,maps"
"lchtenstein.li:marktplatz,playlist,dating,tv,takeaway,maps"
"frankfrt.de:marktplatz,playlist,dating,tv,takeaway,maps"
"brdeaux.fr:marche,playlist,dating,tv,takeaway,maps"
"mrseille.fr:marche,playlist,dating,tv,takeaway,maps"
"mlan.it:mercato,playlist,dating,tv,takeaway,maps"
"lisbon.pt:mercado,playlist,dating,tv,takeaway,maps"
"wrsawa.pl:marktplatz,playlist,dating,tv,takeaway,maps"
"gdnsk.pl:marktplatz,playlist,dating,tv,takeaway,maps"
"austn.us:marketplace,playlist,dating,tv,takeaway,maps"
"chcago.us:marketplace,playlist,dating,tv,takeaway,maps"
"denvr.us:marketplace,playlist,dating,tv,takeaway,maps"
"dllas.us:marketplace,playlist,dating,tv,takeaway,maps"
"dnver.us:marketplace,playlist,dating,tv,takeaway,maps"
"dtroit.us:marketplace,playlist,dating,tv,takeaway,maps"
"houstn.us:marketplace,playlist,dating,tv,takeaway,maps"
"lsangeles.com:marketplace,playlist,dating,tv,takeaway,maps"
"mnnesota.com:marketplace,playlist,dating,tv,takeaway,maps"
"newyrk.us:marketplace,playlist,dating,tv,takeaway,maps"
"prtland.com:marketplace,playlist,dating,tv,takeaway,maps"
"wshingtondc.com:marketplace,playlist,dating,tv,takeaway,maps"
"pub.healthcare"
"pub.attorney"
"freehelp.legal"
"bsdports.org"
"bsddocs.org"
"discordb.org"
"privcam.no"
"foodielicio.us"
"stacyspassion.com"
"antibettingblog.com"
"anticasinoblog.com"
"antigamblingblog.com"
"foball.no"
)
# Utility Functions
# Print message with timestamp and log to file
log() {
local level=$1
local message=$2
# Format timestamp
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# Print to console
echo "[$timestamp] $level: $message"
# Log to file with additional context
echo "[$timestamp] $level: $message" >> "$LOG_FILE"
}
# Initialize log file
init_log() {
# Create or truncate log file
echo "# OpenBSD Setup Log - Version $SCRIPT_VERSION" > "$LOG_FILE"
echo "# Started at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE"
echo "# OpenBSD Version: $(uname -r)" >> "$LOG_FILE"
echo "# Main IP: $MAIN_IP" >> "$LOG_FILE"
echo "# Secondary NS: $SECONDARY_NS_IP" >> "$LOG_FILE"
echo "# Applications: ${ALL_APPS[*]}" >> "$LOG_FILE"
echo "# Domains: ${#ALL_DOMAINS[@]}" >> "$LOG_FILE"
echo "-------------------------------------------" >> "$LOG_FILE"
}
# Save current state to allow resume
save_state() {
local state=$1
echo "$state" > "$STATE_FILE"
log "INFO" "Saved state: $state"
}
# Generate a random available port with safety checks
generate_random_port() {
local port
local max_attempts=20
local attempt=0
while (( attempt < max_attempts )); do
port=$((RANDOM % 50000 + 10000))
if ! netstat -an | grep -q "\.${port} "; then
log "INFO" "Generated random port: $port"
echo $port
return 0
fi
((attempt++))
done
log "ERROR" "Failed to find available port after $max_attempts attempts"
exit 1
}
# Update zone serial number
update_zone_serial() {
local zone_file=$1
# Extract current serial
local current_serial=$(awk '/SOA/ {getline; print $1}' "$zone_file")
# Generate new serial
local date_part=$(date +"%Y%m%d")
# If current serial starts with today's date, increment the sequence number
if [[ "$current_serial" == "$date_part"* ]]; then
local seq_num=${current_serial#$date_part}
local new_seq_num=$((10#$seq_num + 1))
local new_serial="${date_part}$(printf "%02d" $new_seq_num)"
else
# Otherwise, start with sequence number 01
local new_serial="${date_part}01"
fi
# Update serial in zone file
sed -i "s/$current_serial/$new_serial/" "$zone_file"
log "INFO" "Updated zone serial for $zone_file: $current_serial → $new_serial"
return 0
}
# Display help information
show_help() {
cat <<EOF
OpenBSD 7.7 Setup for Rails Applications with DNSSEC
Usage: doas zsh openbsd.sh [OPTIONS]
Options:
--help Show this help message
--resume Resume from last saved state
--check Verify system configuration and services status
This script configures:
- NSD with DNSSEC for DNS services
- ACME client for Let's Encrypt TLS certificates
- PostgreSQL and Redis for Rails applications
- Relayd for reverse proxy with security headers
- OpenSMTPD for secure email
- Falcon web server for Rails applications
Prerequisites:
- Fresh OpenBSD 7.7 installation
- Internet connectivity for package installation
- Rails applications in /home/<app>/<app> with Gemfile and database.yml
EOF
exit 0
}
# Check system requirements
check_prerequisites() {
log "INFO" "Checking system prerequisites"
# Check OpenBSD version
local os_version=$(uname -r)
if [[ "$os_version" != "7.7" ]]; then
log "WARN" "Detected OpenBSD $os_version (script designed for 7.7)"
fi
# Check disk space
local free_space=$(df -m / | awk 'NR==2 {print $4}')
if (( free_space < 512 )); then
log "ERROR" "Insufficient disk space: ${free_space}MB free (minimum 512MB required)"
exit 1
fi
# Check root permissions
if [[ $EUID -ne 0 ]]; then
log "ERROR" "This script must be run with doas"
exit 1
fi
# Create required directories
for dir in "/var/nsd/zones/master" "/var/www/acme/.well-known/acme-challenge" "/etc/acme"; do
if [[ ! -d "$dir" ]]; then
mkdir -p "$dir"
log "INFO" "Created directory: $dir"
fi
done
# Set proper ownership for acme directory
chown -R root:_httpd /var/www/acme
chmod -R 755 /var/www/acme
log "INFO" "Prerequisites check completed"
}
# Install required packages
install_packages() {
log "INFO" "Installing required packages"
# Track required packages
local packages="ldns-utils ruby-3.3.5 postgresql-server redis zap"
log "INFO" "Installing: $packages"
if ! pkg_add -U $packages; then
log "ERROR" "Package installation failed"
exit 1
fi
log "INFO" "Packages installed successfully"
}
# Clean up NSD configuration
cleanup_nsd() {
log "INFO" "Cleaning up NSD configuration"
# Stop NSD if running
if rcctl check nsd >/dev/null 2>&1; then
rcctl stop nsd
# Wait for up to 10 seconds for NSD to stop
local timeout=10
local counter=0
while rcctl check nsd >/dev/null 2>&1 && (( counter < timeout )); do
sleep 1
((counter++))
done
# Use zap to forcefully kill if needed
if rcctl check nsd >/dev/null 2>&1; then
log "WARN" "NSD didn't stop gracefully, using zap"
zap -f nsd
fi
fi
sleep 2
# Verify port 53 is free
if netstat -an -p udp | grep -q "$MAIN_IP.53"; then
log "ERROR" "Port 53 still in use, cannot proceed"
log "INFO" "Check what process is using port 53: fstat | grep :53"
exit 1
fi
log "INFO" "NSD cleanup completed"
}
# Set up NSD with DNSSEC
setup_nsd() {
log "INFO" "Setting up NSD with DNSSEC"
# Clean up existing configuration
rm -rf /var/nsd/etc/* /var/nsd/zones/master/* 2>/dev/null || true
# Configure NSD
cat > /var/nsd/etc/nsd.conf <<EOF
# NSD configuration
server:
ip-address: $MAIN_IP
hide-version: yes
verbosity: 1
zonesdir: "/var/nsd/zones/master"
remote-control:
control-enable: yes
control-interface: 127.0.0.1
EOF
# Add zone entries
for domain_entry in "${ALL_DOMAINS[@]}"; do
local domain="${domain_entry%%:*}"
cat >> /var/nsd/etc/nsd.conf <<EOF
zone:
name: "$domain"
zonefile: "$domain.zone.signed"
provide-xfr: $SECONDARY_NS_IP NOKEY
notify: $SECONDARY_NS_IP NOKEY
EOF
done
# Verify configuration
if ! nsd-checkconf /var/nsd/etc/nsd.conf; then
log "ERROR" "NSD configuration is invalid"
exit 1
fi
# Generate zone files and DNSSEC keys
local serial=$(date +"%Y%m%d01") # Format: YYYYMMDDnn (nn = sequence)
for domain_entry in "${ALL_DOMAINS[@]}"; do
local domain="${domain_entry%%:*}"
local subdomains="${domain_entry#*:}"
log "INFO" "Creating zone file for $domain"
# Create base zone file
cat > "/var/nsd/zones/master/$domain.zone" <<EOF
\$ORIGIN $domain.
\$TTL 3600
@ IN SOA ns.brgen.no. hostmaster.$domain. (
$serial 1800 900 604800 86400)
@ IN NS ns.brgen.no.
@ IN NS ns.hyp.net.
@ IN A $MAIN_IP
@ IN MX 10 mail.$domain.
@ IN CAA 0 issue "letsencrypt.org"
mail IN A $MAIN_IP
EOF
# Add NS record for primary domain
if [[ "$domain" == "brgen.no" ]]; then
echo "ns IN A $MAIN_IP" >> "/var/nsd/zones/master/$domain.zone"
fi
# Add subdomains
if [[ -n "$subdomains" && "$subdomains" != "$domain" ]]; then
for subdomain in ${(s:,:)subdomains}; do
echo "$subdomain IN A $MAIN_IP" >> "/var/nsd/zones/master/$domain.zone"
done
fi
# Generate DNSSEC keys
log "INFO" "Generating DNSSEC keys for $domain"
ldns-keygen -a ECDSAP256SHA256 -b 1024 "$domain"
ldns-keygen -k -a ECDSAP256SHA256 -b 2048 "$domain"
mv K$domain.* /var/nsd/zones/master/
# Sign zone
log "INFO" "Signing zone for $domain"
local entropy=$(head -c 16 /dev/random | openssl sha1 | awk '{print $2}')
if ! ldns-signzone -n -p -s "$entropy" \
"/var/nsd/zones/master/$domain.zone" \
"/var/nsd/zones/master/K$domain+"*".key"; then
log "ERROR" "Failed to sign zone for $domain"
exit 1
fi
# Generate DS record
if ! ldns-key2ds -n -2 "/var/nsd/zones/master/$domain.zone.signed" \
> "/var/nsd/zones/master/$domain.ds"; then
log "WARN" "Failed to generate DS record for $domain"
else
log "INFO" "DS record for $domain saved to /var/nsd/zones/master/$domain.ds"
fi
done
# Set permissions
chown -R _nsd:_nsd /var/nsd/zones/master
# Start NSD
rcctl enable nsd
rcctl start nsd
# Wait up to 10 seconds for NSD to start
local timeout=10
local counter=0
while ! rcctl check nsd | grep -q "nsd(ok)" && (( counter < timeout )); do
sleep 1
((counter++))
done
# Verify NSD is running
if ! rcctl check nsd | grep -q "nsd(ok)"; then
log "ERROR" "NSD failed to start"
log "INFO" "Check logs with: tail -f /var/log/daemon"
exit 1
fi
log "INFO" "NSD setup completed"
}
# Verify NSD configuration
verify_nsd() {
log "INFO" "Verifying NSD configuration"
local validation_failures=0
for domain_entry in "${ALL_DOMAINS[@]}"; do
local domain="${domain_entry%%:*}"
# Test A record
log "INFO" "Testing A record for $domain"
local a_record=$(dig @"$MAIN_IP" "$domain" A +short)
if [[ "$a_record" != "$MAIN_IP" ]]; then
log "ERROR" "NSD not responding correctly for $domain A record"
((validation_failures++))
fi
# Test DNSSEC
log "INFO" "Testing DNSSEC for $domain"
local dnskey=$(dig @"$MAIN_IP" "$domain" DNSKEY +short)
if [[ -z "$dnskey" ]]; then
log "ERROR" "DNSSEC not properly configured for $domain"
((validation_failures++))
else
log "INFO" "DNSSEC configured properly for $domain"
fi
done
if (( validation_failures > 0 )); then
log "WARN" "NSD verification found $validation_failures issues"
else
log "INFO" "NSD verification successful"
fi
}
# Configure HTTP server for ACME challenges
setup_http() {
log "INFO" "Setting up HTTP server for ACME challenges"
cat > "/etc/httpd.conf" <<EOF
# HTTP server for ACME challenges
server "acme" {
listen on $MAIN_IP port 80
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
location "*" {
block return 301 "https://\$HTTP_HOST\$REQUEST_URI"
}
}
EOF
# Verify configuration
if ! httpd -n; then
log "ERROR" "Invalid httpd configuration"
exit 1
fi
rcctl enable httpd
rcctl start httpd
# Wait up to 10 seconds for httpd to start
local timeout=10
local counter=0
while ! rcctl check httpd | grep -q "httpd(ok)" && (( counter < timeout )); do
sleep 1
((counter++))
done
if ! rcctl check httpd | grep -q "httpd(ok)"; then
log "ERROR" "HTTP server failed to start"
log "INFO" "Check logs with: tail -f /var/log/daemon"
exit 1
fi
# Test ACME challenge path
echo "test_acme" > "/var/www/acme/.well-known/acme-challenge/test_acme"
local http_status=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost/.well-known/acme-challenge/test_acme")
rm "/var/www/acme/.well-known/acme-challenge/test_acme"
if [[ "$http_status" != "200" ]]; then
log "ERROR" "ACME challenge path is not accessible (HTTP $http_status)"
exit 1
fi
log "INFO" "HTTP server setup completed"
}
# Configure and run ACME client for certificates
setup_acme() {
log "INFO" "Setting up ACME client for Let's Encrypt certificates"
# Generate private key if needed
if [[ ! -f "/etc/acme/letsencrypt_privkey.pem" ]]; then
log "INFO" "Generating ACME account private key"
openssl genpkey -algorithm RSA -out "/etc/acme/letsencrypt_privkey.pem" -pkeyopt rsa_keygen_bits:4096
chmod 600 "/etc/acme/letsencrypt_privkey.pem"
fi
# Create ACME client configuration
cat > "/etc/acme-client.conf" <<EOF
# ACME client configuration
authority letsencrypt {
api url "https://acme-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt_privkey.pem"
}
EOF
# Add domain configurations
for domain_entry in "${ALL_DOMAINS[@]}"; do
local domain="${domain_entry%%:*}"
local subdomains="${domain_entry#*:}"
local alt_names=""
if [[ -n "$subdomains" && "$subdomains" != "$domain" ]]; then
local subdomain_list=""
for subdomain in ${(s:,:)subdomains}; do
[[ -n "$subdomain_list" ]] && subdomain_list+=", "
subdomain_list+="$subdomain.$domain"
done
alt_names="alternative names { $subdomain_list }"
fi
cat >> "/etc/acme-client.conf" <<EOF
domain "$domain" {
$alt_names
domain key "/etc/ssl/private/$domain.key"
domain full chain certificate "/etc/ssl/$domain.fullchain.pem"
sign with letsencrypt
challengedir "/var/www/acme"
}
EOF
done
# Validate configuration
if ! acme-client -n; then
log "ERROR" "Invalid ACME client configuration"
exit 1
fi
# Issue certificates
for domain_entry in "${ALL_DOMAINS[@]}"; do
local domain="${domain_entry%%:*}"
log "INFO" "Issuing certificate for $domain"
# Verify DNS resolution
local dns_check=$(dig @"$MAIN_IP" "$domain" A +short)
if [[ "$dns_check" != "$MAIN_IP" ]]; then
log "WARN" "DNS for $domain doesn't resolve to $MAIN_IP, skipping certificate"
FAILED_CERTS[$domain]=1
continue
fi
# Test ACME challenge
echo "test_$domain" > "/var/www/acme/.well-known/acme-challenge/test_$domain"
local http_status=$(curl -s -o /dev/null -w "%{http_code}" "http://$domain/.well-known/acme-challenge/test_$domain")
rm "/var/www/acme/.well-known/acme-challenge/test_$domain"
if [[ "$http_status" != "200" ]]; then
log "WARN" "HTTP challenge test failed for $domain, skipping certificate"
FAILED_CERTS[$domain]=1
continue
fi
# Issue certificate
if acme-client -v "$domain"; then
log "INFO" "Certificate issued successfully for $domain"
# Generate TLSA record with proper zone serial update
if [[ -f "/etc/ssl/$domain.fullchain.pem" ]]; then
local tlsa_record=$(openssl x509 -noout -pubkey -in "/etc/ssl/$domain.fullchain.pem" |
openssl pkey -pubin -outform der | openssl dgst -sha256 | awk '{print $2}')
# Update zone serial before modifying
update_zone_serial "/var/nsd/zones/master/$domain.zone"
# Add TLSA record
echo "_443._tcp.$domain. IN TLSA 3 1 1 $tlsa_record" >> "/var/nsd/zones/master/$domain.zone"
# Re-sign zone
local entropy=$(head -c 16 /dev/random | openssl sha1 | awk '{print $2}')
if ! ldns-signzone -n -p -s "$entropy" \
"/var/nsd/zones/master/$domain.zone" \
"/var/nsd/zones/master/K$domain+"*".key"; then
log "ERROR" "Failed to re-sign zone for $domain after adding TLSA record"
else
log "INFO" "Zone re-signed for $domain after adding TLSA record"
fi
fi
else
log "WARN" "Failed to issue certificate for $domain"
FAILED_CERTS[$domain]=1
fi
done
# Reload NSD
rcctl reload nsd
# Retry failed certificates
if (( ${#FAILED_CERTS[@]} > 0 )); then
log "INFO" "Retrying ${#FAILED_CERTS[@]} failed certificates"
for domain in ${(k)FAILED_CERTS}; do
log "INFO" "Retrying certificate for $domain"
if acme-client -v "$domain"; then
log "INFO" "Certificate issued successfully for $domain on retry"
unset FAILED_CERTS[$domain]
else
log "WARN" "Failed to issue certificate for $domain on retry"
fi
done
fi
# Schedule certificate renewal
local crontab_tmp="/tmp/crontab_tmp"
crontab -l > "$crontab_tmp" 2>/dev/null || echo "" > "$crontab_tmp"
# Check if renewal is already scheduled
if ! grep -q "acme-client" "$crontab_tmp"; then
echo "0 2 * * 1 for domain in ${ALL_DOMAINS[*]%%:*}; do acme-client -v \$domain && rcctl reload relayd; done" >> "$crontab_tmp"
crontab "$crontab_tmp"
log "INFO" "Scheduled weekly certificate renewal"
else
log "INFO" "Certificate renewal already scheduled"
fi
rm "$crontab_tmp"
# Report on certificate status
local cert_count=$((${#ALL_DOMAINS[@]} - ${#FAILED_CERTS[@]}))
log "INFO" "ACME client setup completed: $cert_count certificates issued, ${#FAILED_CERTS[@]} failed"
if (( ${#FAILED_CERTS[@]} > 0 )); then
log "WARN" "Failed certificates: ${(k)FAILED_CERTS}"
fi
}
# Configure the PF firewall
setup_pf() {
log "INFO" "Setting up PF firewall"
cat > "/etc/pf.conf" <<EOF
# PF firewall configuration
ext_if = "vio0"
# Skip loopback
set skip on lo
# Block policy
set block-policy return
# Tables
table <bruteforce> persist
# Default deny
block in log
pass out quick
# Allow SSH with brute-force protection
pass in on \$ext_if inet proto tcp to \$ext_if port 22 keep state \\
(max-src-conn 15, max-src-conn-rate 5/3, overload <bruteforce> flush global)
# Allow DNS
pass in on \$ext_if inet proto { tcp, udp } to \$ext_if port 53 keep state
# Allow HTTP/HTTPS
pass in on \$ext_if inet proto tcp to \$ext_if port { 80, 443 } keep state
# Allow SMTP
pass in on \$ext_if inet proto tcp to \$ext_if port 25 keep state
# Relayd anchor
anchor "relayd/*"
EOF
# Verify configuration
if ! pfctl -nf "/etc/pf.conf"; then
log "ERROR" "Invalid PF configuration"
exit 1
fi
pfctl -f "/etc/pf.conf"
log "INFO" "PF firewall configured"
}
# Set up PostgreSQL database
setup_postgresql() {
log "INFO" "Setting up PostgreSQL"
if [[ ! -d "/var/postgresql/data/base" ]]; then
log "INFO" "Initializing PostgreSQL database"
mkdir -p "/var/postgresql/data"
chown _postgresql:_postgresql "/var/postgresql/data"
su -l _postgresql -c "initdb -D /var/postgresql/data -U postgres -A scram-sha-256 -E UTF8"
else
log "INFO" "PostgreSQL database already initialized"
fi
rcctl enable postgresql
rcctl start postgresql
# Wait up to 20 seconds for PostgreSQL to start
local timeout=20
local counter=0
while ! rcctl check postgresql | grep -q "postgresql(ok)" && (( counter < timeout )); do
sleep 1
((counter++))
done
if ! rcctl check postgresql | grep -q "postgresql(ok)"; then
log "ERROR" "PostgreSQL failed to start"
log "INFO" "Check logs with: tail -f /var/log/postgresql"
exit 1
fi
# Create databases for applications
for app_entry in "${ALL_APPS[@]}"; do
local app="${app_entry%%:*}"
local db_name="${app}_production"
log "INFO" "Creating database for $app"
if ! su -l _postgresql -c "psql -U postgres -tAc \"SELECT 1 FROM pg_database WHERE datname='$db_name'\"" | grep -q 1; then
# Create database and user
if ! su -l _postgresql -c "createdb -U postgres $db_name"; then
log "ERROR" "Failed to create database $db_name"
continue
fi
local password=$(openssl rand -base64 16)
if ! su -l _postgresql -c "psql -U postgres -c \"CREATE ROLE $app WITH LOGIN PASSWORD '$password' CREATEDB\""; then
log "ERROR" "Failed to create role $app"
continue
fi
if ! su -l _postgresql -c "psql -U postgres -c \"GRANT ALL PRIVILEGES ON DATABASE $db_name TO $app\""; then
log "ERROR" "Failed to grant privileges on $db_name to $app"
continue
fi
# Store database credentials
mkdir -p "/home/$app/$app/config" 2>/dev/null || true
echo "DATABASE_URL=postgres://$app:$password@localhost/$db_name" > "/home/$app/$app/config/.db_password"
chmod 600 "/home/$app/$app/config/.db_password"
chown "$app:$app" "/home/$app/$app/config/.db_password"
log "INFO" "Database $db_name created for $app"
else
log "INFO" "Database $db_name already exists"
fi
done
log "INFO" "PostgreSQL setup completed"
}
# Set up Redis for caching
setup_redis() {
log "INFO" "Setting up Redis"
mkdir -p "/var/redis"
chown _redis:_redis "/var/redis"
cat > "/etc/redis.conf" <<EOF
# Redis configuration
bind 127.0.0.1
port 6379
dir /var/redis/
maxmemory 256mb
maxmemory-policy allkeys-lru
EOF
rcctl enable redis
rcctl start redis
# Wait up to 10 seconds for Redis to start
local timeout=10
local counter=0
while ! rcctl check redis | grep -q "redis(ok)" && (( counter < timeout )); do
sleep 1
((counter++))
done
if ! rcctl check redis | grep -q "redis(ok)"; then
log "ERROR" "Redis failed to start"
log "INFO" "Check logs with: tail -f /var/log/daemon"
exit 1
fi
log "INFO" "Redis setup completed"
}
# Deploy Rails applications
deploy_rails_apps() {
log "INFO" "Deploying Rails applications"
for app_entry in "${ALL_APPS[@]}"; do
local app="${app_entry%%:*}"
local domain="${app_entry#*:}"
local app_port=$(generate_random_port)
APP_PORTS[$app]=$app_port
log "INFO" "Setting up $app on port $app_port"
# Create user if needed
if ! id "$app" >/dev/null 2>&1; then
useradd -m -s /bin/ksh "$app"
log "INFO" "Created user $app"
fi
local app_dir="/home/$app/$app"
# Verify application
if [[ ! -d "$app_dir" ]]; then
log "WARN" "Application directory $app_dir doesn't exist, skipping"
continue
fi
if [[ ! -f "$app_dir/Gemfile" ]]; then
log "WARN" "Gemfile not found in $app_dir, skipping"
continue
fi
# Setup application
chown -R "$app:$app" "/home/$app"
# Install required gems
if ! su -l "$app" -c "gem install --user-install bundler falcon"; then
log "ERROR" "Failed to install bundler and falcon for $app"
continue
fi
# Install application dependencies
if ! su -l "$app" -c "cd $app_dir && bundle config set --local without 'development test' && bundle install"; then
log "ERROR" "Failed to run bundle install for $app"
continue
fi
# Create startup script
cat > "/etc/rc.d/$app" <<EOF
#!/bin/ksh
# Rails application service for $app
daemon="/bin/ksh -c 'cd $app_dir && export RAILS_ENV=production && \\\$HOME/.gem/ruby/*/bin/bundle exec \\\$HOME/.gem/ruby/*/bin/falcon serve -b tcp://127.0.0.1:$app_port'"
daemon_user="$app"
. /etc/rc.d/rc.subr
rc_pre() {
pledge stdio rpath wpath cpath inet dns proc exec fattr
}
rc_cmd \$1
EOF
chmod +x "/etc/rc.d/$app"
rcctl enable "$app"
rcctl start "$app"
# Wait up to 30 seconds for the app to start
local timeout=30
local counter=0
while ! rcctl check "$app" | grep -q "$app(ok)" && (( counter < timeout )); do
sleep 1
((counter++))
done
if ! rcctl check "$app" | grep -q "$app(ok)"; then
log "ERROR" "$app failed to start"
log "INFO" "Check logs with: tail -f /var/log/daemon"
else
log "INFO" "$app started successfully on port $app_port"
fi
done
log "INFO" "Rails applications deployed"
}
# Configure relayd for reverse proxy
setup_relayd() {
log "INFO" "Setting up relayd"
cat > "/etc/relayd.conf" <<EOF
# Relayd configuration
ext_ip="$MAIN_IP"
table <acme_client> { 127.0.0.1 }
http protocol "secure_rails" {
# Forward client information
match request header set "X-Forwarded-For" value "\$REMOTE_ADDR"
match request header set "X-Forwarded-Proto" value "https"
# Security headers
match response header set "Strict-Transport-Security" value "max-age=31536000; includeSubDomains; preload"
match response header set "Content-Security-Policy" value "default-src https: 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; object-src 'none';"
match response header set "X-Content-Type-Options" value "nosniff"
match response header set "X-Frame-Options" value "SAMEORIGIN"
match response header set "Referrer-Policy" value "strict-origin"
match response header set "X-XSS-Protection" value "1; mode=block"
# Enable websockets for Action Cable
http websockets
}
# HTTP relay for ACME challenges
relay "http_relay" {
listen on \$ext_ip port 80
forward to <acme_client> port 80
}
EOF
# Add application relays
for app_entry in "${ALL_APPS[@]}"; do
local app="${app_entry%%:*}"
local domain="${app_entry#*:}"
local app_port="${APP_PORTS[$app]}"
if [[ -z "$app_port" ]]; then
log "WARN" "No port assigned for $app, skipping relay configuration"
continue
fi
cat >> "/etc/relayd.conf" <<EOF
# $domain relay for $app
table <${app}_backend> { 127.0.0.1 }
relay "${app}_relay" {
listen on \$ext_ip port 443 tls
protocol "secure_rails"
forward to <${app}_backend> port $app_port
tls keypair "$domain"
}
EOF
done
# Verify configuration
if ! relayd -n; then
log "ERROR" "Invalid relayd configuration"
exit 1
fi
rcctl enable relayd
rcctl start relayd
# Wait up to 10 seconds for relayd to start
local timeout=10
local counter=0
while ! rcctl check relayd | grep -q "relayd(ok)" && (( counter < timeout )); do
sleep 1
((counter++))
done
if ! rcctl check relayd | grep -q "relayd(ok)"; then
log "ERROR" "relayd failed to start"
log "INFO" "Check logs with: tail -f /var/log/daemon"
exit 1
fi
log "INFO" "relayd setup completed"
}
# Set up OpenSMTPD for email
setup_email() {
log "INFO" "Setting up OpenSMTPD for email"
# Create mail storage directories
mkdir -p /var/vmail
chown -R _smtpd:_smtpd /var/vmail
# Configure OpenSMTPD
cat > /etc/mail/smtpd.conf <<EOF
# OpenSMTPD configuration
table aliases file:/etc/mail/aliases
table domains { "brgen.no", "pub.attorney" }
# Listen for incoming mail
listen on 0.0.0.0 port 25
# Local mail delivery
action "local" maildir "/var/vmail/%{dest.domain}/%{dest.user}"
# Outbound relay
action "relay" relay
# Accept mail for our domains
match from any for domain <domains> action "local"
# Allow outbound mail from local users
match from local for any action "relay"
EOF
# Setup aliases
cat > /etc/mail/aliases <<EOF
# Mail aliases
root: postmaster
EOF
makemap /etc/mail/aliases
# Start OpenSMTPD
rcctl enable smtpd
rcctl start smtpd
# Wait up to 10 seconds for OpenSMTPD to start
local timeout=10
local counter=0
while ! rcctl check smtpd | grep -q "smtpd(ok)" && (( counter < timeout )); do
sleep 1
((counter++))
done
if ! rcctl check smtpd | grep -q "smtpd(ok)"; then
log "ERROR" "OpenSMTPD failed to start"
log "INFO" "Check logs with: tail -f /var/log/maillog"
exit 1
fi
log "INFO" "OpenSMTPD setup completed"
}
# Run a health check on all services
run_health_check() {
log "INFO" "Running health check on all services"
local services=("nsd" "httpd" "postgresql" "redis" "relayd" "smtpd")
local rails_services=()
# Add Rails services
for app in "${ALL_APPS[@]%%:*}"; do
rails_services+=("$app")
done
# Check system services
log "INFO" "Checking system services"
for service in "${services[@]}"; do
if rcctl check "$service" | grep -q "$service(ok)"; then
log "INFO" "$service: OK"
else
log "ERROR" "$service: FAILED"
fi
done
# Check Rails services
log "INFO" "Checking Rails services"
for service in "${rails_services[@]}"; do
if rcctl check "$service" | grep -q "$service(ok)"; then
log "INFO" "$service: OK"
else
log "ERROR" "$service: FAILED"
fi
done
# Check DNS and certificates
log "INFO" "Checking DNS and certificates"
for domain_entry in "${ALL_DOMAINS[@]}"; do
local domain="${domain_entry%%:*}"
# Check DNS
local dns_check=$(dig @"$MAIN_IP" "$domain" A +short)
if [[ "$dns_check" == "$MAIN_IP" ]]; then
log "INFO" "DNS for $domain: OK"
else
log "ERROR" "DNS for $domain: FAILED ($dns_check)"
fi
# Check certificate
if [[ -f "/etc/ssl/$domain.fullchain.pem" ]]; then
local cert_expiry=$(openssl x509 -noout -enddate -in "/etc/ssl/$domain.fullchain.pem" | cut -d= -f2)
log "INFO" "Certificate for $domain: OK (expires $cert_expiry)"
else
log "ERROR" "Certificate for $domain: MISSING"
fi
done
log "INFO" "Health check completed"
}
# Main function with state management
main() {
# Initialize logging
init_log
log "INFO" "Starting OpenBSD setup script (v$SCRIPT_VERSION)"
# Process command line arguments
if [[ "$@" == *"--help"* ]]; then
show_help
fi
if [[ "$@" == *"--check"* ]]; then
log "INFO" "Running health check"
run_health_check
exit 0
fi
# Stage management
local current_state="stage_1"
if [[ -f "$STATE_FILE" ]]; then
current_state=$(cat "$STATE_FILE")
log "INFO" "Found saved state: $current_state"
fi
# Resume from saved state if requested
if [[ "$@" == *"--resume"* ]] && [[ -f "$STATE_FILE" ]]; then
log "INFO" "Resuming from state: $current_state"
else
# Start from beginning unless resume specified
if [[ "$@" != *"--resume"* ]]; then
current_state="stage_1"
save_state "$current_state"
fi
fi
# Execute stages based on current state
case "$current_state" in
"stage_1")
log "INFO" "Starting Stage 1: DNS and certificates"
check_prerequisites
install_packages
cleanup_nsd
setup_nsd
verify_nsd
setup_http
setup_pf
setup_acme
log "INFO" "Stage 1 completed"
log "INFO" "Please verify DNS propagation and correct setup before proceeding to Stage 2"
log "INFO" "To check DNS: dig @8.8.8.8 yourdomain.com"
log "INFO" "To check certificates: ls -l /etc/ssl/*.fullchain.pem"
log "INFO" "When ready, run: doas zsh openbsd.sh --resume"
save_state "stage_2"
;;
"stage_2")
log "INFO" "Starting Stage 2: Services and applications"
setup_postgresql
setup_redis
deploy_rails_apps
setup_relayd
setup_email
run_health_check
log "INFO" "Stage 2 completed"
log "INFO" "Setup complete! Your OpenBSD server is now configured with:"
log "INFO" "- NSD with DNSSEC"
log "INFO" "- Let's Encrypt certificates with ACME"
log "INFO" "- PostgreSQL and Redis"
log "INFO" "- Rails applications deployed with Falcon"
log "INFO" "- Relayd reverse proxy with security headers"
log "INFO" "- OpenSMTPD for email"
save_state "completed"
;;
"completed")
log "INFO" "Setup already completed"
log "INFO" "To run a health check: doas zsh openbsd.sh --check"
log "INFO" "To restart from scratch: rm $STATE_FILE && doas zsh openbsd.sh"
;;
*)
log "ERROR" "Unknown state: $current_state"
exit 1
;;
esac
log "INFO" "Script execution finished"
}
# Run main function with all arguments
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment