├── puppet ├── spec │ ├── fixtures │ │ └── modules │ ├── classes │ │ ├── web_spec.rb │ │ ├── freight_spec.rb │ │ ├── profiles_base_spec.rb │ │ ├── profiles_website_spec.rb │ │ ├── profiles_jenkins_node_spec.rb │ │ ├── profiles_backup_sender_spec.rb │ │ ├── profiles_foreman_spec.rb │ │ ├── profiles_repo_deb_spec.rb │ │ ├── profiles_repo_rpm_spec.rb │ │ ├── profiles_backup_receiver_spec.rb │ │ ├── profiles_puppetserver_spec.rb │ │ ├── profiles_jenkins_controller_spec.rb │ │ ├── discourse_spec.rb │ │ ├── secretsgit_spec.rb │ │ ├── profiles_redmine_spec.rb │ │ ├── profiles_discourse_spec.rb │ │ └── jenkins_node_spec.rb │ ├── spec_helper.rb │ └── defines │ │ └── users_account_spec.rb ├── .gitignore ├── modules │ ├── secretsgit │ │ ├── files │ │ │ └── gitconfig │ │ └── manifests │ │ │ └── init.pp │ ├── web │ │ ├── files │ │ │ ├── stagingrpm │ │ │ │ └── robots.txt │ │ │ ├── rpm │ │ │ │ ├── robots.txt │ │ │ │ └── pulpcore-HEADER.html │ │ │ ├── stagingyum │ │ │ │ ├── robots.txt │ │ │ │ └── HEADER.html │ │ │ ├── yum │ │ │ │ ├── robots.txt │ │ │ │ └── pulpcore-HEADER.html │ │ │ ├── rss-stat.sh │ │ │ ├── rsync.sh │ │ │ ├── downloads-HEADER.html │ │ │ └── filter_apache_stats.sh │ │ ├── templates │ │ │ ├── keys.erb │ │ │ ├── stagingrpm │ │ │ │ └── HEADER.html.epp │ │ │ ├── deploy-stagingyum.sh.erb │ │ │ ├── rsync_downloads.sh.erb │ │ │ ├── deploy-stagingrpm.sh.epp │ │ │ └── rpm │ │ │ │ └── HEADER.html.epp │ │ └── manifests │ │ │ ├── base.pp │ │ │ ├── letsencrypt.pp │ │ │ ├── vhost │ │ │ ├── stagingdeb.pp │ │ │ ├── archivedeb.pp │ │ │ ├── deb.pp │ │ │ ├── downloads.pp │ │ │ ├── stagingyum.pp │ │ │ ├── stagingrpm.pp │ │ │ └── rpm.pp │ │ │ ├── init.pp │ │ │ ├── jenkins.pp │ │ │ └── vhost.pp │ ├── jenkins_job_builder │ │ ├── .gitignore │ │ ├── templates │ │ │ └── jenkins_jobs.ini.erb │ │ ├── manifests │ │ │ ├── install.pp │ │ │ ├── init.pp │ │ │ └── config.pp │ │ └── README.md │ ├── jenkins_node │ │ ├── files │ │ │ ├── gemrc │ │ │ ├── pbuilder_F99printrepos │ │ │ ├── gitconfig │ │ │ ├── pbuilder_C10foremanlog │ │ │ ├── db │ │ │ │ └── postgresql │ │ │ ├── pbuilder_F65-add-backports-repos │ │ │ ├── pbuilder_sudoers │ │ │ ├── pbuilder_D80no-man-db-rebuild │ │ │ ├── pbuilder_F70aptupdate │ │ │ ├── pbuilder_A10nozstd │ │ │ ├── reboot-jenkins-node │ │ │ ├── pbuilder_F66-add-nodesource-nodistro-repos │ │ │ ├── pbuilder_F60addforemanrepo │ │ │ └── pbuilder_F67-add-puppet-repos │ │ ├── templates │ │ │ ├── npm_cleaner.sh.erb │ │ │ ├── pbuilder_pdebuild.erb │ │ │ └── 90-nproc.conf.erb │ │ └── manifests │ │ │ ├── db_config.pp │ │ │ ├── rbenv.pp │ │ │ ├── swap.pp │ │ │ ├── packaging.pp │ │ │ ├── packaging │ │ │ ├── rpm.pp │ │ │ └── debian.pp │ │ │ ├── postgresql.pp │ │ │ └── pbuilder_setup.pp │ ├── redmine │ │ ├── templates │ │ │ ├── cron.erb │ │ │ ├── secure_config.yaml.erb │ │ │ └── database.yml.erb │ │ └── files │ │ │ ├── anubis.botPolicies.yaml │ │ │ └── git_repos.sh │ ├── rbenv │ │ └── manifests │ │ │ ├── install.pp │ │ │ ├── init.pp │ │ │ └── build.pp │ ├── openvox_repo │ │ └── manifests │ │ │ ├── purge_puppet.pp │ │ │ ├── init.pp │ │ │ ├── apt.pp │ │ │ └── yum.pp │ ├── freight │ │ ├── templates │ │ │ ├── keys.erb │ │ │ ├── stagingdeb-HEADER.html.epp │ │ │ ├── rsync.erb │ │ │ ├── freight.conf.erb │ │ │ ├── archivedeb-HEADER.html.epp │ │ │ ├── rsync_main.erb │ │ │ ├── deb-HEADER.html.epp │ │ │ ├── cron.erb │ │ │ └── deploy_debs.erb │ │ └── manifests │ │ │ ├── init.pp │ │ │ ├── uploader.pp │ │ │ └── user.pp │ ├── users │ │ ├── manifests │ │ │ ├── init.pp │ │ │ └── account.pp │ │ └── files │ │ │ ├── ogajduse-authorized_keys │ │ │ ├── ehelms-authorized_keys │ │ │ ├── mhulan-authorized_keys │ │ │ ├── Odilhao-authorized_keys │ │ │ ├── gregsutcliffe-authorized_keys │ │ │ ├── jenkins-authorized_keys │ │ │ ├── nalfassi-authorized_keys │ │ │ ├── pcreech-authorized_keys │ │ │ ├── zhunting-authorized_keys │ │ │ ├── evgeni-authorized_keys │ │ │ └── ekohl-authorized_keys │ ├── crb │ │ └── manifests │ │ │ └── init.pp │ ├── fastly_purge │ │ ├── files │ │ │ ├── fastly-purge.sh │ │ │ └── fastly-purge-find.sh │ │ └── manifests │ │ │ └── init.pp │ ├── profiles │ │ ├── files │ │ │ └── base │ │ │ │ ├── reboot-inactive-system │ │ │ │ └── restic-prometheus-exporter.py │ │ ├── manifests │ │ │ ├── website.pp │ │ │ ├── jenkins │ │ │ │ ├── node.pp │ │ │ │ └── controller.pp │ │ │ ├── base.pp │ │ │ ├── repo │ │ │ │ ├── deb.pp │ │ │ │ └── rpm.pp │ │ │ ├── puppetserver.pp │ │ │ ├── backup │ │ │ │ ├── sender.pp │ │ │ │ ├── receiver │ │ │ │ │ └── target.pp │ │ │ │ └── receiver.pp │ │ │ ├── discourse.pp │ │ │ ├── foreman.pp │ │ │ ├── redmine.pp │ │ │ └── base │ │ │ │ └── monitoring.pp │ │ ├── metadata.json │ │ ├── lib │ │ │ └── facter │ │ │ │ └── external_ips.rb │ │ └── templates │ │ │ └── base │ │ │ └── alloy-config.epp │ ├── secure_ssh │ │ ├── manifests │ │ │ ├── receiver.pp │ │ │ ├── uploader.pp │ │ │ ├── rsync │ │ │ │ ├── receiver.pp │ │ │ │ ├── uploader.pp │ │ │ │ ├── uploader_key.pp │ │ │ │ └── receiver_setup.pp │ │ │ ├── uploader_key.pp │ │ │ └── receiver_setup.pp │ │ ├── templates │ │ │ ├── auth_keys.erb │ │ │ └── script_rsync.erb │ │ └── README.md │ ├── deploy │ │ ├── manifests │ │ │ └── init.pp │ │ └── files │ │ │ └── script.sh │ ├── utility │ │ └── manifests │ │ │ └── init.pp │ ├── ssh │ │ ├── manifests │ │ │ └── init.pp │ │ └── lib │ │ │ └── puppet │ │ │ └── functions │ │ │ └── ssh │ │ │ └── keygen.rb │ ├── unattended │ │ └── manifests │ │ │ └── init.pp │ └── discourse │ │ ├── templates │ │ └── mail-receiver.yml.epp │ │ └── manifests │ │ └── init.pp ├── environment.conf ├── .puppet-lint.rc ├── data │ ├── osfamily │ │ └── RedHat-9.yaml │ ├── nodes │ │ ├── redmine01.conova.theforeman.org.yaml │ │ └── website01.osuosl.theforeman.org.yaml │ ├── common.yaml │ └── vagrant.yaml ├── Gemfile ├── Rakefile ├── hiera.yaml ├── manifests │ └── site.pp └── Puppetfile ├── ansible ├── ansible.cfg ├── secrets.yml.example └── fastly.yml ├── requirements.txt ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── pages.yml │ └── main.yml ├── renovate.json ├── docs ├── foreman.md ├── puppet.md ├── backups.md ├── redmine.md ├── secrets.md ├── monitoring.md ├── webserver.md ├── bootstrap.md ├── repo-rpm.md ├── virt.md ├── repo-deb.md └── gpg.md ├── mkdocs.yml ├── vagrant ├── manifests │ └── default.pp └── hiera.yaml ├── .gitmodules └── README.md /puppet/spec/fixtures/modules: -------------------------------------------------------------------------------- 1 | ../../modules -------------------------------------------------------------------------------- /puppet/.gitignore: -------------------------------------------------------------------------------- 1 | .ruby-* 2 | Gemfile.lock 3 | -------------------------------------------------------------------------------- /ansible/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | roles = roles/ 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | jenkins-job-builder 2 | lxml 3 | requests 4 | -------------------------------------------------------------------------------- /puppet/modules/secretsgit/files/gitconfig: -------------------------------------------------------------------------------- 1 | [safe] 2 | directory = * 3 | -------------------------------------------------------------------------------- /puppet/environment.conf: -------------------------------------------------------------------------------- 1 | modulepath = external_modules:modules:$basemodulepath 2 | -------------------------------------------------------------------------------- /puppet/modules/web/files/stagingrpm/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /ansible/secrets.yml.example: -------------------------------------------------------------------------------- 1 | fastly_api_key: abcdef 2 | cloudfiles_access_key: abcdef 3 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_job_builder/.gitignore: -------------------------------------------------------------------------------- 1 | jenkins_job.ini 2 | jenkins_jobs.ini 3 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/files/gemrc: -------------------------------------------------------------------------------- 1 | install: --no-document 2 | update: --no-document 3 | -------------------------------------------------------------------------------- /puppet/.puppet-lint.rc: -------------------------------------------------------------------------------- 1 | --no-documentation-check 2 | --no-arrow_on_right_operand_line-check 3 | -------------------------------------------------------------------------------- /puppet/modules/web/files/rpm/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /foreman/nightly/ 3 | Disallow: /pulpcore/nightly/ 4 | -------------------------------------------------------------------------------- /puppet/modules/redmine/templates/cron.erb: -------------------------------------------------------------------------------- 1 | @hourly <%= @username %> /usr/local/bin/redmine_repos.sh <%= @app_root %> <%= @data_dir %> 2 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/files/pbuilder_F99printrepos: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | tail -n 100 /etc/apt/sources.list /etc/apt/sources.list.d/*.list 4 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/files/gitconfig: -------------------------------------------------------------------------------- 1 | [http] 2 | lowSpeedLimit = 1024 3 | lowSpeedTime = 60 4 | 5 | [user] 6 | email = ci@theforeman.org 7 | name = jenkins 8 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/files/pbuilder_C10foremanlog: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Read Foreman package installation logs on failure 4 | cat /var/log/foreman-install.log || true 5 | -------------------------------------------------------------------------------- /puppet/data/osfamily/RedHat-9.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apache::default_mods: 3 | - status 4 | apache::protocols: 5 | - 'h2' 6 | - 'h2c' 7 | - 'http/1.1' 8 | apache::mpm_module: event 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ssl/* 2 | yaml/* 3 | puppet/puppet.conf 4 | puppet/external_modules/ 5 | g10k 6 | g10k-linux-amd64.zip 7 | .g10k/cache 8 | .idea 9 | .vagrant 10 | puppet/.bundle 11 | -------------------------------------------------------------------------------- /puppet/data/nodes/redmine01.conova.theforeman.org.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | profiles::base::monitoring::scrape_targets: 3 | - '{"__address__" = "localhost:9090", "instance" = "anubis-redmine"}' 4 | -------------------------------------------------------------------------------- /puppet/modules/web/files/stagingyum/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /foreman/ 3 | Disallow: /plugins/ 4 | Disallow: /katello/ 5 | Disallow: /client/ 6 | Disallow: /pulpcore/ 7 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/files/db/postgresql: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: postgresql 3 | database: foreman 4 | username: foreman 5 | password: foreman 6 | host: localhost 7 | template: template0 8 | -------------------------------------------------------------------------------- /puppet/modules/rbenv/manifests/install.pp: -------------------------------------------------------------------------------- 1 | # @api private 2 | class rbenv::install ( 3 | Array[String[1]] $packages, 4 | ) { 5 | package { $packages: 6 | ensure => installed, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/templates/npm_cleaner.sh.erb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | find <%= @homedir %>/tmp -maxdepth 1 -name 'npm-*' -type d -mtime +1 -exec rm -rf {} + 4 | rm -rf <%= @homedir %>/.npm/_cacache/ 5 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/templates/pbuilder_pdebuild.erb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | pdebuild --use-pdebuild-internal --configfile /etc/pbuilder/<%= @name %>/pbuilderrc --architecture <%= @arch %> --buildresult .. 3 | -------------------------------------------------------------------------------- /puppet/modules/openvox_repo/manifests/purge_puppet.pp: -------------------------------------------------------------------------------- 1 | class openvox_repo::purge_puppet () { 2 | package {['puppet-release', 'puppet7-release', 'puppet8-release']: 3 | ensure => absent, 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /puppet/modules/web/templates/keys.erb: -------------------------------------------------------------------------------- 1 | <% @ip_data.sort.each do |host,data| -%> 2 | from="<%= data['ipaddress'] %>",command="/home/website/bin/web_rsync" ssh-rsa <%= @pub_key %> <%= host %> 3 | <% end -%> 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "daily" 9 | -------------------------------------------------------------------------------- /puppet/modules/freight/templates/keys.erb: -------------------------------------------------------------------------------- 1 | <% @ip_data.sort.each do |host,data| -%> 2 | from="<%= data['external_ip4'] %>",command="<%= @home %>/bin/freight_rsync" ssh-rsa <%= @pub_key %> <%= host %> 3 | <% end -%> 4 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/files/pbuilder_F65-add-backports-repos: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Backports 4 | echo "deb http://backports.debian.org/debian-backports/ ${DISTRIBUTION}-backports main" >> /etc/apt/sources.list 5 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/files/pbuilder_sudoers: -------------------------------------------------------------------------------- 1 | Cmnd_Alias PBUILDER = /usr/sbin/pbuilder, /usr/bin/pdebuild, /usr/local/bin/pbuilder-*, /usr/local/bin/pdebuild-* 2 | Defaults!PBUILDER env_keep += FOREMAN_VERSION 3 | -------------------------------------------------------------------------------- /puppet/modules/web/files/yum/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /nightly/ 3 | Disallow: /client/nightly/ 4 | Disallow: /plugins/nightly/ 5 | Disallow: /releases/nightly/ 6 | Disallow: /rails/foreman-nightly/ 7 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "enabledManagers": [ 7 | "puppet" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /puppet/modules/users/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class users ( 2 | Hash[String, Hash] $users = {}, 3 | ) { 4 | include sudo 5 | 6 | # Users hash is passed from Foreman 7 | create_resources(users::account, $users) 8 | } 9 | -------------------------------------------------------------------------------- /puppet/modules/redmine/templates/secure_config.yaml.erb: -------------------------------------------------------------------------------- 1 | # Config not suitable for storing in git://theforeman/redmine 2 | secret_token: <%= @secret_token %> 3 | email_password: <%= @email_password %> 4 | data_dir: <%= @data_dir %> 5 | -------------------------------------------------------------------------------- /puppet/modules/crb/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # @summary Enable the CRB repository on a RedHat family system 2 | class crb { 3 | if $facts['os']['family'] == 'RedHat' { 4 | yumrepo { 'crb': 5 | enabled => '1', 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/files/pbuilder_D80no-man-db-rebuild: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Don't rebuild man-db 3 | 4 | echo "I: Preseed man-db/auto-update to false" 5 | debconf-set-selections < Package['freight'] 6 | } 7 | 8 | package { 'freight': 9 | ensure => 'installed', 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /puppet/modules/web/manifests/base.pp: -------------------------------------------------------------------------------- 1 | # Basic webserver config 2 | class web::base { 3 | include apache 4 | include logrotate 5 | 6 | file { '/var/www/vhosts': 7 | ensure => directory, 8 | owner => 'root', 9 | group => 'root', 10 | mode => '0755', 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /puppet/spec/classes/freight_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'freight' do 4 | on_supported_os.each do |os, os_facts| 5 | context "on #{os}" do 6 | let(:facts) { os_facts } 7 | 8 | it { is_expected.to compile.with_all_deps } 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/files/pbuilder_F70aptupdate: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # F is executed just before 4 | # user logs in, or program starts executing, after chroot is created 5 | # in --login or --execute target. 6 | 7 | # Update apt 8 | /usr/bin/apt-get update 9 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/files/pbuilder_A10nozstd: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # revert Ubuntu's default zstd compression for .debs 4 | if dpkg-deb --help | grep zstd && grep "my @dpkg_options;" /usr/bin/dh_builddeb; then 5 | sed -i -e "s/my @dpkg_options;/my @dpkg_options = ('-Zxz');/" /usr/bin/dh_builddeb 6 | fi 7 | -------------------------------------------------------------------------------- /puppet/spec/classes/profiles_base_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'profiles::base' do 4 | on_supported_os.each do |os, os_facts| 5 | context "on #{os}" do 6 | let(:facts) { os_facts } 7 | 8 | it { is_expected.to compile.with_all_deps } 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /puppet/spec/classes/profiles_website_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'profiles::website' do 4 | on_supported_os.each do |os, os_facts| 5 | context "on #{os}" do 6 | let(:facts) { os_facts } 7 | 8 | it { is_expected.to compile.with_all_deps } 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /puppet/spec/classes/profiles_jenkins_node_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'profiles::jenkins::node' do 4 | on_supported_os.each do |os, facts| 5 | context "on #{os}" do 6 | let(:facts) { facts } 7 | 8 | it { is_expected.to compile.with_all_deps } 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /puppet/modules/profiles/files/base/reboot-inactive-system: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | ACTIVE_SESSIONS=$(loginctl list-sessions --output json | jq 'any(.state == "active")') 7 | 8 | if [[ $ACTIVE_SESSIONS != true ]]; then 9 | shutdown -r +5 'Rebooting after applying package updates' 10 | fi 11 | -------------------------------------------------------------------------------- /puppet/spec/classes/profiles_backup_sender_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'profiles::backup::sender' do 4 | on_supported_os.each do |os, os_facts| 5 | context "on #{os}" do 6 | let(:facts) { os_facts } 7 | 8 | it { is_expected.to compile.with_all_deps } 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_job_builder/templates/jenkins_jobs.ini.erb: -------------------------------------------------------------------------------- 1 | [jenkins] 2 | user=<%= @username %> 3 | password=<%= @password %> 4 | url=<%= @url %> 5 | 6 | [job_builder] 7 | ignore_cache=True 8 | keep_descriptions=False 9 | include_path=.:/etc/jenkins_jobs/jenkins-jobs/<%= @config_name %> 10 | recursive=True 11 | allow_duplicates=False 12 | -------------------------------------------------------------------------------- /docs/foreman.md: -------------------------------------------------------------------------------- 1 | # Foreman 2 | 3 | | | foreman01.conova.theforeman.org | 4 | | - | - | 5 | | type | Libvirt VM | 6 | | OS | CentOS Stream 9 | 7 | | CPUs | 4 | 8 | | RAM | 4GB | 9 | | Storage | /dev/vda (20GB) | 10 | | Managed by | [profiles::foreman](https://github.com/theforeman/foreman-infra/blob/master/puppet/modules/profiles/manifests/foreman.pp) | 11 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/files/reboot-jenkins-node: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | NODE_NAME=$(hostname -f) 7 | NODE_IDLE=$(curl --fail --silent https://ci.theforeman.org/computer/${NODE_NAME}/api/json/ | jq .idle) 8 | 9 | if [[ $NODE_IDLE == true ]]; then 10 | shutdown -r +5 'Rebooting after applying package updates' 11 | fi 12 | -------------------------------------------------------------------------------- /puppet/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'puppet_metadata' 4 | gem 'semantic_puppet' 5 | 6 | gem 'openvox', '~> 8.0', require: false 7 | gem 'puppet-lint', require: false 8 | gem 'voxpupuli-puppet-lint-plugins', '~> 7.0' 9 | gem 'puppet-syntax', require: false 10 | gem 'rspec-puppet', require: false 11 | gem 'rspec-puppet-facts', require: false 12 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/templates/90-nproc.conf.erb: -------------------------------------------------------------------------------- 1 | # Templated by Puppet 2 | # 3 | # Default limit for number of user's processes to prevent 4 | # accidental fork bombs. 5 | # See rhbz #432903 for reasoning. 6 | 7 | * soft nproc <%= (@facts['memory']['system']['total_bytes'] / (1024*1024) || 2048).to_i %> 8 | root soft nproc unlimited 9 | -------------------------------------------------------------------------------- /puppet/modules/redmine/files/anubis.botPolicies.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | bots: 3 | - name: community-theforeman-org 4 | action: ALLOW 5 | remote_addresses: 6 | - 195.192.212.29/32 7 | - import: (data)/meta/default-config.yaml 8 | - name: does-not-need-to-have-accept 9 | expression: '!("Accept" in headers)' 10 | action: WEIGH 11 | weight: 12 | adjust: -5 13 | -------------------------------------------------------------------------------- /puppet/modules/web/files/rpm/pulpcore-HEADER.html: -------------------------------------------------------------------------------- 1 |

Pulpcore packages

2 | 3 | These are RPM builds for Pulp 3 and various plugins for use by Katello. They are only intended to be used by Katello. Only branches used by Katello are maintained. No explicit end of life announcements will be made. 4 | -------------------------------------------------------------------------------- /puppet/modules/web/files/yum/pulpcore-HEADER.html: -------------------------------------------------------------------------------- 1 |

Pulpcore packages

2 | 3 | These are RPM builds for Pulp 3 and various plugins for use by Katello. They are only intended to be used by Katello. Only branches used by Katello are maintained. No explicit end of life announcements will be made. 4 | -------------------------------------------------------------------------------- /puppet/modules/web/files/rss-stat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LOGPREFIX="/var/log/httpd/web-https_access_ssl.log-" 4 | OUTPUT=/var/log/rss-stat 5 | 6 | for logfile in ${LOGPREFIX}*; do 7 | logsuffix=${logfile#${LOGPREFIX}} 8 | outfile="${OUTPUT}/rss-stat-${logsuffix}.gz" 9 | if [[ ! -f ${outfile} ]]; then 10 | grep 'feed.xml.*"Foreman/' ${logfile} | gzip > ${outfile} 11 | fi 12 | done 13 | -------------------------------------------------------------------------------- /puppet/modules/profiles/manifests/website.pp: -------------------------------------------------------------------------------- 1 | # @summary A profile for the machines hosting the website and downloads 2 | # 3 | # @param stable 4 | # Latest release that users expect 5 | class profiles::website ( 6 | String[1] $stable, 7 | ) { 8 | contain web 9 | 10 | contain web::vhost::downloads 11 | 12 | class { 'web::vhost::web': 13 | stable => $stable, 14 | } 15 | contain web::vhost::web 16 | } 17 | -------------------------------------------------------------------------------- /puppet/modules/users/files/ehelms-authorized_keys: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAltWDQPbk38TKa9cBG8mD6A/7P277wzFRuZSKBhJCcoxxx0fmW9K3EvjGmADHm16k9xEJvUvWrHQpTAZOXVoi8BGKVL+dV4ENXWFE+0VZY9rF6QIYNUiYmfH3gxW+1kytFjg99xRInktRHtFszlpM3eqQ1zuMKVUBenPZ8LO4zt+fEimdc6c/TS9SdmOjPOcTztYdCkCbgXtRL3ZXCDmXIfEMapd45bKrU+GRFL3onffTRAetIW+74S/DtmbTxwVUqvNyJDgZywepP9AMFu3b+giO/f936I/sH8PEODJydjHU5y/Frhwau/2t5W+nSh8H0iiLklUHYdtvg9xWNt+WUw== 2 | -------------------------------------------------------------------------------- /puppet/modules/web/files/rsync.sh: -------------------------------------------------------------------------------- 1 | # Permit transfer 2 | $SSH_ORIGINAL_COMMAND 3 | 4 | find /home/website/rsync_cache -type f -exec chmod 644 {} \; 5 | find /home/website/rsync_cache -type d -exec chmod 755 {} \; 6 | # Publish the site - stderr/out redirect is required to stop the noninteractive shell from hanging 7 | rsync -rvx --delete-after /home/website/rsync_cache/ /var/www/vhosts/web/htdocs/ 2>&1 >/dev/null ; 8 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Foreman Infrastructure 2 | 3 | nav: 4 | - Overview: index.md 5 | - Roles: 6 | - Discourse: discourse.md 7 | - Foreman: foreman.md 8 | - Jenkins: jenkins.md 9 | - Puppet: puppet.md 10 | - Redmine: redmine.md 11 | - Virt: virt.md 12 | - Webserver: webserver.md 13 | - GPG: gpg.md 14 | - Secrets: secrets.md 15 | - Backups: backups.md 16 | - Bootstrap: bootstrap.md 17 | -------------------------------------------------------------------------------- /puppet/modules/openvox_repo/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class openvox_repo ( 2 | Enum['8', '7'] $release = '8' 3 | ) { 4 | case $facts['os']['family'] { 5 | 'RedHat': { 6 | include openvox_repo::yum 7 | } 8 | 'Debian': { 9 | include openvox_repo::apt 10 | } 11 | default: { 12 | fail("${facts['os']['family']} is unsupported") 13 | } 14 | } 15 | include openvox_repo::purge_puppet 16 | } 17 | -------------------------------------------------------------------------------- /puppet/modules/users/files/mhulan-authorized_keys: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA5mX2JV6T6CVCRmTlC9vrpgEQp259XQYBKIkhWeHtJXGe8bV6RWxR07deny/coS3OZ0TcM8nXb3J3mykaF1xAK+9nXljcIJy76Us9bjXS25UVXQ18DMOQ0mKR3u29jYjw0TCGBUZxjpOTiUudCoAI7i3XHanQz0vyQaKOhDa1fDb3Qc4hKujJYf76J/XPYpb5DHYcWxELgCFo5MWRnYex2L92gm1xcx1CAPVN5JbpIxhkHuXIJx0JBhJAHUvYopByK0ZNFqMkgylVwvuY2gmo085cO6I5GL23NnaKjLKJo7LLPQpJZ1BscMIyrwQTm2CAvJMok6vh88WsB/TI7nsPmQ== ares@bart 2 | -------------------------------------------------------------------------------- /puppet/modules/secure_ssh/manifests/receiver.pp: -------------------------------------------------------------------------------- 1 | # This class takes a hash of names of secure ssh keys to 2 | # permit upload from, the IPs to allow upload from, and 3 | # the script to run when accepting an upload 4 | # 5 | # @param keys Hash of names of keys to permit access from 6 | class secure_ssh::receiver ( 7 | Hash[String[1], Hash[String[1], Any]] $keys = {}, 8 | ) { 9 | create_resources('secure_ssh::receiver_setup', $keys) 10 | } 11 | -------------------------------------------------------------------------------- /puppet/modules/users/files/Odilhao-authorized_keys: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCcLkG55ADrT4NwVZYIlxdz7JlocaF52zjsG6HmBULYzWRjf5gx670LX9uuC9zKFsMg2WepftMVnKdJ0DFxApuWMZjMtmK1K48nnKDhvBLc1fxaN7TItqDyTN+Hb5Q0siE4fpvTzL/DvKfauvLb7/jkgMMr02ahQsiPr7hFdxzVIyrTCTo86U+sZ425Hlq+1FLJ0dKHBclgCONnkKJj7kyTPP+puoGWgrxaZX8p/V5a0JXMNfkc1GCfbuwI4dlFZRDjkpW1khGucIPLYhPc2fCblL8NgyrYleHlykf3t1YO0yUzotKl6KZzpgpwnadTCsfjs5+bsayoRBst350FwRfd osousa@osousa.remote.csb 2 | -------------------------------------------------------------------------------- /puppet/modules/profiles/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "theforeman-profiles", 3 | "version": "0.1.0", 4 | "author": "theforeman", 5 | "summary": "Foreman Infrastructure profiles", 6 | "license": "Apache-2.0", 7 | "source": "https://github.com/theforeman/foreman-infra", 8 | "project_page": "https://github.com/theforeman/foreman-infra", 9 | "issues_url": "https://github.com/theforeman/foreman-infra/issues", 10 | "dependencies": [] 11 | } 12 | -------------------------------------------------------------------------------- /puppet/modules/secure_ssh/manifests/uploader.pp: -------------------------------------------------------------------------------- 1 | # This class takes an array of names of secure ssh keys to 2 | # create on a host which will be uploading to hosts configured 3 | # using the receiver class 4 | # 5 | # @param keys 6 | # Hash of names to user/dir pairs for ssh keys to create 7 | class secure_ssh::uploader ( 8 | Hash[String[1], Hash[String[1], Any]] $keys = {}, 9 | ) { 10 | create_resources('secure_ssh::uploader_key', $keys) 11 | } 12 | -------------------------------------------------------------------------------- /puppet/modules/users/files/gregsutcliffe-authorized_keys: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+1sjrMV3VKV1zE5caeqE6rwU528I8bfNxbkYuWKyiR0n9jg2fWidCGdoWC6+KzMJqGqR/wO1m5VXj6lIKYyGbYm+f3SyI6B9NJ0h4P25fLcSGRCGwCvv3vkqehcDvir1bKwGU0BewrUwI5ljm4+nfAdhDO8hnrFKg8paRrbwRL7GeR/ZMCRMEFLsQT96z0NPUk5yDYWE3xCTcVKENP89OKc1Sk0J6Xk5FFDBrEExD/0cSe2WhblvVC7sL7k3YwLKbq36UxGTer1nCzY2v9AsUpI0hmqN4fwh1XDTPR6ONASRw1fazybrbFRnh/hmsm4X8EUAzbOClwXYBMixYwKmx /home/greg/.ssh/id_greg 2 | -------------------------------------------------------------------------------- /puppet/modules/secure_ssh/manifests/rsync/receiver.pp: -------------------------------------------------------------------------------- 1 | # This class takes a hash of names of rsync ssh keys to 2 | # permit upload from, the IPs to allow upload from, and 3 | # the script to run when accepting an upload 4 | # 5 | # @param keys Hash of names of keys to permit access from 6 | class secure_ssh::rsync::receiver ( 7 | Hash[String[1], Hash[String[1], Any]] $keys = {}, 8 | ) { 9 | create_resources('secure_ssh::rsync::receiver_setup', $keys) 10 | } 11 | -------------------------------------------------------------------------------- /puppet/modules/users/files/jenkins-authorized_keys: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD0QacqcRQzycs7r6odx94FqSEme3O8q/IQAu8A4GTxGzUpsUKQtvE9gzi0TrYZbyzYCMD+MTmDXmk+wPB+QaaHdJhSLe+Tu4tPgX5cp3u6RnrDXVfMg8BTK5kUPcgQJ9QIOMH03weLHw7G5ZC6YCDQz0iRhofJ6ZAFle2aHTg6emjUIxG0Ox4oHhX8cqozcCm/TI3ZspBiUgJo6oaWwkDHcnzi453j5jaxOLD2ykI8+dO6F1Strk1+DnbbnD91PAZuHG9Jg3C1naGdGOYLJ5rfIRLxDkGHXLEwpS7s1lti+p4zNWpcLa5lspEVmdPrEzxmyRNul+/WR/STjKbTtCWt root@master02.rackspace.theforeman.org 2 | -------------------------------------------------------------------------------- /puppet/modules/secure_ssh/manifests/rsync/uploader.pp: -------------------------------------------------------------------------------- 1 | # This class takes an array of names of secure ssh keys to 2 | # create on a host which will be uploading to hosts configured 3 | # using the receiver class 4 | # 5 | # @param keys Hash of names to user/dir pairs for ssh keys to create 6 | class secure_ssh::rsync::uploader ( 7 | Hash[String[1], Hash[String[1], Any]] $keys = {}, 8 | ) { 9 | create_resources('secure_ssh::rsync::uploader_key', $keys) 10 | } 11 | -------------------------------------------------------------------------------- /puppet/spec/classes/profiles_foreman_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'profiles::foreman' do 4 | on_supported_os.each do |os, os_facts| 5 | context "on #{os}" do 6 | let(:facts) { os_facts } 7 | let(:pre_condition) do 8 | <<~PUPPET 9 | class { 'restic': 10 | password => 'SuperSecret', 11 | } 12 | PUPPET 13 | end 14 | 15 | it { is_expected.to compile.with_all_deps } 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /puppet/spec/classes/profiles_repo_deb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'profiles::repo::deb' do 4 | on_supported_os.each do |os, os_facts| 5 | context "on #{os}" do 6 | let(:facts) { os_facts } 7 | let(:pre_condition) do 8 | <<~PUPPET 9 | class { 'restic': 10 | password => 'SuperSecret', 11 | } 12 | PUPPET 13 | end 14 | 15 | it { is_expected.to compile.with_all_deps } 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /puppet/spec/classes/profiles_repo_rpm_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'profiles::repo::rpm' do 4 | on_supported_os.each do |os, os_facts| 5 | context "on #{os}" do 6 | let(:facts) { os_facts } 7 | let(:pre_condition) do 8 | <<~PUPPET 9 | class { 'restic': 10 | password => 'SuperSecret', 11 | } 12 | PUPPET 13 | end 14 | 15 | it { is_expected.to compile.with_all_deps } 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /puppet/modules/web/files/stagingyum/HEADER.html: -------------------------------------------------------------------------------- 1 |

stagingyum.theforeman.org

2 | 3 |

Staging Repositories for Foreman releases

4 | 5 |

Accessing this repo

6 | 7 | This repository is available over HTTP and HTTPS: 8 | 9 |
    10 |
  • http://stagingyum.theforeman.org
  • 11 |
  • https://stagingyum.theforeman.org
  • 12 |
13 | 14 |

Support

15 | 16 |

These are staging repositories used for testing purposes only. They are NOT supported.

17 | -------------------------------------------------------------------------------- /puppet/modules/web/manifests/letsencrypt.pp: -------------------------------------------------------------------------------- 1 | class web::letsencrypt ( 2 | $email = 'foreman-infra-notifications@googlegroups.com', 3 | ) { 4 | class { 'letsencrypt': 5 | email => $email, 6 | configure_epel => false, 7 | } 8 | 9 | cron { 'letsencrypt_renew': 10 | command => "${letsencrypt::command} renew --quiet --renew-hook '/bin/systemctl reload httpd'", 11 | user => 'root', 12 | weekday => '6', 13 | hour => '3', 14 | minute => '27', 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /puppet/modules/deploy/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # @summary Set up a deployment of Puppet environments 2 | # 3 | # @param user 4 | # The username for the user 5 | class deploy ( 6 | String[1] $user = 'deploypuppet', 7 | ) { 8 | # TODO: install g10k in $PATH 9 | 10 | secure_ssh::receiver_setup { 'deploy': 11 | user => $user, 12 | foreman_search => 'host ~ node*.jenkins.osuosl.theforeman.org and (name = external_ip4 or name = external_ip6)', 13 | script_content => file('deploy/script.sh'), 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /puppet/modules/web/templates/stagingrpm/HEADER.html.epp: -------------------------------------------------------------------------------- 1 | <%- | 2 | Stdlib::Fqdn $servername, 3 | | -%> 4 |

<%= $servername %>

5 | 6 |

RPM Staging Repositories for Foreman releases

7 | 8 |

Accessing this repo

9 | 10 | This repository is available over HTTP and HTTPS: 11 | 12 |
    13 |
  • http://<%= $servername %>
  • 14 |
  • https://<%= $servername %>
  • 15 |
16 | 17 |

Support

18 | 19 |

These are staging repositories used for testing purposes only. They are NOT supported.

20 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_job_builder/manifests/install.pp: -------------------------------------------------------------------------------- 1 | # @summary install JJB 2 | # @api private 3 | class jenkins_job_builder::install ( 4 | String[1] $ensure = $jenkins_job_builder::ensure, 5 | ) { 6 | stdlib::ensure_packages(['python3-pip', 'python3-pyyaml']) 7 | 8 | package { 'jenkins-job-builder': 9 | ensure => $jenkins_job_builder::ensure, 10 | provider => 'pip', 11 | require => Package['python3-pip', 'python3-pyyaml'], 12 | } 13 | 14 | file { '/etc/jenkins_jobs': 15 | ensure => directory, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /puppet/spec/classes/profiles_backup_receiver_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'profiles::backup::receiver' do 4 | on_supported_os.each do |os, os_facts| 5 | context "on #{os}" do 6 | let(:facts) { os_facts } 7 | 8 | it { is_expected.to compile.with_all_deps } 9 | 10 | context 'with targets' do 11 | let(:params) do 12 | { 13 | targets: ['example01', 'example02'] 14 | } 15 | end 16 | 17 | it { is_expected.to compile.with_all_deps } 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /docs/puppet.md: -------------------------------------------------------------------------------- 1 | # Puppetserver 2 | 3 | The puppetserver is hosted on `puppet.theforeman.org`, which is a CNAME to the actual server. 4 | On the actual server a subjectAltName is configured so both the hostname and service name should work. 5 | 6 | | | puppet01.conova.theforeman.org | 7 | | - | - | 8 | | type | Libvirt VM | 9 | | OS | CentOS Stream 9 | 10 | | CPUs | 4 | 11 | | RAM | 8GB | 12 | | Storage | /dev/vda (20GB) | 13 | | Managed by | [profiles::puppetserver](https://github.com/theforeman/foreman-infra/blob/master/puppet/modules/profiles/manifests/puppetserver.pp) | 14 | -------------------------------------------------------------------------------- /puppet/modules/fastly_purge/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # @summary scripts to manage fastly CDN purging 2 | # 3 | class fastly_purge { 4 | file { '/usr/local/bin/fastly-purge': 5 | ensure => file, 6 | owner => 'root', 7 | group => 'root', 8 | mode => '0755', 9 | content => file("${module_name}/fastly-purge.sh"), 10 | } 11 | 12 | file { '/usr/local/bin/fastly-purge-find': 13 | ensure => file, 14 | owner => 'root', 15 | group => 'root', 16 | mode => '0755', 17 | content => file("${module_name}/fastly-purge-find.sh"), 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /puppet/modules/utility/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # Various basic utilities 2 | class utility ($sysadmins = ['/dev/null']) { 3 | $vim = $facts['os']['family'] ? { 4 | 'RedHat' => 'vim-enhanced', 5 | default => 'vim', 6 | } 7 | 8 | stdlib::ensure_packages([$vim]) 9 | 10 | stdlib::ensure_packages(['htop', 'iftop', 'tmux']) 11 | 12 | stdlib::ensure_packages(['rsync']) 13 | 14 | mailalias { 'sysadmins': 15 | ensure => present, 16 | recipient => $sysadmins, 17 | } 18 | mailalias { 'root': 19 | ensure => present, 20 | recipient => 'sysadmins', 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /puppet/spec/classes/profiles_puppetserver_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'profiles::puppetserver' do 4 | on_supported_os.each do |os, os_facts| 5 | context "on #{os}" do 6 | let(:facts) { os_facts } 7 | let(:pre_condition) do 8 | <<~PUPPET 9 | class { 'restic': 10 | password => 'SuperSecret', 11 | } 12 | class { 'puppet': 13 | server_environments_owner => 'deploypuppet', 14 | } 15 | PUPPET 16 | end 17 | 18 | it { is_expected.to compile.with_all_deps } 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /puppet/modules/openvox_repo/manifests/apt.pp: -------------------------------------------------------------------------------- 1 | class openvox_repo::apt () { 2 | include apt 3 | 4 | $os_name = downcase($facts['os']['name']) 5 | $release = "${os_name}${facts['os']['release']['major']}" 6 | 7 | apt::source { 'openvox': 8 | comment => "OpenVox ${openvox_repo::release} ${release} Repository", 9 | location => 'https://apt.voxpupuli.org', 10 | release => $release, 11 | repos => "openvox${openvox_repo::release}", 12 | key => { 13 | 'name' => 'openvox-keyring.gpg', 14 | 'source' => 'https://apt.voxpupuli.org/openvox-keyring.gpg', 15 | }, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /puppet/modules/openvox_repo/manifests/yum.pp: -------------------------------------------------------------------------------- 1 | class openvox_repo::yum () { 2 | $os_name = $facts['os']['name'] ? { 3 | 'Fedora' => 'fedora', 4 | 'Amazon' => 'amazon', 5 | default => 'el', 6 | } 7 | 8 | yumrepo { 'openvox': 9 | descr => "OpenVox ${openvox_repo::release} ${os_name} ${facts['os']['release']['major']} Repository", 10 | baseurl => "https://yum.voxpupuli.org/openvox${openvox_repo::release}/${os_name}/${facts['os']['release']['major']}/\$basearch", 11 | gpgcheck => '1', 12 | gpgkey => 'https://yum.voxpupuli.org/GPG-KEY-openvox.pub', 13 | enabled => '1', 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /puppet/data/common.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | stable_release: '3.17' 3 | profiles::website::stable: '%{alias("stable_release")}' 4 | profiles::repo::deb::stable: '%{alias("stable_release")}' 5 | profiles::repo::rpm::stable_foreman: '%{alias("stable_release")}' 6 | 7 | rsync_usernames: 8 | - 'ehelms' 9 | - 'ekohl' 10 | - 'evgeni' 11 | - 'Odilhao' 12 | - 'ogajduse' 13 | - 'pcreech' 14 | - 'zhunting' 15 | 16 | web::vhost::stagingrpm::usernames: '%{alias("rsync_usernames")}' 17 | web::vhost::stagingyum::usernames: '%{alias("rsync_usernames")}' 18 | 19 | puppet::client_package: "openvox-agent" 20 | puppet::server_package: "openvox-server" 21 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/manifests/db_config.pp: -------------------------------------------------------------------------------- 1 | # @api private 2 | define jenkins_node::db_config ( 3 | Enum['file', 'absent'] $ensure = 'file', 4 | Stdlib::Absolutepath $jenkins_home = '/home/jenkins', 5 | String[1] $jenkins_user = 'jenkins', 6 | String[1] $jenkins_group = 'jenkins', 7 | ) { 8 | if $ensure == 'file' { 9 | $content = file("${module_name}/db/${title}") 10 | } else { 11 | $content = undef 12 | } 13 | 14 | file { "${jenkins_home}/${title}.db.yaml": 15 | ensure => $ensure, 16 | content => $content, 17 | owner => $jenkins_user, 18 | group => $jenkins_group, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /puppet/modules/secure_ssh/templates/auth_keys.erb: -------------------------------------------------------------------------------- 1 | <% 2 | if @ip_data.nil? 3 | # Flat array from user 4 | array = @allowed_ips 5 | else 6 | # Facts hash from Foreman 7 | array = @ip_data['results'].values.map{|a| a.values_at('external_ip4', 'external_ip6') }.flatten.compact 8 | end 9 | users = @authorized_keys 10 | -%> 11 | <% array.sort.each do |ip| -%> 12 | from="<%= ip %>",command="<%= @homedir %>/bin/secure_<%= @name %>" ssh-rsa <%= @pub_key %> <%= ip %>_secure_<%= @name %> 13 | <% end -%> 14 | <% users.sort.each do |user_key| -%> 15 | command="<%= @homedir %>/bin/secure_<%= @name %>" <%= user_key %> 16 | <% end -%> 17 | -------------------------------------------------------------------------------- /puppet/modules/web/templates/deploy-stagingyum.sh.erb: -------------------------------------------------------------------------------- 1 | # Make sure target dir can be created 2 | YUM_PATH=`echo "${SSH_ORIGINAL_COMMAND}" | awk '{ print $NF }'` 3 | PROJECT=`echo $YUM_PATH | /bin/cut -f2 -d/` 4 | RELEASE=`echo $YUM_PATH | /bin/cut -f3 -d/` 5 | mkdir -p <%= @home %>/rsync_cache/$PROJECT/$RELEASE 6 | 7 | # Permit transfer 8 | $SSH_ORIGINAL_COMMAND 9 | 10 | # Publish the repo - stderr/out redirect is required to stop the noninteractive shell from hanging 11 | rsync --recursive --times --verbose --one-file-system --delete-after <%= @home %>/rsync_cache/$PROJECT/$RELEASE <%= @yum_directory %>/$PROJECT/ 2>&1 >/dev/null ; 12 | -------------------------------------------------------------------------------- /puppet/modules/freight/templates/stagingdeb-HEADER.html.epp: -------------------------------------------------------------------------------- 1 |

The Debian Foreman staging repo

2 | 3 |

This repo is for building and testing scratch packages before pushing them to the real repos

4 | 5 |

Every user will have the packages pushed to their own component, based on the github repository used.
6 | So if you specified 'ares' as the github repo, and 'bookworm' as the distro, your package is at:

7 | 8 |
deb http://stagingdeb.theforeman.org/ bookworm ares-nightly
9 | 10 |

Plugins are in a similar per-user component:

11 | 12 |
deb http://stagingdeb.theforeman.org/ plugins ares
13 | 14 |

Enjoy

15 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/manifests/rbenv.pp: -------------------------------------------------------------------------------- 1 | # rbenv Configuration 2 | # @api private 3 | class jenkins_node::rbenv { 4 | file { '/home/jenkins/.rbenv': 5 | ensure => directory, 6 | owner => 'jenkins', 7 | group => 'jenkins', 8 | require => User['jenkins'], 9 | } 10 | 11 | class { 'rbenv': 12 | install_dir => '/home/jenkins/.rbenv', 13 | require => [File['/home/jenkins/.rbenv'], Package['gcc-c++']], 14 | user => 'jenkins', 15 | } 16 | 17 | stdlib::ensure_packages(['gcc-c++']) 18 | 19 | rbenv::build { '3.2.9': } 20 | rbenv::build { '3.1.0': } 21 | rbenv::build { '3.0.4': } 22 | rbenv::build { '2.7.6': } 23 | } 24 | -------------------------------------------------------------------------------- /puppet/spec/classes/profiles_jenkins_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'profiles::jenkins::controller' do 4 | on_supported_os.each do |os, facts| 5 | context "on #{os}" do 6 | let(:facts) { facts } 7 | let(:pre_condition) do 8 | <<~PUPPET 9 | class { 'restic': 10 | password => 'SuperSecret', 11 | } 12 | PUPPET 13 | end 14 | let(:params) do 15 | { 16 | jenkins_job_builder_username: 'user', 17 | jenkins_job_builder_password: 'password', 18 | } 19 | end 20 | 21 | it { is_expected.to compile.with_all_deps } 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /puppet/modules/deploy/files/script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e # dont rsync if clone fails 3 | echo "Deploy started at $(date)" 4 | dir=$(mktemp -d) 5 | environment=production 6 | trap 'rm -rf ${dir}' EXIT 7 | git clone --quiet --depth 1 https://github.com/theforeman/foreman-infra "${dir}/" 8 | g10k -quiet -cachedir "$HOME/.cache/g10k" -puppetfile -puppetfilelocation "${dir}/puppet/Puppetfile" -moduledir "${dir}/puppet/external_modules" 9 | rsync -aqx --delete-after --exclude=.git "${dir}"/puppet/* "/etc/puppetlabs/code/environments/$environment/" 10 | puppet generate types --environment "$environment" --config /etc/puppetlabs/puppet/puppet.conf --force 11 | echo "Deploy complete at $(date)" 12 | -------------------------------------------------------------------------------- /puppet/modules/freight/templates/rsync.erb: -------------------------------------------------------------------------------- 1 | # Make sure target dir can be created 2 | DEB_PATH=`echo "${SSH_ORIGINAL_COMMAND}" | awk '{ print $NF }'` 3 | DISTRO=`echo $DEB_PATH | /bin/cut -f2 -d/` 4 | REPO=`echo $DEB_PATH | /bin/cut -f3 -d/` 5 | mkdir -p <%= @home %>/rsync_cache/$DISTRO/$REPO 6 | 7 | # Permit transfer 8 | $SSH_ORIGINAL_COMMAND 9 | 10 | find <%= @home %>/rsync_cache/$DISTRO/$REPO -iname '*.deb' -exec freight-add -v -c <%= @home %>/freight.conf {} apt/$DISTRO/$REPO \; 11 | # Publish the debs 12 | freight-cache -c <%= @home %>/freight.conf -v apt/$DISTRO 13 | # Cleanup - no need to keep the debs 14 | find <%= @home %>/rsync_cache/$DISTRO/$REPO -iname '*.deb' -delete || true 15 | -------------------------------------------------------------------------------- /puppet/modules/secure_ssh/templates/script_rsync.erb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | case "$SSH_ORIGINAL_COMMAND" in 6 | *\&*) 7 | echo "Rejected" 8 | ;; 9 | *\(*) 10 | echo "Rejected" 11 | ;; 12 | *\{*) 13 | echo "Rejected" 14 | ;; 15 | *\;*) 16 | echo "Rejected" 17 | ;; 18 | *\<*) 19 | echo "Rejected" 20 | ;; 21 | *\`*) 22 | echo "Rejected" 23 | ;; 24 | *\|*) 25 | echo "Rejected" 26 | ;; 27 | rsync\ --server*) 28 | # Only push to the rsync cache 29 | if [[ `echo "${SSH_ORIGINAL_COMMAND}" | awk '{ print $NF }'` =~ ^rsync_cache/.* ]] ; then 30 | <%= @script_content %> 31 | fi 32 | ;; 33 | *) 34 | echo "Rejected" 35 | ;; 36 | esac 37 | # ERB highlighting looks terrible in this script... 38 | # vim: set ft=sh : # 39 | -------------------------------------------------------------------------------- /puppet/data/nodes/website01.osuosl.theforeman.org.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | profiles::base::monitoring::blackbox_targets: 3 | - name: www.theforeman.org 4 | address: https://www.theforeman.org 5 | module: http 6 | - name: ci.theforeman.org 7 | address: https://ci.theforeman.org 8 | module: http 9 | - name: yum.theforeman.org 10 | address: https://yum.theforeman.org 11 | module: http 12 | - name: deb.theforeman.org 13 | address: https://deb.theforeman.org 14 | module: http 15 | - name: community.theforeman.org 16 | address: https://community.theforeman.org 17 | module: http 18 | - name: projects.theforeman.org 19 | address: https://projects.theforeman.org 20 | module: http 21 | -------------------------------------------------------------------------------- /puppet/spec/classes/discourse_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'discourse' do 4 | on_supported_os.each do |os, facts| 5 | context "on #{os}" do 6 | let(:facts) { facts } 7 | let(:pre_condition) do 8 | <<~PUPPET 9 | class { 'discourse': 10 | developer_emails => 'admin@example.com', 11 | api_key => '1234567890abcdef', 12 | le_account_email => 'infra@example.com', 13 | smtp_address => 'mail.example.com', 14 | smtp_user_name => 'discourse', 15 | smtp_password => 'changeme', 16 | } 17 | PUPPET 18 | end 19 | 20 | it { is_expected.to compile.with_all_deps } 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /puppet/modules/users/files/nalfassi-authorized_keys: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIdjbAFYaXcwkI8nFytAZcEakfTevzIMetCJfbbrRW/E 2 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICD+sdaV1+pnTvNgRY3s5WiBZBq0ZvemdG0ZeXtIcoXN 3 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC8QeHvVqYPyOY7aQv/nEfYzh7qPQPTVUfMdSf1rjj3VOH2VKYRgcNcxbm/cfjW2Btunuppp75JKPfDFyMLCqXC+GrGpYPRhN/KivgGaJ5P5cKnIpKDXwqjX4nswsmxp/YSql7XR+gy4BDy1qgiLbckbHxwT1DC4b7YzPBTMQJrJebnQ/tyzWpnbAfCb2C9aSV/cls55FAmrt0mIGRXRHd9iW4vV4qyUNY+/3mu3df2pNpDycqIO0JAyDsWqWfC/qgFwhf13b/C2mIA/O1AyfhfweWYmjnFGC9PL1hIxJwSw1ITlouS/od0OywfbEQ/t1tsZoa8bJkt44vlDdiuaepCxhhIHrkm00Sf3k6bpZoBW5EoePEwgBmgCo2xXa5vx09OyHGz4oKoZJgbX1a/Z5qHnqfVzPfmcBd6f5gT/6aQsdOf7XgIXIIsLM5hzTTQn2zOz7Yt7akymWlfHI3njUoqjJHz7FN9KNbIURk1/IHQEGqwdUmPx2XoEbmNK0XxX+8= 4 | -------------------------------------------------------------------------------- /puppet/modules/profiles/manifests/jenkins/node.pp: -------------------------------------------------------------------------------- 1 | # @summary A Jenkins node 2 | # 3 | # @param swap_size_mb 4 | # The swap file size in MBs. Will be unmanaged if set to 0 5 | # 6 | # @param unittests 7 | # Should the node be able to run unittests 8 | # 9 | # @param packaging 10 | # Should the node be able to run packaging jobs 11 | class profiles::jenkins::node ( 12 | Integer[0] $swap_size_mb = 8192, 13 | Boolean $unittests = $facts['os']['family'] == 'RedHat', 14 | Boolean $packaging = true, 15 | ) { 16 | class { 'jenkins_node': 17 | unittests => $unittests, 18 | packaging => $packaging, 19 | } 20 | 21 | if $swap_size_mb > 0 { 22 | class { 'jenkins_node::swap': 23 | size_mb => $swap_size_mb, 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vagrant/manifests/default.pp: -------------------------------------------------------------------------------- 1 | node /^jenkins-controller.*/ { 2 | include profiles::jenkins::controller 3 | } 4 | 5 | node /^jenkins-(deb-)?node.*/ { 6 | sudo::conf { 'vagrant': 7 | content => 'vagrant ALL=(ALL) NOPASSWD: ALL', 8 | } 9 | 10 | include profiles::jenkins::node 11 | } 12 | 13 | node /^website.*/ { 14 | include profiles::website 15 | } 16 | 17 | node /^backup.*/ { 18 | include profiles::backup::receiver 19 | } 20 | 21 | node /^redmine.*/ { 22 | include profiles::base 23 | include profiles::redmine 24 | } 25 | 26 | node /^discourse.*/ { 27 | include profiles::discourse 28 | } 29 | 30 | node /^repo-deb.*/ { 31 | include profiles::repo::deb 32 | } 33 | 34 | node /^repo-rpm.*/ { 35 | include profiles::repo::rpm 36 | } 37 | -------------------------------------------------------------------------------- /puppet/modules/freight/manifests/uploader.pp: -------------------------------------------------------------------------------- 1 | # Cheap class to deploy an SSH private key for use in contacting the 2 | # freight server to upload deb packages for signing 3 | # 4 | # @param user 5 | # The username for which to deploy the upload key 6 | # @param workspace 7 | # The workspace where to deploy the key 8 | class freight::uploader ( 9 | String $user, 10 | Stdlib::Absolutepath $workspace, 11 | ) { 12 | secure_ssh::rsync::uploader_key { 'freight': 13 | user => $user, 14 | dir => "${workspace}/deb_key", 15 | manage_dir => true, 16 | } 17 | 18 | secure_ssh::rsync::uploader_key { 'freightstage': 19 | user => $user, 20 | dir => "${workspace}/staging_key", 21 | manage_dir => true, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /puppet/modules/web/templates/rsync_downloads.sh.erb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | case "$SSH_ORIGINAL_COMMAND" in 6 | *\&*) 7 | echo "Rejected" 8 | ;; 9 | *\(*) 10 | echo "Rejected" 11 | ;; 12 | *\{*) 13 | echo "Rejected" 14 | ;; 15 | *\;*) 16 | echo "Rejected" 17 | ;; 18 | *\<*) 19 | echo "Rejected" 20 | ;; 21 | *\`*) 22 | echo "Rejected" 23 | ;; 24 | *\|*) 25 | echo "Rejected" 26 | ;; 27 | rsync\ --server*) 28 | # Permit transfer 29 | $SSH_ORIGINAL_COMMAND 30 | ;; 31 | update-discovery-latest-release) 32 | DISCOVERY_RELEASES="<%= @downloads_directory %>/discovery/releases" 33 | pushd ${DISCOVERY_RELEASES} 34 | rm -f latest 35 | ln -snf $(ls -t | head -n 1) latest 36 | popd 37 | ;; 38 | *) 39 | echo "Rejected" 40 | ;; 41 | esac 42 | -------------------------------------------------------------------------------- /puppet/modules/redmine/templates/database.yml.erb: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | development: 3 | adapter: sqlite3 4 | database: db/development.sqlite3 5 | pool: 5 6 | timeout: 5000 7 | 8 | # Warning: The database defined as "test" will be erased and 9 | # re-generated from your development database when you run "rake". 10 | # Do not set this db to the same as development or production. 11 | test: 12 | adapter: sqlite3 13 | database: db/test.sqlite3 14 | pool: 5 15 | timeout: 5000 16 | 17 | # Database is managed by Puppet 18 | production: 19 | adapter: postgresql 20 | database: <%= @db_name %> 21 | username: <%= @username %> 22 | password: "<%= @db_password %>" 23 | pool: 5 24 | encoding: utf8 25 | min_messages: ERROR 26 | reconnect: false 27 | -------------------------------------------------------------------------------- /puppet/modules/web/templates/deploy-stagingrpm.sh.epp: -------------------------------------------------------------------------------- 1 | <%- | 2 | Stdlib::Absolutepath $home, 3 | Stdlib::Absolutepath $rpm_staging_directory, 4 | | -%> 5 | # Make sure target dir can be created 6 | RPM_PATH=`echo "${SSH_ORIGINAL_COMMAND}" | awk '{ print $NF }'` 7 | PROJECT=`echo $RPM_PATH | /bin/cut -f2 -d/` 8 | RELEASE=`echo $RPM_PATH | /bin/cut -f3 -d/` 9 | mkdir -p <%= $home %>/rsync_cache/$PROJECT/$RELEASE 10 | 11 | # Permit transfer 12 | $SSH_ORIGINAL_COMMAND 13 | 14 | # Publish the repo - stderr/out redirect is required to stop the noninteractive shell from hanging 15 | rsync --recursive --times --verbose --one-file-system --delete-after <%= $home %>/rsync_cache/$PROJECT/$RELEASE <%= $rpm_staging_directory %>/$PROJECT/ 2>&1 >/dev/null ; 16 | -------------------------------------------------------------------------------- /puppet/modules/freight/templates/freight.conf.erb: -------------------------------------------------------------------------------- 1 | # Example Freight configuration. 2 | 3 | # Directories for the Freight library and Freight cache. Your web 4 | # server's document root should be `$VARCACHE`. 5 | VARLIB="<%= @stagedir %>" 6 | VARCACHE="<%= @webdir %>" 7 | 8 | # Default `Origin` and `Label` fields for `Release` files. 9 | ORIGIN="TheForeman" 10 | LABEL="TheForeman" 11 | 12 | # GPG key to use to sign repositories. This is required by the `apt` 13 | # repository provider. Use `gpg --gen-key` (see `gpg`(1) for more 14 | # details) to generate a key and put its email address here. 15 | # 2021 16 | GPG="FD7AAC8A" 17 | 18 | # Follow symlinks in VARLIB 19 | SYMLINKS="on" 20 | 21 | # x86 plus supported ARM platforms 22 | ARCHS="i386 amd64 armhf arm64" 23 | -------------------------------------------------------------------------------- /puppet/modules/users/files/pcreech-authorized_keys: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCdCWFgGX4wBdwHaWWiWtuqqRH3ANL3cOi2DqDSGkqWWwl9iQkfd5aNFbWR9x3t312wteW9nGD/s2fzvtMuPWklsgMkOHX53419UfzY7hBGrIMJCcTSpTTaDxOOlUJhagXbXI9T9hib/21SgkyC0csEiW9onP6fsIOtnLf9Seubtu8qA2TOvOzw7P/mGKTJYX279uIoEDq/Gx8thiUrQ71QrFZx4Wod3QULOOs5jUtxKvtv3oIhUFG0E21Sv1WZekC8n5+EcLIke4FolX3e2zgkJuFhu2cK1CJxwSvVBfE6Bu4LLt0nrcOgxbkWtKKXRNHT8RjeHxi9XLn3RoMfx3LGCFgxw6MJrENvBmrHmRCn8YV6EDJ1+w1QYiQ1zRqvJ/jzGt0lcya1uvQBsejy5wrcN4v3A1Zp9lh/+IfanQu/8VvyDora31I+4H6UQX0qAFKFI9U7+25PaEQrRBSofmQYHjVed3guBuTUkOy/NigINLa2WUHtlfUIIhwUcuulMN5iPIYkgq2Xw8/FGCHo9J81IjETW0vC5dUjDerTPB7oUo+oDobnXYYPt+UN21ZYeywp/s2wr9gjL1Au4F79TO3/BY3PwxSOHrpG2MLKNK2YLp7M5+UWLM3P9MXJ3BFicFJU7OLLslRAUyAZoxRI7if1dygzyXhaxwq9nH6qpt65DQ== pcreech@redhat.com 2 | -------------------------------------------------------------------------------- /puppet/modules/users/files/zhunting-authorized_keys: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCetpDeJIcHmc4/cez8BMiqyEElY7eXYQcmXGXnBuvjH7eX8ezovFWi5o/L4699O3V1ohqOMwkuUfvuCVsjDmnlOoVp463ZdSl8RTN4mFeKf9jFoA4HROCKprgHrFV6dIo2JVQ8E15sRrLTayghyMeFwanLSm/8MZ6auqV7w4IVlVVN9GOp5Xh/k5b61w1RCqYlojPOY2xBG5CNIBCdV1FyPbJqfaCUJSNoacKozM9i9uUvxX+iSfwvUZnsl2Kepc3sl/L+t8CHeEk/vAnjecwHiXiolJWsGensFJcRyojYm26KeH/jTdz32j4I3p5XH4x8WN2M26F+NpUAz3DSh32sQWoD8TjyVAbtb5VGUjGRFriyTLgb3VC9tGaueE+UtV3p8/nARTmcbsYP47xz6X+NwNvFNHDLfkDCfJNxh0pzkTm186KXB6eDZQcALLzgZJyflzLAUZqDA3Lnf0Hrk5YJKH6EMm0pHXkzCWvZV5tw0j26+whc7INHsXsyDNDfuannmTlqITEqwIUFeaGllF0EExZ8MZz5uYUo/WTLfEzyl5yYKFaaYfWrb5nYdXOWWFOAiBxF21cJ3MnTHYD9L+GUa7EAIujOzdxsu7HsG69vqT/K5Ol39Qka8uPIHSFqpxqt+vQ0gha7NMAOTJwH3oyIBtKXrbQEtBU2w+zMK0G0Cw== zjhuntingtonmeath@gmail.com 2 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/manifests/swap.pp: -------------------------------------------------------------------------------- 1 | # @summary Create a swapfile 2 | # 3 | # @param file 4 | # The location of the swapfile 5 | # 6 | # @param size_mb 7 | # The size in MBs 8 | class jenkins_node::swap ( 9 | Stdlib::Absolutepath $file = '/swap', 10 | Integer[0] $size_mb = 2048, 11 | ) { 12 | exec { 'create-swap': 13 | command => "/bin/dd if=/dev/zero of=${file} bs=1M count=${size_mb}", 14 | creates => $file, 15 | } -> 16 | exec { 'enable-swap': 17 | command => "/sbin/mkswap ${file} && /sbin/swapon ${file}", 18 | unless => "/sbin/swapon -s | grep ${file}", 19 | } -> 20 | mounttab { 'swap': 21 | ensure => present, 22 | device => $file, 23 | fstype => swap, 24 | options => 'defaults', 25 | provider => augeas, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /puppet/modules/rbenv/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # @summary Install rbenv 2 | # 3 | # This module is based on jdowning/rbenv but uses system packages instead of git checkouts. 4 | # 5 | # @param install_dir 6 | # The path where rbenv builds will be installed to 7 | # 8 | # @param env 9 | # Default Environment variables to use when installing a build 10 | class rbenv ( 11 | Stdlib::Absolutepath $install_dir = '/usr/local/rbenv', 12 | Array[String[1]] $env = [], 13 | Optional[String[1]] $user = undef, 14 | ) { 15 | $packages = $facts['os']['family'] ? { 16 | 'RedHat' => ['rbenv', 'ruby-build-rbenv'], 17 | 'Debian' => ['rbenv', 'ruby-build'], # ruby-build contains the rbenv plugin 18 | undef => [], 19 | } 20 | 21 | class { 'rbenv::install': 22 | packages => $packages, 23 | } 24 | 25 | contain rbenv::install 26 | } 27 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/manifests/packaging.pp: -------------------------------------------------------------------------------- 1 | # @api private 2 | class jenkins_node::packaging ( 3 | Boolean $uploader, 4 | Stdlib::Absolutepath $homedir, 5 | Stdlib::Absolutepath $workspace, 6 | ) { 7 | # CLI JSON parser 8 | package { 'jq': 9 | ensure => installed, 10 | } 11 | 12 | case $facts['os']['family'] { 13 | 'RedHat': { 14 | class { 'jenkins_node::packaging::rpm': 15 | homedir => $homedir, 16 | user => 'jenkins', 17 | workspace => $workspace, 18 | } 19 | contain jenkins_node::packaging::rpm 20 | } 21 | 'Debian': { 22 | class { 'jenkins_node::packaging::debian': 23 | uploader => $uploader, 24 | user => 'jenkins', 25 | workspace => $workspace, 26 | } 27 | contain jenkins_node::packaging::debian 28 | } 29 | default: {} 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /puppet/data/vagrant.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | discourse::developer_emails: 'admin@example.com' 3 | discourse::api_key: '1234567890abcdef' 4 | discourse::le_account_email: 'infra@example.com' 5 | discourse::smtp_address: 'mail.example.com' 6 | discourse::smtp_user_name: 'discourse' 7 | discourse::smtp_password: 'changeme' 8 | 9 | restic::password: "SomethingVerySecret" 10 | 11 | profiles::backup::receiver: 12 | - redmine 13 | - jenkins-master 14 | 15 | profiles::jenkins::controller::hostname: "%{facts.networking.fqdn}" 16 | profiles::jenkins::controller::https: false 17 | profiles::jenkins::controller::jenkins_job_builder: true 18 | profiles::jenkins::controller::jenkins_job_builder_password: "changeme" 19 | profiles::jenkins::controller::jenkins_job_builder_username: "admin" 20 | 21 | profiles::jenkins::node::swap_size_mb: 0 22 | 23 | redmine::https: false 24 | redmine::anubis: false 25 | 26 | web::https: false 27 | -------------------------------------------------------------------------------- /puppet/modules/profiles/lib/facter/external_ips.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'resolv' 3 | 4 | Facter.add(:external_ip4) do 5 | setcode do 6 | begin 7 | Resolv::DNS.open do |dns| 8 | addr = dns.getresource("ipv4.icanhazip.com", Resolv::DNS::Resource::IN::A).address.to_s 9 | Net::HTTP.start(addr) do |http| 10 | http.get('http://ipv4.icanhazip.com/').body.chomp 11 | end 12 | end 13 | rescue 14 | nil 15 | end 16 | end 17 | end 18 | 19 | Facter.add(:external_ip6) do 20 | setcode do 21 | begin 22 | Resolv::DNS.open do |dns| 23 | addr = dns.getresource("ipv6.icanhazip.com", Resolv::DNS::Resource::IN::AAAA).address.to_s 24 | Net::HTTP.start(addr) do |http| 25 | http.get('http://ipv6.icanhazip.com/').body.chomp 26 | end 27 | end 28 | rescue 29 | nil 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /puppet/modules/profiles/manifests/base.pp: -------------------------------------------------------------------------------- 1 | # @summary A base profile for all machines 2 | class profiles::base ( 3 | ) { 4 | include motd 5 | include openvox_repo 6 | include puppet 7 | include ssh 8 | include systemd 9 | include unattended 10 | if $facts['os']['family'] == 'RedHat' { 11 | package { 'ntp': 12 | ensure => absent, 13 | before => Class['chrony'], 14 | } 15 | include chrony 16 | } else { 17 | include ntp 18 | } 19 | 20 | # Ensure REX can log in 21 | class { 'foreman_proxy::plugin::remote_execution::ssh_user': 22 | manage_user => true, 23 | } 24 | 25 | include profiles::base::monitoring 26 | 27 | file { '/usr/local/sbin/reboot-inactive-system': 28 | ensure => file, 29 | owner => 'root', 30 | group => 'root', 31 | mode => '0755', 32 | content => file("${module_name}/base/reboot-inactive-system"), 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /puppet/modules/secure_ssh/manifests/rsync/uploader_key.pp: -------------------------------------------------------------------------------- 1 | # @summary Define which deploys the key for a specific user 2 | # 3 | # @param name 4 | # Name of the key 5 | # 6 | # @param user 7 | # User to own the key 8 | # 9 | # @param dir 10 | # Directory to store the key in 11 | # 12 | # @param mode 13 | # Mode of $dir 14 | # 15 | # @param manage_dir 16 | # Whether or not to manage $dir 17 | # 18 | define secure_ssh::rsync::uploader_key ( 19 | String[1] $user, 20 | Stdlib::Absolutepath $dir = "/home/${user}/.ssh", 21 | Stdlib::Filemode $mode = '0600', 22 | Boolean $manage_dir = false, 23 | String[1] $ensure = 'present', 24 | ) { 25 | secure_ssh::uploader_key { $name: 26 | ensure => $ensure, 27 | user => $user, 28 | dir => $dir, 29 | mode => $mode, 30 | manage_dir => $manage_dir, 31 | ssh_key_name => "rsync_${name}_key", 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /puppet/modules/ssh/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class ssh { 2 | $ssh_service = $facts['os']['family'] ? { 3 | 'RedHat' => 'sshd', 4 | default => 'ssh', 5 | } 6 | 7 | file { '/etc/ssh/sshd_config': 8 | ensure => file, 9 | owner => 'root', 10 | group => 'root', 11 | notify => Service[$ssh_service], 12 | } 13 | 14 | service { $ssh_service: 15 | ensure => running, 16 | enable => true, 17 | } 18 | 19 | Sshd_config <| |> ~> Service[$ssh_service] 20 | 21 | sshd_config { 'PermitRootLogin': 22 | ensure => present, 23 | value => 'without-password', 24 | } 25 | 26 | sshd_config { 'PasswordAuthentication': 27 | ensure => present, 28 | value => 'no', 29 | } 30 | 31 | sshd_config { 'StrictModes': 32 | ensure => present, 33 | value => 'yes', 34 | } 35 | 36 | # Log SSH key fingerprints 37 | sshd_config { 'LogLevel': 38 | ensure => present, 39 | value => 'VERBOSE', 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /puppet/modules/web/manifests/vhost/stagingdeb.pp: -------------------------------------------------------------------------------- 1 | # @summary Set up the stagingdeb vhost 2 | # @api private 3 | class web::vhost::stagingdeb ( 4 | String $user = 'freightstage', 5 | Stdlib::Absolutepath $home = "/home/${user}", 6 | Stdlib::Absolutepath $stagedir = "/var/www/${user}", 7 | ) { 8 | # Manual step: each user needs the GPG key in it's keyring 9 | freight::user { 'staging': 10 | user => $user, 11 | home => $home, 12 | webdir => '/var/www/vhosts/stagingdeb/htdocs', 13 | stagedir => $stagedir, 14 | vhost => 'stagingdeb', 15 | cron_matches => 'all', 16 | } 17 | 18 | secure_ssh::rsync::receiver_setup { $user: 19 | user => $user, 20 | homedir => $home, 21 | homedir_mode => '0750', 22 | foreman_search => 'host.hostgroup = Debian and (name = external_ip4 or name = external_ip6)', 23 | script_content => template('freight/rsync.erb'), 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ansible/roles/Jimdo.fastly"] 2 | path = ansible/roles/Jimdo.fastly 3 | url = https://github.com/evgeni/ansible-fastly 4 | branch = theforeman 5 | [submodule "puppet/test_modules/yumrepo_core"] 6 | path = puppet/test_modules/yumrepo_core 7 | url = https://github.com/puppetlabs/puppetlabs-yumrepo_core.git 8 | [submodule "puppet/test_modules/cron_core"] 9 | path = puppet/test_modules/cron_core 10 | url = https://github.com/puppetlabs/puppetlabs-cron_core.git 11 | [submodule "puppet/test_modules/selinux_core"] 12 | path = puppet/test_modules/selinux_core 13 | url = https://github.com/puppetlabs/puppetlabs-selinux_core 14 | [submodule "puppet/test_modules/augeas_core"] 15 | path = puppet/test_modules/augeas_core 16 | url = https://github.com/puppetlabs/puppetlabs-augeas_core 17 | [submodule "puppet/test_modules/sshkeys_core"] 18 | path = puppet/test_modules/sshkeys_core 19 | url = https://github.com/puppetlabs/puppetlabs-sshkeys_core 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Foreman Infrastructure 2 | 3 | This repo contains puppet modules that are used to manage infrastructure used by the Foreman project. These modules manage many different pieces of software, including Jenkins nodes, package build machines, the Jenkins frontend, as well as an internal Foreman instance and puppetserver. 4 | 5 | View the [documentation](https://theforeman.github.io/foreman-infra). 6 | 7 | ## Puppet module directories 8 | The `puppet` folder contains the following directories for Puppet modules: 9 | 10 | ### `external_modules` 11 | Externally maintained modules. Preferably straight from the [Puppet Forge](https://forge.puppet.com) but potentially via git. 12 | 13 | ### `modules` 14 | Our own custom modules, relevant only in this particular repository for this particular setup. 15 | 16 | ### `test_modules` 17 | Modules relevant only in the Puppet spec tests, e.g. Puppet's core modules, that are coming bundled with the agent in a real setup. 18 | -------------------------------------------------------------------------------- /puppet/modules/profiles/manifests/repo/deb.pp: -------------------------------------------------------------------------------- 1 | # @summary A profile for the debian repo machines 2 | # 3 | # @param stable 4 | # Latest release that users expect 5 | class profiles::repo::deb ( 6 | String[1] $stable, 7 | ) { 8 | contain web 9 | 10 | contain web::vhost::archivedeb 11 | 12 | class { 'web::vhost::deb': 13 | stable => $stable, 14 | } 15 | contain web::vhost::deb 16 | 17 | contain web::vhost::stagingdeb 18 | 19 | include profiles::backup::sender 20 | 21 | restic::repository { 'repo_deb': 22 | backup_cap_dac_read_search => true, 23 | backup_path => [ 24 | $web::vhost::deb::home, 25 | $web::vhost::deb::stagedir, 26 | $web::vhost::archivedeb::home, 27 | $web::vhost::archivedeb::stagedir, 28 | ], 29 | backup_post_cmd => [ 30 | '-/bin/bash -c "/usr/local/bin/restic-prometheus-exporter | sponge /var/lib/prometheus/node-exporter/restic.prom"', 31 | ], 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_job_builder/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # Class to install, configure, and maintain JJB, as well 2 | # as to deploy the actual jobs to jenkins 3 | # 4 | # Loosely based on 5 | # https://git.openstack.org/cgit/openstack-infra/puppet-jenkins/tree/manifests/job_builder.pp 6 | # and should probably be kept up to date from there 7 | 8 | # @param ensure 9 | # The package state to ensure. Can be installed or a specific version. 10 | # 11 | # @param configs 12 | # A hash of: 'name' => 'url', 'username', 'password' 13 | # The name matches the name under files/ of the config directory. 14 | # 15 | class jenkins_job_builder ( 16 | String[1] $ensure = installed, 17 | Hash[String, Hash] $configs = {}, 18 | ) { 19 | contain jenkins_job_builder::install 20 | 21 | $configs.each |$config, $params| { 22 | jenkins_job_builder::config { $config: 23 | * => $params, 24 | subscribe => Class['jenkins_job_builder::install'], 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /puppet/modules/web/manifests/vhost/archivedeb.pp: -------------------------------------------------------------------------------- 1 | # @summary Set up the archivedeb vhost 2 | # @api private 3 | class web::vhost::archivedeb ( 4 | String $user = 'freightarchive', 5 | Stdlib::Absolutepath $home = "/home/${user}", 6 | Stdlib::Absolutepath $stagedir = "/var/www/${user}", 7 | ) { 8 | # Manual step: each user needs the GPG key in it's keyring 9 | freight::user { 'archive': 10 | user => $user, 11 | home => $home, 12 | webdir => '/var/www/vhosts/archivedeb/htdocs', 13 | stagedir => $stagedir, 14 | vhost => 'archivedeb', 15 | cron_matches => [], 16 | cron_enable => false, 17 | } 18 | 19 | secure_ssh::rsync::receiver_setup { $user: 20 | user => $user, 21 | homedir => $home, 22 | homedir_mode => '0750', 23 | foreman_search => 'host.hostgroup = Debian and (name = external_ip4 or name = external_ip6)', 24 | script_content => template('freight/rsync.erb'), 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /puppet/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec-puppet' 2 | require 'rspec-puppet-facts' 3 | include RspecPuppetFacts 4 | 5 | add_custom_fact :root_home, '/root' 6 | add_custom_fact :service_provider, 'systemd' 7 | add_custom_fact :sudoversion, '1.8.23' 8 | 9 | def on_supported_os(opts = {}) 10 | opts[:supported_os] ||= [ 11 | { 12 | 'operatingsystem' => 'CentOS', 13 | 'operatingsystemrelease' => ['9'], 14 | }, 15 | { 16 | 'operatingsystem' => 'Debian', 17 | 'operatingsystemrelease' => ['11', '12'], 18 | }, 19 | ] 20 | super(opts) 21 | end 22 | 23 | base_dir = File.dirname(File.expand_path(__dir__)) 24 | 25 | RSpec.configure do |c| 26 | c.module_path = [File.join(base_dir, 'modules'), File.join(base_dir, 'external_modules'), File.join(base_dir, 'test_modules')].join(File::PATH_SEPARATOR) 27 | c.hiera_config = File.join(base_dir, 'hiera.yaml') 28 | c.environmentpath = base_dir 29 | c.strict_variables = true 30 | end 31 | -------------------------------------------------------------------------------- /puppet/modules/freight/templates/archivedeb-HEADER.html.epp: -------------------------------------------------------------------------------- 1 |

The Debian Foreman archive repo

2 | 3 |

You will find them at:

4 | 5 |
deb http://archivedeb.theforeman.org/ <codename> <component>
6 | 7 |

Currently we have

8 | 9 |
10 | deb http://archivedeb.theforeman.org/ buster <version>
11 | deb http://archivedeb.theforeman.org/ bionic <version>
12 | 
13 | deb http://archivedeb.theforeman.org/ plugins <version>
14 | 
15 | 16 |

An example of how to add it:

17 | 18 |
19 | wget https://archivedeb.theforeman.org/foreman.asc -O /etc/apt/trusted.gpg.d/foreman.asc
20 | echo "deb http://archivedeb.theforeman.org/ buster 1.24" > /etc/apt/sources.list.d/foreman.list
21 | echo "deb http://archivedeb.theforeman.org/ plugins 1.24" >> /etc/apt/sources.list.d/foreman.list
22 | 
23 | 24 |

If you have any issues, you can find ways to reach us on our Support page.

25 | -------------------------------------------------------------------------------- /puppet/modules/profiles/manifests/repo/rpm.pp: -------------------------------------------------------------------------------- 1 | # @summary A profile for the rpm repo machines 2 | # 3 | # @param stable_foreman 4 | # Latest Foreman release that users expect 5 | class profiles::repo::rpm ( 6 | String[1] $stable_foreman, 7 | ) { 8 | contain web 9 | 10 | class { 'web::vhost::yum': 11 | stable => $stable_foreman, 12 | } 13 | contain web::vhost::yum 14 | 15 | contain web::vhost::stagingyum 16 | 17 | class { 'web::vhost::rpm': 18 | stable_foreman => $stable_foreman, 19 | } 20 | contain web::vhost::rpm 21 | 22 | contain web::vhost::stagingrpm 23 | 24 | include profiles::backup::sender 25 | 26 | restic::repository { 'repo_rpm': 27 | backup_cap_dac_read_search => true, 28 | backup_path => [$web::vhost::rpm::rpm_directory, $web::vhost::yum::yum_directory], 29 | backup_post_cmd => [ 30 | '-/bin/bash -c "/usr/local/bin/restic-prometheus-exporter | sponge /var/lib/prometheus/node-exporter/restic.prom"', 31 | ], 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /puppet/modules/web/files/downloads-HEADER.html: -------------------------------------------------------------------------------- 1 | downloads.theforeman.org 2 |

downloads.theforeman.org

3 | 4 | 5 |

Official downloads

6 | 7 |

Official tarballs of core Foreman projects are made available here for download and repackaging.

8 | 9 |

Packages of our projects are also available at deb.theforeman.org and yum.theforeman.org, while gems are published to rubygems.org.

10 | 11 |

Support

12 | 13 |

You can find installation instructions here, but we strongly recommend using our Installer. 14 | 15 |

If you have any issues, see Support for community guidelines and support options.

16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /puppet/spec/classes/secretsgit_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'secretsgit' do 4 | on_supported_os.each do |os, facts| 5 | context "on #{os}" do 6 | let :facts do 7 | facts 8 | end 9 | 10 | context 'default' do 11 | it { is_expected.to contain_group 'secretsgit' } 12 | it do is_expected.to contain_file('/srv/secretsgit').with( 13 | ensure: 'directory', 14 | owner: 'root', 15 | group: 'secretsgit', 16 | mode: '2770' 17 | ) 18 | end 19 | end 20 | 21 | context "with userlistt" do 22 | let(:params) do 23 | { 24 | users: ['user1', 'user2'], 25 | } 26 | end 27 | let(:pre_condition) { 'user{["user1", "user2"]: ensure => present}' } 28 | it { is_expected.to contain_user('user1').with_groups(['secretsgit']) } 29 | it { is_expected.to contain_user('user2').with_groups(['secretsgit']) } 30 | end 31 | end 32 | 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /docs/backups.md: -------------------------------------------------------------------------------- 1 | # Backups 2 | 3 | For backups, [restic](https://restic.net/) is used. 4 | The SFTP backend is used, for simplicity. 5 | Both sender and receiver profile classes exist (`profiles::backup::sender` and `profiles::backup::receiver`). 6 | 7 | In addition to that there are private Hiera data files on the puppetserver in `/etc/puppetlabs/puppet/data` which are not tracked in git. 8 | This contains the backup passwords to encrypt the data. 9 | 10 | ## Adding a receiver target 11 | 12 | The `profiles::backup::receiver` class has a parameter `targets` which is an array of target names. 13 | This gets converted into instances of `profiles::backup::receiver::target`. 14 | Add the short hostname to this array in `data/common.yaml` 15 | 16 | ## Adding a sender 17 | 18 | First, add a password in `/etc/puppetlabs/puppet/data/nodes/HOSTNAME.yaml` on the puppetserver: 19 | 20 | ```yaml 21 | --- 22 | restic::password: "ThePassword" 23 | ``` 24 | 25 | A password can be generated using `pwgen -y -s 25`. 26 | 27 | TODO: securely store this 28 | TODO: shared storage? 29 | -------------------------------------------------------------------------------- /puppet/modules/profiles/manifests/puppetserver.pp: -------------------------------------------------------------------------------- 1 | # @summary A Puppetserver with Foreman integration 2 | class profiles::puppetserver { 3 | include puppet 4 | include puppet::server 5 | 6 | include foreman::repo 7 | class { 'foreman_proxy': 8 | puppet => true, 9 | puppetca => true, 10 | } 11 | include foreman_proxy::plugin::ansible 12 | include foreman_proxy::plugin::remote_execution::script 13 | 14 | class { 'deploy': 15 | user => $puppet::server_environments_owner, 16 | } 17 | 18 | include profiles::backup::sender 19 | 20 | restic::repository { 'puppetserver': 21 | backup_cap_dac_read_search => true, 22 | backup_path => [ 23 | '/etc/puppetlabs/puppet/data', 24 | '/etc/puppetlabs/puppet/ssl', 25 | '/opt/puppetlabs/server/data/puppetserver/ssh', 26 | '/opt/puppetlabs/server/data/puppetserver/foreman_cache_data', 27 | ], 28 | backup_post_cmd => [ 29 | '-/bin/bash -c "/usr/local/bin/restic-prometheus-exporter | sponge /var/lib/prometheus/node-exporter/restic.prom"', 30 | ], 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy docs to Pages 2 | 3 | on: 4 | push: 5 | branches: ['master'] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | concurrency: 14 | group: "pages" 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | deploy: 19 | environment: 20 | name: github-pages 21 | url: ${{ steps.deployment.outputs.page_url }} 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v6 26 | - name: Set up Python 27 | uses: actions/setup-python@v6 28 | with: 29 | python-version: "3" 30 | - name: Install sphinx 31 | run: pip install mkdocs 32 | - name: Build docs 33 | run: mkdocs build 34 | - name: Set up Pages 35 | uses: actions/configure-pages@v5 36 | - name: Upload artifact 37 | uses: actions/upload-pages-artifact@v4 38 | with: 39 | path: site 40 | - name: Deploy to GitHub Pages 41 | id: deployment 42 | uses: actions/deploy-pages@v4 43 | -------------------------------------------------------------------------------- /puppet/modules/profiles/manifests/backup/sender.pp: -------------------------------------------------------------------------------- 1 | # @summary The backup sender 2 | # 3 | # @param host 4 | # The target backup host 5 | # @param ssh_key 6 | # The SSH key to use as a known host 7 | # @param ssh_key_type 8 | # The type of SSH key 9 | # @param username 10 | # The remote username 11 | class profiles::backup::sender ( 12 | Stdlib::Host $host, 13 | String[1] $ssh_key, 14 | String[1] $ssh_key_type, 15 | String[1] $username, 16 | ) { 17 | require restic 18 | 19 | $ssh_dir = "${restic::user_homedir}/.ssh" 20 | 21 | file { $ssh_dir: 22 | ensure => directory, 23 | owner => $restic::user, 24 | group => $restic::group, 25 | mode => '0700', 26 | } 27 | 28 | file { "${ssh_dir}/id_rsa": 29 | ensure => file, 30 | owner => $restic::user, 31 | group => $restic::group, 32 | mode => '0600', 33 | content => ssh::keygen($username), 34 | } 35 | 36 | sshkey { $restic::host: 37 | ensure => present, 38 | type => $ssh_key_type, 39 | key => $ssh_key, 40 | } 41 | 42 | User<| title == $restic::user |> { groups +> ['prometheus'] } 43 | } 44 | -------------------------------------------------------------------------------- /puppet/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | RSpec::Core::RakeTask.new(:spec) do |t| 3 | t.pattern = Rake::FileList['spec/{aliases,classes,defines,functions,hosts,integration,plans,tasks,type_aliases,types,unit}/**/*_spec.rb'] 4 | end 5 | 6 | require 'puppet-syntax/tasks/puppet-syntax' 7 | PuppetSyntax.manifests_paths = ['modules/*/manifests/**/*.pp'] 8 | PuppetSyntax.templates_paths = ['modules/*/templates/**/*.{erb,epp}'] 9 | PuppetSyntax.exclude_paths = ["vendor/**/*"] 10 | 11 | require 'puppet-lint/tasks/puppet-lint' 12 | # those two types totally make sense, but require lots of updates first 13 | PuppetLint.configuration.disable_parameter_documentation 14 | PuppetLint.configuration.disable_parameter_types 15 | PuppetLint::RakeTask.new :lint do |config| 16 | config.pattern = PuppetSyntax.manifests_paths 17 | config.fail_on_warnings = true 18 | end 19 | 20 | desc 'Run puppet-lint and fix issues automatically' 21 | PuppetLint::RakeTask.new(:lint_fix) do |config| 22 | config.pattern = PuppetSyntax.manifests_paths 23 | config.fix = true 24 | end 25 | 26 | task :test => [:syntax, :lint, :spec] 27 | 28 | task :default => [:test] 29 | -------------------------------------------------------------------------------- /puppet/hiera.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 5 3 | defaults: # Used for any hierarchy level that omits these keys. 4 | # The default value for "datadir" is "data" under the same directory as the hiera.yaml 5 | # file (this file) 6 | # When specifying a datadir, make sure the directory exists. 7 | # See https://puppet.com/docs/puppet/latest/environments_about.html for further details on environments. 8 | datadir: data 9 | data_hash: yaml_data 10 | 11 | hierarchy: 12 | - name: "Per-node data" 13 | path: "nodes/%{trusted.certname}.yaml" 14 | 15 | - name: "Per-OS major defaults" 16 | path: "os/%{facts.os.name}-%{facts.os.release.major}.yaml" 17 | 18 | - name: "Per-OS defaults" 19 | path: "os/%{facts.os.name}.yaml" 20 | 21 | - name: "Per-OS family major defaults" 22 | path: "osfamily/%{facts.os.family}-%{facts.os.release.major}.yaml" 23 | 24 | - name: "Per-OS family defaults" 25 | path: "osfamily/%{facts.os.family}.yaml" 26 | 27 | - name: "Common data that changes often" 28 | paths: 29 | - "common.yaml" 30 | 31 | - name: "Internal data that should rarely change" 32 | paths: 33 | - "internal.yaml" 34 | -------------------------------------------------------------------------------- /puppet/modules/users/files/evgeni-authorized_keys: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfSQjbt711By0teY/RbvX1DD7DMYG59fk1Vpeck83O5Os0/Kis8sSxjXr32lKAuNFpzdkPUWsFUBiHab5njWRDbju3SxsKhyMR1N5Vse1AVUAbhgrKtuh/TrV4caUKMtcSzSmxpMrr3KLdxZ7nOiuDstgmV632XvsEJ9m2yTDxuYAz/lw7Fu5vvqIin0n+RLHdLcOuz4sm607VeRiZkInpy5XqRvAcleFe0HV4n+w5vZaJi0p5xlBmr1iw9Z/s0XJBlvrFXeB1ONIXeK+1e2JwXMJSOONR4yG+wIcW2gAaZqfB0zw+Pr/Ru0behbvjikXXaQiMfDLPuat6sz/qLv6t egolov@kangae.egolov.redhat.com 2 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCbPYy7pq/0s5rb2cCboG9/YJdoCWlcvBl9BugEPZGkDrTHkjE6F7Bz+gnQR/ocjM2tGZRwmMQR2chMNnV1Z+JY1zcY9XqNme5M/ZuSjsNHK2fGmQvmGHEm60hgfwsX17tx7q/tTmWQVUu12BC4dtRS8TBCtJOj/exCd+nBQaO2zE0TLlIw7jhW8c7MxQaFIMw/sLk5pllBhjgEkItea9JaiSnM2smmJDvxcLbE6ZjXFbF3e6lxumVraO92q+cMUU2zvwfg2BktasQFvQ47E7AXzRKOi45+ZNu3UYc1ljG7LQ6F7KuYEp5J2k+W1ZKe/no1UvNWWZIb1dwPLBH+SGH3XTIwJvJf0B3sEGL8LCUin5FmekcjTBcp5rJw1ogBjmd8qvALZBnzvOnTAwJOLWHTvmXVqmqfkd416MdGoowNw69WZTVxWjO7aD+4/CVXDOBRMvy+FYs0y0m9q7g5xSJwgnZJq/j/V1moFTbwp+To0si/FIkXSllM20V+PrqzwqCYT4tPHRgbBJGNt/7De0G4g2vkWMszjzTemxW5p5nsvqbIw/ZstHTnAuCtwf3jiTdi3yy34dhJuoPdSprjZHTvYynsXUL2QAPGjqNT2p46AN4XL+EcyzT6PpDJ/L3ayWfsoETKCkcg+IFIoaqHf7d/Qqcb0RX9EWt9BftkBdAYYQ== openpgp:0xBBA2617A 3 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | puppet: 7 | runs-on: ubuntu-latest 8 | name: Puppet 9 | defaults: 10 | run: 11 | working-directory: puppet 12 | steps: 13 | - uses: actions/checkout@v6 14 | with: 15 | submodules: true 16 | - name: Setup ruby 17 | uses: ruby/setup-ruby@v1 18 | with: 19 | ruby-version: '3.2' 20 | bundler-cache: true 21 | working-directory: puppet 22 | - name: Install yajl-tools 23 | run: sudo apt-get install -y yajl-tools 24 | - name: Setup g10k 25 | run: | 26 | wget https://github.com/xorpaul/g10k/releases/download/v0.8.9/g10k-linux-amd64.zip 27 | unzip g10k-linux-amd64.zip 28 | - name: Install modules using g10k 29 | run: ./g10k -cachedir .g10k/cache -puppetfile 30 | - name: Verify dependencies are compatible 31 | run: bundle exec ./check_dependencies 32 | - name: Run syntax 33 | run: bundle exec rake syntax 34 | - name: Run lint 35 | run: bundle exec rake lint 36 | - name: Run tests 37 | run: bundle exec rake spec 38 | -------------------------------------------------------------------------------- /puppet/modules/profiles/manifests/discourse.pp: -------------------------------------------------------------------------------- 1 | # @summary Manage a Discourse server 2 | # @see https://github.com/discourse/discourse/blob/main/docs/INSTALL-cloud.md 3 | class profiles::discourse { 4 | yumrepo { 'docker-ce-stable': 5 | descr => 'Docker CE Stable - $basearch', 6 | baseurl => 'https://download.docker.com/linux/centos/$releasever/$basearch/stable', 7 | gpgkey => 'https://download.docker.com/linux/centos/gpg', 8 | } 9 | 10 | stdlib::ensure_packages(['docker-ce'], { require => Yumrepo['docker-ce-stable'] }) 11 | 12 | service { 'docker': 13 | ensure => 'running', 14 | enable => true, 15 | require => Package['docker-ce'], 16 | } 17 | 18 | include discourse 19 | $backup_path = ["${discourse::root}/shared/standalone/backups"] 20 | 21 | include profiles::backup::sender 22 | 23 | restic::repository { 'discourse': 24 | backup_cap_dac_read_search => true, 25 | backup_path => $backup_path, 26 | backup_pre_cmd => ['+/usr/bin/docker exec app discourse backup'], 27 | backup_post_cmd => [ 28 | '-/bin/bash -c "/usr/local/bin/restic-prometheus-exporter | sponge /var/lib/prometheus/node-exporter/restic.prom"', 29 | ], 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /vagrant/hiera.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # This is a copy of puppet/hiera.yaml but with an additional vagrant layer 3 | version: 5 4 | defaults: # Used for any hierarchy level that omits these keys. 5 | # The default value for "datadir" is "data" under the same directory as the hiera.yaml 6 | # file (this file) 7 | # When specifying a datadir, make sure the directory exists. 8 | # See https://puppet.com/docs/puppet/latest/environments_about.html for further details on environments. 9 | datadir: data 10 | data_hash: yaml_data 11 | 12 | hierarchy: 13 | - name: "Per-node data" 14 | path: "nodes/%{trusted.certname}.yaml" 15 | 16 | - name: "Per-OS major defaults" 17 | path: "os/%{facts.os.name}-%{facts.os.release.major}.yaml" 18 | 19 | - name: "Per-OS defaults" 20 | path: "os/%{facts.os.name}.yaml" 21 | 22 | - name: "Per-OS family major defaults" 23 | path: "osfamily/%{facts.os.family}-%{facts.os.release.major}.yaml" 24 | 25 | - name: "Per-OS family defaults" 26 | path: "osfamily/%{facts.os.family}.yaml" 27 | 28 | - name: "Other YAML hierarchy levels" 29 | paths: 30 | - "vagrant.yaml" 31 | - "common.yaml" 32 | 33 | - name: "Internal data that should rarely change" 34 | paths: 35 | - "internal.yaml" 36 | -------------------------------------------------------------------------------- /puppet/modules/secretsgit/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # @summary A shared secret storage 2 | # 3 | # The shared secret storage creates a group where all users have access 4 | # to a shared directory for use with gopass. It is then expected that 5 | # there are several git repositories within that directory. Each 6 | # repository is created using: 7 | # `git init --bare --shared=group therepo.git` 8 | # 9 | # @param group The group every user should be part of 10 | # @param path The location to store the git repositories 11 | # @param users The users who should be part of the group 12 | # 13 | # @see https://www.gopass.pw/ 14 | class secretsgit ( 15 | String $group = 'secretsgit', 16 | Stdlib::Absolutepath $path = '/srv/secretsgit', 17 | Array[String] $users = [], 18 | ) { 19 | file { '/etc/gitconfig': 20 | ensure => file, 21 | owner => 'root', 22 | group => 'root', 23 | mode => '0644', 24 | content => file('secretsgit/gitconfig'), 25 | } 26 | 27 | group { $group: 28 | ensure => present, 29 | } 30 | 31 | file { $path: 32 | ensure => directory, 33 | owner => 'root', 34 | group => $group, 35 | mode => '2770', 36 | } 37 | 38 | $users.each |String $user| { 39 | User<| title == $user |> { groups +> [$group] } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /puppet/spec/defines/users_account_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'users::account' do 4 | on_supported_os.each do |os, facts| 5 | context "on #{os}" do 6 | let(:title) { 'jenkins' } 7 | let(:facts) { facts } 8 | let(:sudo_group) { facts[:os]['family'] == 'Debian' ? 'sudo' : 'wheel' } 9 | 10 | context 'default parameters' do 11 | it { is_expected.to compile.with_all_deps } 12 | it { is_expected.to contain_user('jenkins').with_ensure('present').with_groups([sudo_group]) } 13 | it { is_expected.to contain_file('/home/jenkins').with_ensure('directory') } 14 | end 15 | 16 | context 'without sudo' do 17 | let(:params) do 18 | {sudo: false} 19 | end 20 | 21 | it { is_expected.to compile.with_all_deps } 22 | it { is_expected.to contain_user('jenkins').with_ensure('present').with_groups([]) } 23 | end 24 | 25 | context 'ensure => absent' do 26 | let(:params) do 27 | {ensure: 'absent'} 28 | end 29 | 30 | it { is_expected.to compile.with_all_deps } 31 | it { is_expected.to contain_user('jenkins').with_ensure('absent') } 32 | it { is_expected.not_to contain_file('/home/jenkins') } 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /puppet/modules/secure_ssh/manifests/uploader_key.pp: -------------------------------------------------------------------------------- 1 | # Define which deploys the key for a specific user 2 | # 3 | # @param user 4 | # User to own the key 5 | # 6 | # @param dir 7 | # Directory to store the key in 8 | # 9 | # @param mode 10 | # Mode of $dir 11 | # 12 | # @param manage_dir 13 | # Whether or not to manage $dir 14 | # 15 | # @param ssh_key_name 16 | # The name of the key 17 | # 18 | define secure_ssh::uploader_key ( 19 | String[1] $user, 20 | Stdlib::Absolutepath $dir = "/home/${user}/.ssh", 21 | Stdlib::Filemode $mode = '0600', 22 | Boolean $manage_dir = false, 23 | String[1] $ssh_key_name = "${name}_key", 24 | String[1] $ensure = 'present', 25 | ) { 26 | $pub_key = ssh::keygen($ssh_key_name, true) 27 | $priv_key = ssh::keygen($ssh_key_name) 28 | 29 | if $manage_dir { 30 | file { $dir: 31 | ensure => directory, 32 | owner => $user, 33 | mode => $mode, 34 | } 35 | } 36 | 37 | file { "${dir}/${ssh_key_name}": 38 | ensure => $ensure, 39 | owner => $user, 40 | mode => '0400', 41 | content => $priv_key, 42 | } 43 | 44 | file { "${dir}/${ssh_key_name}.pub": 45 | ensure => $ensure, 46 | owner => $user, 47 | mode => '0644', 48 | content => "ssh-rsa ${pub_key} ${ssh_key_name} from puppetmaster\n", 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /puppet/modules/users/manifests/account.pp: -------------------------------------------------------------------------------- 1 | define users::account ( 2 | Enum['present', 'absent'] $ensure = 'present', 3 | Optional[String] $fullname = undef, 4 | Optional[String] $passwd = undef, 5 | Stdlib::Absolutepath $homedir = "/home/${title}", 6 | Boolean $sudo = true, 7 | ) { 8 | if $sudo { 9 | include sudo 10 | $groups = [if $facts['os']['family'] == 'Debian' { 'sudo' } else { 'wheel' }] 11 | } else { 12 | $groups = [] 13 | } 14 | 15 | user { $name: 16 | ensure => $ensure, 17 | comment => $fullname, 18 | home => $homedir, 19 | groups => $groups, 20 | managehome => true, 21 | shell => '/bin/bash', 22 | password => $passwd, 23 | } 24 | 25 | if $ensure == 'present' { 26 | file { $homedir: 27 | ensure => directory, 28 | owner => $name, 29 | group => $name, 30 | mode => '0755', 31 | } 32 | 33 | file { "${homedir}/.ssh": 34 | ensure => directory, 35 | owner => $name, 36 | group => $name, 37 | mode => '0700', 38 | } 39 | 40 | file { "${homedir}/.ssh/authorized_keys": 41 | ensure => file, 42 | content => file("${module_name}/${name}-authorized_keys"), 43 | owner => $name, 44 | group => $name, 45 | mode => '0600', 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_job_builder/README.md: -------------------------------------------------------------------------------- 1 | # jenkins_job_builder 2 | 3 | `files/theforeman.org` contains job definitions for Jenkins 4 | 5 | ## development setup 6 | 7 | Warning: Your local Jenkins may not work for all the jobs you try to import, because some jobs rely on plugins and global environment variables. Unfortunately, we do not have a list of plugins and variables in a source control yet. 8 | 9 | 10 | * Set up a local Jenkins instance 11 | 1. In Docker 12 | * you can use official [Docker images](https://hub.docker.com/r/jenkins/jenkins/) 13 | * `docker run --name jenkins-lts -p 8080:8080 -p 50000:50000 -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts` 14 | 2. Using Puppet 15 | * Approved [Puppet module](https://forge.puppet.com/rtyler/jenkins). 16 | 17 | * `pip install --user jenkins-job-builder` 18 | * create `jjb.ini` containing credentials and location of your Jenkins: 19 | 20 | ``` 21 | [jenkins] 22 | user=admin 23 | password=changeme 24 | url=http://jenkins.example.com:8080 25 | 26 | [job_builder] 27 | ignore_cache=True 28 | keep_descriptions=False 29 | recursive=True 30 | allow_duplicates=False 31 | ``` 32 | 33 | * `cd foreman-infra/puppet/modules/jenkins_job_builder/files/theforeman.org` 34 | * `jenkins-jobs --conf ~/jjb.ini -l debug update -r . release_mash` to update the `release_mash` job. Omit the job name to update all. 35 | -------------------------------------------------------------------------------- /puppet/manifests/site.pp: -------------------------------------------------------------------------------- 1 | node default { 2 | include profiles::base 3 | } 4 | 5 | node /^backup\d+\.[a-z]+\.theforeman\.org$/ { 6 | include profiles::base 7 | include profiles::backup::receiver 8 | } 9 | 10 | node /^controller\d+\.jenkins\.[a-z]+\.theforeman\.org$/ { 11 | include profiles::base 12 | include profiles::jenkins::controller 13 | } 14 | 15 | node /^discourse\d+\.([a-z]+\.)?theforeman\.org$/ { 16 | include profiles::base 17 | include profiles::discourse 18 | } 19 | 20 | node /^foreman\d+\.[a-z]+\.theforeman\.org$/ { 21 | include profiles::base 22 | include profiles::foreman 23 | } 24 | 25 | node /^(deb-)?node\d+\.jenkins\.[a-z]+\.theforeman\.org$/ { 26 | include profiles::base 27 | include profiles::jenkins::node 28 | } 29 | 30 | node /^puppet\d+\.[a-z]+\.theforeman\.org$/ { 31 | include profiles::base 32 | include profiles::puppetserver 33 | } 34 | 35 | node /^redmine\d+\.[a-z]+\.theforeman\.org$/ { 36 | include profiles::base 37 | include profiles::redmine 38 | } 39 | 40 | node /^repo-deb\d+\.[a-z]+\.theforeman\.org$/ { 41 | include profiles::base 42 | include profiles::repo::deb 43 | } 44 | 45 | node /^repo-rpm\d+\.[a-z]+\.theforeman\.org$/ { 46 | include profiles::base 47 | include profiles::repo::rpm 48 | } 49 | 50 | node /^website\d+\.[a-z]+\.theforeman\.org$/ { 51 | include profiles::base 52 | include profiles::website 53 | } 54 | -------------------------------------------------------------------------------- /puppet/modules/unattended/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class unattended { 2 | if $facts['os']['family'] == 'Debian' { 3 | class { 'unattended_upgrades': 4 | auto => { 'reboot' => false }, 5 | blacklist => [ 6 | 'openjdk-17-jdk', 7 | 'openjdk-17-jdk-headless', 8 | 'openjdk-17-jre', 9 | 'openjdk-17-jre-headless', 10 | ], 11 | extra_origins => [ 12 | 'site=apt.grafana.com,component=main', 13 | 'origin=Vox Pupuli', 14 | ], 15 | mail => { 'to' => 'sysadmins', }, 16 | } 17 | } 18 | 19 | if $facts['os']['family'] == 'RedHat' { 20 | if $trusted['certname'] =~ /^node\d+\.jenkins\.[a-z]+\.theforeman\.org$/ { 21 | $reboot = 'when-needed' 22 | $reboot_command = '/usr/local/sbin/reboot-jenkins-node' 23 | } elsif $trusted['certname'] =~ /^(repo-deb|repo-rpm|backup|website)\d+\.[a-z]+\.theforeman\.org$/ { 24 | $reboot = 'when-needed' 25 | $reboot_command = '/usr/local/sbin/reboot-inactive-system' 26 | } else { 27 | $reboot = 'never' 28 | $reboot_command = "shutdown -r +5 'Rebooting after applying package updates'" 29 | } 30 | 31 | class { 'yum_cron': 32 | apply_updates => true, 33 | mailto => 'sysadmins', 34 | exclude_packages => ['java*', 'jenkins'], 35 | reboot => $reboot, 36 | reboot_command => $reboot_command, 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /puppet/modules/web/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # @summary the webserver configuration 2 | # 3 | # All vhosts can be protected by a single SSL cert with additional names added 4 | # in the certonly $domains parameter below. 5 | # 6 | # @param https 7 | # to request an LE cert via webroot mode, the HTTP vhost must be up. To 8 | # start httpd, the certs have to exist, so keep SSL vhosts disabled until the 9 | # certs are present via the HTTP vhost and only then enable the SSL vhosts. 10 | # 11 | class web ( 12 | Boolean $https = true, 13 | ) { 14 | include web::base 15 | 16 | if $facts['os']['selinux']['enabled'] { 17 | include selinux 18 | 19 | # Use a non-HTTP specific context to be shared with rsync 20 | selinux::fcontext { 'fcontext-www': 21 | seltype => 'public_content_t', 22 | pathspec => '/var/www(/.*)?', 23 | } 24 | } 25 | 26 | # METRICS 27 | # script to do initial filtering of apache logs for download metrics 28 | file { '/usr/local/bin/filter_apache_stats': 29 | ensure => file, 30 | owner => 'root', 31 | group => 'root', 32 | mode => '0744', 33 | source => 'puppet:///modules/web/filter_apache_stats.sh', 34 | } 35 | 36 | # daily at 4am, should be fairly quiet on the server 37 | cron { 'filter_apache_stats': 38 | command => '/usr/bin/nice -19 /usr/local/bin/filter_apache_stats', 39 | user => root, 40 | hour => '4', 41 | minute => '0', 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /docs/redmine.md: -------------------------------------------------------------------------------- 1 | # Redmine 2 | 3 | | | redmine01.conova.theforeman.org | 4 | | - | - | 5 | | type | Libvirt VM | 6 | | OS | CentOS Stream 9 | 7 | | CPUs | 8 | 8 | | RAM | 8GB | 9 | | Storage | /dev/vda (30GB) | 10 | | Managed by | [profiles::redmine](https://github.com/theforeman/foreman-infra/blob/master/puppet/modules/profiles/manifests/redmine.pp) | 11 | 12 | ## Deployment 13 | 14 | Redmine is deployed using Ruby 3.0 and PSQL 13. 15 | 16 | A copy of the git repository is stored here: https://github.com/theforeman/redmine/. When upgrading Redmine it is required to rebase our changes onto the new upstream ref, and then push that back to our fork. 17 | 18 | The repository is a copy of Redmine's master branch with the following changes: 19 | 20 | * e-mail configuration using mailgun.com, under config/configuration.yml 21 | * a few local customisation commits such as images and spider blocks 22 | 23 | This should be kept up to date from the Redmine project by merging in upstream/master on a regular basis. 24 | 25 | Note there are also some cron jobs (handled in foreman-infra Puppet code), and plugins (some of which are tracked as submodules, but care should be taken). 26 | 27 | ## Backups 28 | 29 | The Redmine database and files are backed up daily to the Puppetserver via restic. Should you need to recover the setup, apply the Puppet manifests from foreman-infra to a new Centos host, and then restore the backup DB to PostgreSQL and the files to `/var/lib/redmine`. 30 | -------------------------------------------------------------------------------- /puppet/modules/profiles/manifests/foreman.pp: -------------------------------------------------------------------------------- 1 | # @summary A Foreman application server 2 | class profiles::foreman { 3 | include foreman 4 | include foreman::repo 5 | include foreman::compute::libvirt 6 | include foreman::compute::openstack 7 | include foreman::plugin::ansible 8 | include foreman::plugin::puppet 9 | include foreman::plugin::remote_execution 10 | 11 | include puppet 12 | 13 | puppet::config::main { 'dns_alt_names': 14 | value => $foreman::serveraliases, 15 | } 16 | 17 | package { 'rubygem-foreman_maintain': 18 | ensure => present, 19 | } 20 | 21 | $backup_base_path = '/var/backups' 22 | $backup_path = "${backup_base_path}/foreman" 23 | 24 | file { $backup_base_path: 25 | ensure => directory, 26 | owner => 'root', 27 | group => 'root', 28 | mode => '0755', 29 | } 30 | file { $backup_path: 31 | ensure => directory, 32 | owner => 'root', 33 | group => 'root', 34 | mode => '0750', 35 | } 36 | 37 | include profiles::backup::sender 38 | 39 | restic::repository { 'foreman': 40 | backup_cap_dac_read_search => true, 41 | backup_path => $backup_path, 42 | backup_pre_cmd => ["+/usr/bin/foreman-maintain backup online --assumeyes --preserve-directory ${backup_path}"], 43 | backup_post_cmd => [ 44 | '-/bin/bash -c "/usr/local/bin/restic-prometheus-exporter | sponge /var/lib/prometheus/node-exporter/restic.prom"', 45 | ], 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /puppet/modules/users/files/ekohl-authorized_keys: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCYUCD5BVc5ILSXhBh+Kj8gC/x7ctPhpk6GGrB/nJCyoY8hBXW6mspaVaQeLwDmjFFH+YwpfJZhCI1GFVsNKZWcLB319stoQkDEdYFcZZ6WMD4dc3kBVdEUqVBbe51pQlHIfuaROXAI42cjbJTuYy2AIVCKYdasg3ztFNuDE/RPuscbn9Kv7u8bi0CYjEYeSTML8YXX0n4xS7k1v6r0ksRnUxvNhgiEi73pSboRIeyizX3iaHB+vqWy9EoM8YjnJOUDjtMsflkHoV3UHWmcKOPjjvKNJeuUJs4rCksiXGM6iGdHx7jjcx9iW5v3L0mCuetV6AoZNw2bkXx7/6o6jao6vyJATAKQbHu31oJiPWjbtSgKzg2OepGcrpSm3fuDGncXqPSbf+RGYm0+6gMyt3Wbfl0q0DsB5nixbUqtx6BlG0zZyWhOD7FDmMh4L+fxM/pndh3rSOwLk7DlR9UiDWGvn7jxPXiinTn6aoef20IF+i9sRImpGYQaGDlpCIYI+3+V4MYm5KcgGFVOk4TSxqISvzKr9Rb19VxUV0QBd3v/U4TJBjLjmXlN5feSmEROWFURemvlH1nZfZOxPgAnR0S2YlW22q1Zoz6Ts/uq6lZTl5b1sBFFD1mCKS4aB4/R2GE3DWcieCpdZVLaHOpT4DBvKMovyMOp+dHBearmWs7NeQ== ekohl@donald 2 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDUeRHvXLF+3vIhI50zsM7FIc7QW0O36ZY2RYluc+MiPDfTDJlGUb8eBU+jxpkohOVys0llbuC01M8nRzJzOQne+LyGurFOYc3/SE0Q4BlaJqYAoF2GW5aYm+0TNHxCVWWnSSZWJZuLi4P0Zv5egSfzXG898vErLUwaHUybi4S230DdSECgtrWyc4NhMAma7Zh9zkoJ2SuYDdQWrnk2hWA4AWNn1LKRIYATg4cT+BucqqkNglFZutPWcdoL2WqD3pNNrJaA5bIdjUpkNghNsN9IbGLnQK+txsnwyI4bkqeI51JGPtZbdQ44WEA0I2wqGuLF2p76grSDi9y6I7dlk91yAqBwZ73iwGJoFdhtjgiQ0BRS3hTNP/lpsQVYrZmUj6IN/76gvKWwqCEKMpoJZ8hO64KS9HgnpTBvbtZehJVqC1SPtuBmW+PK1Z3snJ34uV6amvXtKs/WQjW94GLhlWss3ySJrgQjgpKPu9Lw1IYT3Ypei0Mo0iT3ZUsTciIJZ6STeK6WVTHTCiuhVoC7K0JCqcBk8t7sdSA5NIObpjyu4zv4MvrZY0T0C1DyzoptkFRKOnsYEVNOkZJaXle/RZNwFFzEKgBeWJiA7FVgK5I0q+8F6paXFyHkc4P16cVa4sL+Ue1mGYq0c9zaVw2Tr+58rGeS++ODnscxV60bGZrWGw== ekohl@wisse 3 | -------------------------------------------------------------------------------- /puppet/spec/classes/profiles_redmine_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'profiles::redmine' do 4 | on_supported_os.each do |os, os_facts| 5 | context "on #{os}" do 6 | let(:facts) { os_facts } 7 | let(:pre_condition) do 8 | <<~PUPPET 9 | class { 'restic': 10 | password => 'SuperSecret', 11 | } 12 | PUPPET 13 | end 14 | 15 | it { is_expected.to compile.with_all_deps } 16 | it do 17 | is_expected.to contain_class('restic') 18 | .with_backup_timer('daily') 19 | .with_type('sftp') 20 | .with_host('backups.theforeman.org') 21 | .with_id("backup-#{facts[:networking]['hostname']}") 22 | end 23 | 24 | it do 25 | is_expected.to contain_file('/var/lib/restic/.ssh') 26 | .with_ensure('directory') 27 | .with_owner('restic') 28 | .with_group('restic') 29 | .with_mode('0700') 30 | end 31 | 32 | it do 33 | is_expected.to contain_file('/var/lib/restic/.ssh/id_rsa') 34 | .with_ensure('file') 35 | .with_owner('restic') 36 | .with_group('restic') 37 | .with_mode('0600') 38 | .with_content(%r{.+}) 39 | end 40 | 41 | it do 42 | is_expected.to contain_sshkey('backups.theforeman.org') 43 | .with_ensure('present') 44 | .with_type('ecdsa-sha2-nistp256') 45 | .with_key(%r{^AAAA.+$}) 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /docs/secrets.md: -------------------------------------------------------------------------------- 1 | # Secret storage 2 | 3 | The Foreman project uses [gopass](https://github.com/gopasspw/gopass) to store shared secrets. 4 | This is achieved by storing GPG encrypted files in git repositories. 5 | 6 | ## Client access 7 | 8 | First install gopass. On Fedora: 9 | 10 | ```sh 11 | dnf install gopass 12 | ``` 13 | 14 | Ensure that `gopass` is initialized after installing the first time (and that your GPG private key is present on the system): 15 | 16 | ``` 17 | gopass init 18 | ``` 19 | 20 | ## Stores 21 | 22 | ### Releases 23 | 24 | This store is meant for release engineers and can be cloned: 25 | 26 | ``` 27 | gopass clone secrets.theforeman.org:/srv/secretsgit/theforeman-release.git theforeman/releases 28 | ``` 29 | 30 | ### Shared 31 | 32 | Contains account access for Infra admins. 33 | 34 | ``` 35 | gopass clone secrets.theforeman.org:/srv/secretsgit/theforeman-passwords.git theforeman/shared 36 | ``` 37 | 38 | ## Server setup 39 | 40 | This is managed by the Puppet class `secretsgit` and served on the `secrets.theforeman.org` hostname. Technically this is a DNS CNAME to the real server. 41 | 42 | ### Granting access 43 | 44 | * Ensure [SSH access](https://theforeman.github.io/foreman-infra/#access) is available 45 | * Add the user to [`secretsgit::users`](https://foreman.theforeman.org/foreman_puppet/puppetclasses/564-secretsgit/edit) 46 | * Add the user's key as a recipient: `gopass sync && gopass recipients add --store theforeman/releases 1234567890ABCDEF && gopass sync` 47 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/manifests/packaging/rpm.pp: -------------------------------------------------------------------------------- 1 | # All RPM packaging tools 2 | # @api private 3 | class jenkins_node::packaging::rpm ( 4 | Stdlib::Absolutepath $homedir, 5 | String $user, 6 | Stdlib::Absolutepath $workspace, 7 | ) { 8 | $ansible_python_version = 'python3' 9 | 10 | package { ['rpm-build', 'createrepo', 'copr-cli', 'rpmlint']: 11 | ensure => installed, 12 | } 13 | 14 | yumrepo { 'git-annex': 15 | name => 'git-annex', 16 | baseurl => 'https://downloads.kitenet.net/git-annex/linux/current/rpms/', 17 | enabled => '1', 18 | gpgcheck => '0', 19 | } -> 20 | package { ['git-annex-standalone']: 21 | ensure => installed, 22 | } 23 | 24 | $obal_packages = [ 25 | $ansible_python_version, 26 | "${ansible_python_version}-pyyaml", 27 | "${ansible_python_version}-setuptools", 28 | ] 29 | $foreman_rel_eng_packages = [ 30 | 'python3-pyyaml', 31 | 'rsync', 32 | ] 33 | 34 | stdlib::ensure_packages($obal_packages + $foreman_rel_eng_packages) 35 | 36 | # specs-from-koji 37 | package { ['scl-utils-build', 'rpmdevtools']: 38 | ensure => present, 39 | } 40 | 41 | package { ['dnf', 'dnf-plugins-core']: 42 | ensure => present, 43 | } 44 | 45 | secure_ssh::rsync::uploader_key { 'yumrepostage': 46 | user => $user, 47 | dir => "${workspace}/staging_key", 48 | manage_dir => true, 49 | } 50 | 51 | secure_ssh::rsync::uploader_key { 'yumstage': 52 | ensure => 'absent', 53 | user => $user, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /puppet/modules/freight/templates/rsync_main.erb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | case "$SSH_ORIGINAL_COMMAND" in 6 | *\&*) 7 | echo "Rejected" 8 | ;; 9 | *\(*) 10 | echo "Rejected" 11 | ;; 12 | *\{*) 13 | echo "Rejected" 14 | ;; 15 | *\;*) 16 | echo "Rejected" 17 | ;; 18 | *\<*) 19 | echo "Rejected" 20 | ;; 21 | *\`*) 22 | echo "Rejected" 23 | ;; 24 | *\|*) 25 | echo "Rejected" 26 | ;; 27 | rsync\ --server*) 28 | # Only push to the rsync cache 29 | if [[ `echo "${SSH_ORIGINAL_COMMAND}" | awk '{ print $NF }'` =~ ^rsync_cache/.* ]] ; then 30 | # Make sure target dir can be created 31 | DEB_PATH=`echo "${SSH_ORIGINAL_COMMAND}" | awk '{ print $NF }'` 32 | DISTRO=`echo $DEB_PATH | /bin/cut -f2 -d/` 33 | REPO=`echo $DEB_PATH | /bin/cut -f3 -d/` 34 | mkdir -p <%= @home %>/rsync_cache/$DISTRO/$REPO 35 | 36 | # Permit transfer 37 | $SSH_ORIGINAL_COMMAND 38 | 39 | find <%= @home %>/rsync_cache/$DISTRO/$REPO -iname '*.deb' -exec freight-add -v -c <%= @home %>/freight.conf {} apt/$DISTRO/$REPO \; 40 | # Publish the debs 41 | freight-cache -c <%= @home %>/freight.conf -v apt/$DISTRO 42 | # Cleanup - no need to keep the debs 43 | find <%= @home %>/rsync_cache/$DISTRO/$REPO -iname '*.deb' -delete || true 44 | fi 45 | ;; 46 | deploy*) 47 | OS=`echo "${SSH_ORIGINAL_COMMAND}" | awk '{ print $2 }'` 48 | VERS=`echo "${SSH_ORIGINAL_COMMAND}" | awk '{ print $3 }'` 49 | <%= @home %>/bin/secure_deploy_debs $OS $VERS 50 | ;; 51 | *) 52 | echo "Rejected" 53 | ;; 54 | esac 55 | # ERB highlighting looks terrible in this script... 56 | # vim: set ft=sh : # 57 | -------------------------------------------------------------------------------- /puppet/modules/web/files/filter_apache_stats.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Semi-crude script to run daily for doing initial parsing of apache logs to 4 | # remove lines we definitely don't want. It gets: 5 | # 6 | # * only successful requests (code == 200) 7 | # * only GET requrests 8 | # * only actual packages (strings ending in .rpm or .deb) 9 | # * strips out "source", "tfm" and "rubygem" from the rpms to distinguish 10 | # foreman packages from dependencies 11 | # 12 | # This reduces the apache logs by a factor of about 20, which can then be 13 | # downloaded for further intensive processing (by date, version, user-agent, 14 | # etc) - date processing is particularly cpu instensive, so it's best kept off 15 | # the webserver. 16 | # 17 | # This script is intended to run daily on the latest logfile, however, since it 18 | # is possible that a logrotation has happened overnight, the last *two* files 19 | # are parsed. Files are datestamped with a one-week cleanup. 20 | 21 | debs=$(ls -1rt /var/log/httpd/deb_access.log*|tail -n2) 22 | yums=$(ls -1rt /var/log/httpd/yum_access.log*|tail -n2) 23 | date=$(date '+%Y%m%d') 24 | ldir='/var/cache/parsed_apache_logs' 25 | 26 | mkdir -p $ldir 27 | 28 | grep "\s200\s" $debs \ 29 | | grep "\"GET" \ 30 | | grep "\.deb\s" \ 31 | > ${ldir}/deb_downloads.${date}.log 32 | 33 | grep "\s200\s" $yums \ 34 | | grep "\"GET" \ 35 | | grep "\.rpm\s" \ 36 | | grep -v "\/source\/" \ 37 | | grep -E -v "/(releases|nightly)/.*(tfm|rubygem)" \ 38 | > ${ldir}/yum_downloads.${date}.log 39 | 40 | # Clean up old backups over a week old 41 | find $ldir -mtime +6 -delete 42 | -------------------------------------------------------------------------------- /puppet/modules/web/manifests/vhost/deb.pp: -------------------------------------------------------------------------------- 1 | # @summary Set up the deb vhost 2 | # @api private 3 | class web::vhost::deb ( 4 | String[1] $stable, 5 | String $user = 'freight', 6 | Stdlib::Absolutepath $home = "/home/${user}", 7 | Stdlib::Absolutepath $stagedir = "/var/www/${user}", 8 | ) { 9 | include fastly_purge 10 | 11 | # Manual step: each user needs the GPG key in it's keyring 12 | freight::user { 'main': 13 | user => $user, 14 | home => $home, 15 | webdir => '/var/www/vhosts/deb/htdocs', 16 | stagedir => $stagedir, 17 | vhost => 'deb', 18 | cron_matches => ['nightly', 'scratch'], 19 | stable => $stable, 20 | } 21 | 22 | # Can't use a standard rsync define here as we need to extend the 23 | # script to handle deployment too 24 | secure_ssh::receiver_setup { $user: 25 | user => $user, 26 | groups => ['freightstage'], 27 | homedir => $home, 28 | foreman_search => 'host.hostgroup = Debian and (name = external_ip4 or name = external_ip6)', 29 | script_content => template('freight/rsync_main.erb'), 30 | ssh_key_name => "rsync_${user}_key", 31 | } 32 | file { "${home}/rsync_cache": 33 | ensure => directory, 34 | owner => $user, 35 | } 36 | # This ruby script is called from the secure_freight template 37 | file { "${home}/bin/secure_deploy_debs": 38 | ensure => file, 39 | owner => 'freight', 40 | mode => '0700', 41 | content => template('freight/deploy_debs.erb'), 42 | } 43 | 44 | User <| title == 'freightstage' |> -> User <| title == $user |> 45 | } 46 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_job_builder/manifests/config.pp: -------------------------------------------------------------------------------- 1 | # Deploys a set of jobs to one Jenkins instance 2 | # 3 | define jenkins_job_builder::config ( 4 | Stdlib::Httpurl $url, 5 | String $username, 6 | String $password, 7 | Integer[0] $jenkins_jobs_update_timeout = 600, 8 | String $command_arguments = 'jenkins-job-update', 9 | String $git_project_name = 'jenkins-jobs', 10 | String $git_repo = 'https://github.com/theforeman/jenkins-jobs.git', 11 | Optional[String] $git_ref = undef, 12 | ) { 13 | $config_name = $name 14 | $directory = '/etc/jenkins_jobs' 15 | $inifile = "${directory}/jenkins_jobs_${config_name}.ini" 16 | 17 | stdlib::ensure_packages(['git-core']) 18 | 19 | vcsrepo { "${directory}/${git_project_name}": 20 | ensure => latest, 21 | provider => git, 22 | source => $git_repo, 23 | revision => $git_ref, 24 | notify => Exec["jenkins-jobs-update-${config_name}"], 25 | require => Package['git-core'], 26 | } 27 | 28 | exec { "jenkins-jobs-update-${config_name}": 29 | command => "jenkins-jobs --conf ${inifile} update ${directory}/${git_project_name}/${config_name} ${command_arguments}", 30 | timeout => $jenkins_jobs_update_timeout, 31 | path => '/bin:/usr/bin:/usr/local/bin', 32 | require => File[$inifile], 33 | refreshonly => true, 34 | } 35 | 36 | # TODO: We should put in notify Exec['jenkins_jobs_update'] 37 | # at some point, but that still has some problems. 38 | file { $inifile: 39 | ensure => file, 40 | mode => '0400', 41 | content => template('jenkins_job_builder/jenkins_jobs.ini.erb'), 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /puppet/modules/profiles/manifests/backup/receiver/target.pp: -------------------------------------------------------------------------------- 1 | # @summary A backup receiver target 2 | # 3 | # Every server backs up to its own target. This is implemented by creating is 4 | # own user with SSH key. The user is limited to just sftp. 5 | # 6 | # @param username 7 | # The remote username 8 | # @param ensure 9 | # The backup receiver target state to ensure. 10 | define profiles::backup::receiver::target ( 11 | String[1] $username = "backup-${title}", 12 | Enum['present', 'absent'] $ensure = present, 13 | ) { 14 | include profiles::backup::receiver 15 | 16 | $homedir = "${profiles::backup::receiver::directory}/${title}" 17 | 18 | user { $username: 19 | ensure => $ensure, 20 | gid => $profiles::backup::receiver::group, 21 | home => $homedir, 22 | managehome => true, 23 | password => '!', 24 | purge_ssh_keys => true, 25 | } 26 | 27 | # Ensure authorized keys file for proper SELinux labels 28 | file { "${homedir}/.ssh": 29 | ensure => bool2str($ensure == present, 'directory', 'absent'), 30 | owner => $username, 31 | mode => '0700', 32 | force => $ensure == absent, 33 | seltype => 'ssh_home_t', 34 | } 35 | 36 | file { "${homedir}/.ssh/authorized_keys": 37 | ensure => bool2str($ensure == present, 'file', 'absent'), 38 | owner => $username, 39 | mode => '0600', 40 | seltype => 'ssh_home_t', 41 | } 42 | 43 | ssh_authorized_key { "${username} managed by Puppet": 44 | ensure => $ensure, 45 | user => $username, 46 | key => ssh::keygen("backup-${title}", true), 47 | type => 'ssh-rsa', 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /docs/monitoring.md: -------------------------------------------------------------------------------- 1 | # Monitoring 2 | 3 | The Foreman project uses a sponsored Grafana instance at https://theforeman.grafana.net for metrics and alerting 4 | 5 | ## Access 6 | 7 | People who work on infrastructure can be added to the organization by Eric, Evgeni or Ewoud. 8 | 9 | ## Dashboards 10 | 11 | - [Blackbox Exporter (HTTP Prober)](https://grafana.com/grafana/dashboards/13659-blackbox-exporter-http-prober/) shows HTTP status at https://theforeman.grafana.net/d/NEzutrbMk/blackbox-exporter-http-prober 12 | - Restic Exporter shows backup status at https://theforeman.grafana.net/d/9f4a1fae-9438-41af-97f3-ca0f87f8ba3f/restic-exporter 13 | - Various OS-level views can be seen at https://theforeman.grafana.net/dashboards/f/integration---linux-node/ 14 | 15 | ## Alerting 16 | 17 | ### Contact points 18 | 19 | Contact points in Grafana are "notification groups", that can use different integrations (like mail, Slack, etc) and targets (like mail address etc). 20 | 21 | Right now only one contact point is defined: `grafana-default-email` - it sends email to Eric, Ewoud and Evgeni. 22 | 23 | ### Alert rules 24 | 25 | #### pending package updates 26 | 27 | When `apt_upgrades_pending` or `yum_upgrades_pending` is `> 0` an alert is sent to `grafana-default-email` 28 | 29 | #### reboot required 30 | 31 | When `node_reboot_required` is `> 0` an alert is sent to `grafana-default-email` 32 | 33 | #### missing backup 34 | 35 | When `time() - restic_snapshot_timestamp_seconds` is `> 90000` (= the last restic snapshot is older than 25h) an alert is sent to `grafana-default-email` 36 | 37 | #### http status 38 | 39 | When `probe_http_status_code` is `!= 200` an alert is sent to `grafana-default-email` 40 | -------------------------------------------------------------------------------- /puppet/modules/discourse/templates/mail-receiver.yml.epp: -------------------------------------------------------------------------------- 1 | <%- | 2 | Stdlib::Absolutepath $root, 3 | Stdlib::Host $hostname, 4 | String $api_key, 5 | | -%> 6 | ## this is the incoming mail receiver container template 7 | ## 8 | ## After making changes to this file, you MUST rebuild 9 | ## /var/discourse/launcher rebuild mail-receiver 10 | ## 11 | ## BE *VERY* CAREFUL WHEN EDITING! 12 | ## YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT! 13 | ## visit http://www.yamllint.com/ to validate this file as needed 14 | 15 | base_image: discourse/mail-receiver:release 16 | update_pups: false 17 | 18 | expose: 19 | - "25:25" # SMTP 20 | 21 | env: 22 | LANG: en_US.UTF-8 23 | 24 | ## Where e-mail to your forum should be sent. In general, it's perfectly fine 25 | ## to use the same domain as the forum itself here. 26 | MAIL_DOMAIN: <%= $hostname %> 27 | 28 | ## The base URL for this Discourse instance. 29 | ## This will be whatever your Discourse site URL is. For example, 30 | ## https://discourse.example.com. If you're running a subfolder setup, 31 | ## be sure to account for that (ie https://example.com/forum). 32 | DISCOURSE_BASE_URL: "https://<%= $hostname %>" 33 | 34 | ## The master API key of your Discourse forum. You can get this from 35 | ## the "API" tab of your admin panel. 36 | DISCOURSE_API_KEY: "<%= $api_key %>" 37 | 38 | ## The username to use for processing incoming e-mail. Unless you have 39 | ## renamed the `system` user, you should leave this as-is. 40 | DISCOURSE_API_USERNAME: system 41 | 42 | volumes: 43 | - volume: 44 | host: <%= $root %>/shared/mail-receiver/postfix-spool 45 | guest: /var/spool/postfix 46 | -------------------------------------------------------------------------------- /puppet/modules/web/manifests/vhost/downloads.pp: -------------------------------------------------------------------------------- 1 | # @summary Set up the downloads vhost 2 | # @api private 3 | class web::vhost::downloads ( 4 | Stdlib::Absolutepath $downloads_directory = '/var/www/vhosts/downloads/htdocs', 5 | String $user = 'downloads', 6 | ) { 7 | $downloads_directory_config = [ 8 | { 9 | path => $downloads_directory, 10 | options => ['Indexes', 'FollowSymLinks', 'MultiViews'], 11 | }, 12 | { 13 | path => '.+\.(bz2|csv|gem|gz|img|iso|iso-img|iso-vmlinuz|pdf|tar|webm|rpm|deb)$', 14 | provider => 'filesmatch', 15 | expires_active => 'on', 16 | expires_default => 'access plus 30 days', 17 | }, 18 | ] 19 | 20 | secure_ssh::receiver_setup { $user: 21 | user => $user, 22 | foreman_search => 'host ~ node*.jenkins.osuosl.theforeman.org and (name = external_ip4 or name = external_ip6)', 23 | script_content => template('web/rsync_downloads.sh.erb'), 24 | } 25 | 26 | web::vhost { 'downloads': 27 | servername => "downloads-backend.${facts['networking']['fqdn']}", 28 | docroot => $downloads_directory, 29 | docroot_owner => $user, 30 | docroot_group => $user, 31 | docroot_mode => '0755', 32 | directories => $downloads_directory_config, 33 | } 34 | 35 | include apache::mod::alias 36 | include apache::mod::autoindex 37 | include apache::mod::dir 38 | include apache::mod::expires 39 | include apache::mod::mime 40 | 41 | file { "${downloads_directory}/HEADER.html": 42 | ensure => file, 43 | owner => 'root', 44 | group => 'root', 45 | mode => '0644', 46 | source => 'puppet:///modules/web/downloads-HEADER.html', 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /puppet/modules/profiles/manifests/redmine.pp: -------------------------------------------------------------------------------- 1 | # @summary A Redmine server 2 | class profiles::redmine ( 3 | ) { 4 | include redmine 5 | 6 | $backup_path = $redmine::data_dir 7 | $backup_db_path = "${backup_path}/db" 8 | 9 | include profiles::backup::sender 10 | include postgresql::server 11 | 12 | postgresql::server::role { 'restic': 13 | } 14 | 15 | postgresql::server::database_grant { "restic-${redmine::db_name}": 16 | privilege => 'CONNECT', 17 | db => $redmine::db_name, 18 | role => 'restic', 19 | } 20 | 21 | postgresql::server::grant { "restic-${redmine::db_name}-tables": 22 | privilege => 'SELECT', 23 | object_type => 'ALL TABLES IN SCHEMA', 24 | object_name => 'public', 25 | db => $redmine::db_name, 26 | role => 'restic', 27 | } 28 | 29 | postgresql::server::grant { "restic-${redmine::db_name}-sequences": 30 | privilege => 'SELECT', 31 | object_type => 'ALL SEQUENCES IN SCHEMA', 32 | object_name => 'public', 33 | db => $redmine::db_name, 34 | role => 'restic', 35 | } 36 | 37 | file { $backup_db_path: 38 | ensure => directory, 39 | owner => 'restic', 40 | mode => '0600', 41 | } 42 | 43 | restic::repository { 'redmine': 44 | backup_cap_dac_read_search => true, 45 | backup_path => $backup_path, 46 | backup_flags => ['--exclude', "${backup_path}/git"], 47 | backup_pre_cmd => ["/usr/bin/pg_dump --file=${backup_db_path}/redmine.sql ${redmine::db_name}"], 48 | backup_post_cmd => [ 49 | '-/bin/bash -c "/usr/local/bin/restic-prometheus-exporter | sponge /var/lib/prometheus/node-exporter/restic.prom"', 50 | ], 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /puppet/modules/discourse/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class discourse ( 2 | String $developer_emails, 3 | String $api_key, 4 | String $le_account_email, 5 | Stdlib::Host $smtp_address, 6 | String $smtp_user_name, 7 | String $smtp_password, 8 | Stdlib::Port $smtp_port = 587, 9 | Stdlib::Absolutepath $root = '/var/discourse', 10 | Stdlib::Host $hostname = 'community.theforeman.org', 11 | ) { 12 | stdlib::ensure_packages(['git']) 13 | 14 | vcsrepo { $root: 15 | ensure => present, 16 | provider => git, 17 | source => 'https://github.com/discourse/discourse_docker.git', 18 | } 19 | 20 | $containers = "${root}/containers" 21 | 22 | file { $containers: 23 | ensure => directory, 24 | owner => 'root', 25 | group => 'root', 26 | mode => '0700', 27 | require => Vcsrepo[$root], 28 | } 29 | 30 | $app_context = { 31 | 'root' => $root, 32 | 'hostname' => $hostname, 33 | 'developer_emails' => $developer_emails, 34 | 'smtp_address' => $smtp_address, 35 | 'smtp_port' => $smtp_port, 36 | 'smtp_user_name' => $smtp_user_name, 37 | 'smtp_password' => $smtp_password, 38 | 'le_account_email' => $le_account_email, 39 | } 40 | 41 | file { "${containers}/app.yml": 42 | owner => 'root', 43 | group => 'root', 44 | mode => '0700', 45 | content => epp('discourse/app.yml.epp', $app_context), 46 | } 47 | 48 | $mail_context = { 49 | 'root' => $root, 50 | 'hostname' => $hostname, 51 | 'api_key' => $api_key, 52 | } 53 | 54 | file { "${containers}/mail-receiver.yml": 55 | owner => 'root', 56 | group => 'root', 57 | mode => '0700', 58 | content => epp('discourse/mail-receiver.yml.epp', $mail_context ), 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /puppet/modules/profiles/files/base/restic-prometheus-exporter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import json 4 | import os 5 | import subprocess 6 | import sys 7 | 8 | import dateutil.parser 9 | 10 | from prometheus_client import CollectorRegistry, Gauge, generate_latest 11 | 12 | def main(): 13 | restic_bin = os.environ.get('RESTIC_BIN', 'restic') 14 | restic_repository = os.environ.get('RESTIC_REPOSITORY', 'unknown') 15 | 16 | cmd = [restic_bin, 'snapshots', '--latest', '1', '--json'] 17 | 18 | try: 19 | restic_result = subprocess.check_output(cmd, stderr=subprocess.STDOUT) 20 | except subprocess.CalledProcessError as e: 21 | print(f'Failed to run {" ".join(cmd)}: {e.output.decode()}', file=sys.stderr) 22 | sys.exit(1) 23 | except IOError as e: 24 | print(f'Failed to run {" ".join(cmd)}: {e}', file=sys.stderr) 25 | sys.exit(1) 26 | 27 | snapshots = json.loads(restic_result) 28 | 29 | registry = CollectorRegistry() 30 | label_names = ['repository', 'hostname', 'paths'] 31 | 32 | timestamp = Gauge('restic_snapshot_timestamp_seconds', "Restic snapshot timestamp", label_names, registry=registry) 33 | size = Gauge('restic_snapshot_size_bytes', "Restic snapshot size", label_names, registry=registry) 34 | 35 | for snapshot in snapshots: 36 | labels = [restic_repository, snapshot['hostname'], ','.join(snapshot['paths'])] 37 | 38 | timestamp.labels(*labels).set(dateutil.parser.parse(snapshot['time']).timestamp()) 39 | 40 | # summary is a restic 0.17+ feature 41 | if (summary := snapshot.get('summary')) is not None: 42 | size.labels(*labels).set(summary.get('total_bytes_processed')) 43 | 44 | print(generate_latest(registry).decode(), end='') 45 | 46 | if __name__ == "__main__": 47 | main() 48 | -------------------------------------------------------------------------------- /puppet/modules/secure_ssh/manifests/rsync/receiver_setup.pp: -------------------------------------------------------------------------------- 1 | # @summary Define which deploys the key for a specific user 2 | # 3 | # @param user 4 | # User to own the key 5 | # 6 | # @param script_content 7 | # Content of a script that'll be run by sshd when the user connects with the 8 | # key 9 | # 10 | # @param groups 11 | # Groups the user belongs to 12 | # 13 | # @param homedir 14 | # Home directory the user 15 | # 16 | # @param homedir_mode 17 | # Mode of the user's home directory 18 | # 19 | # @param allowed_ips 20 | # List of allowed ips in the authorized keys file. Unused if $foreman_search 21 | # is provided 22 | # 23 | # @param foreman_search 24 | # String to search in the foreman API, unused by default if specified, uses 25 | # the foreman search puppet function to get IP addresses matching the 26 | # required string. 27 | # 28 | define secure_ssh::rsync::receiver_setup ( 29 | String $user, 30 | Array[String] $groups = [], 31 | Stdlib::Absolutepath $homedir = "/home/${user}", 32 | Stdlib::Filemode $homedir_mode = '0700', 33 | Optional[String] $foreman_search = undef, 34 | Array[Stdlib::IP::Address] $allowed_ips = [], 35 | String $script_content = "# Permit transfer\n\$SSH_ORIGINAL_COMMAND\n", 36 | Array[String] $authorized_keys = [], 37 | ) { 38 | secure_ssh::receiver_setup { $name: 39 | user => $user, 40 | groups => $groups, 41 | homedir => $homedir, 42 | homedir_mode => $homedir_mode, 43 | foreman_search => $foreman_search, 44 | allowed_ips => $allowed_ips, 45 | ssh_key_name => "rsync_${name}_key", 46 | script_content => template('secure_ssh/script_rsync.erb'), 47 | authorized_keys => $authorized_keys, 48 | } 49 | 50 | file { "${homedir}/rsync_cache": 51 | ensure => directory, 52 | owner => $user, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /puppet/spec/classes/profiles_discourse_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'profiles::discourse' do 4 | on_supported_os.each do |os, os_facts| 5 | context "on #{os}" do 6 | let(:facts) { os_facts } 7 | let(:pre_condition) do 8 | <<~PUPPET 9 | class { 'restic': 10 | password => 'SuperSecret', 11 | } 12 | class { 'discourse': 13 | developer_emails => 'admin@example.com', 14 | api_key => '1234567890abcdef', 15 | le_account_email => 'infra@example.com', 16 | smtp_address => 'mail.example.com', 17 | smtp_user_name => 'discourse', 18 | smtp_password => 'changeme', 19 | } 20 | PUPPET 21 | end 22 | 23 | it { is_expected.to compile.with_all_deps } 24 | it do 25 | is_expected.to contain_class('restic') 26 | .with_backup_timer('daily') 27 | .with_type('sftp') 28 | .with_host('backups.theforeman.org') 29 | .with_id("backup-#{facts[:networking]['hostname']}") 30 | end 31 | 32 | it do 33 | is_expected.to contain_file('/var/lib/restic/.ssh') 34 | .with_ensure('directory') 35 | .with_owner('restic') 36 | .with_group('restic') 37 | .with_mode('0700') 38 | end 39 | 40 | it do 41 | is_expected.to contain_file('/var/lib/restic/.ssh/id_rsa') 42 | .with_ensure('file') 43 | .with_owner('restic') 44 | .with_group('restic') 45 | .with_mode('0600') 46 | .with_content(%r{.+}) 47 | end 48 | 49 | it do 50 | is_expected.to contain_sshkey('backups.theforeman.org') 51 | .with_ensure('present') 52 | .with_type('ecdsa-sha2-nistp256') 53 | .with_key(%r{^AAAA.+$}) 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /puppet/Puppetfile: -------------------------------------------------------------------------------- 1 | moduledir 'external_modules' 2 | 3 | mod 'puppet/anubis', '1.2.0' 4 | mod 'puppet/archive', '8.1.0' 5 | mod 'puppet/augeasproviders_core', '4.2.0' 6 | mod 'puppet/augeasproviders_mounttab', '4.0.0' 7 | mod 'puppet/augeasproviders_shellvar', '6.0.1' 8 | mod 'puppet/augeasproviders_ssh', '7.0.0' 9 | mod 'puppet/chrony', '5.0.0' 10 | mod 'puppet/epel', '5.0.0' 11 | mod 'puppet/extlib', '7.5.1' 12 | mod 'puppet/grafana_alloy', '2.0.0' 13 | mod 'puppet/jenkins', '7.0.0' 14 | mod 'puppet/letsencrypt', '13.1.0' 15 | mod 'puppet/logrotate', '9.0.0' 16 | mod 'puppet/mosquitto', '3.0.0' 17 | mod 'puppet/nodejs', '11.0.0' 18 | mod 'puppet/pbuilder', '2.0.0' 19 | mod 'puppet/redis', '12.0.0' 20 | mod 'puppet/selinux', '5.0.0' 21 | mod 'puppet/systemd', '9.4.0' 22 | mod 'puppet/unattended_upgrades', '9.1.0' 23 | mod 'puppetlabs/apache', '13.1.0' 24 | mod 'puppetlabs/apt', '11.2.0' 25 | mod 'puppetlabs/concat', '9.1.0' 26 | mod 'puppetlabs/inifile', '6.2.0' 27 | mod 'puppetlabs/java', '11.2.0' 28 | mod 'puppetlabs/mailalias_core', '1.2.0' 29 | mod 'puppetlabs/mount_providers', '2.0.1' 30 | mod 'puppetlabs/ntp', '11.1.0' 31 | mod 'puppetlabs/postgresql', '10.6.0' 32 | mod 'puppetlabs/stdlib', '9.7.0' 33 | mod 'puppetlabs/vcsrepo', '7.0.0' 34 | mod 'richardc/datacat', '0.6.2' 35 | mod 'saz/sudo', '9.0.2' 36 | mod 'theforeman/dns', '12.0.0' 37 | mod 'theforeman/foreman', '28.1.0' 38 | mod 'theforeman/foreman_proxy', '29.1.0' 39 | mod 'theforeman/motd', '2.1.1' 40 | mod 'theforeman/puppet', '22.0.1' 41 | mod 'theforeman/puppetserver_foreman', '4.3.0' 42 | mod 'theforeman/tftp', '10.0.0' 43 | mod 'treydock/yum_cron', '8.0.0' 44 | # Maintain a stable branch until https://github.com/syseleven/puppet-restic/pull/24 is merged 45 | # The PR is on a stable branch which can be force pushed 46 | # foreman-infra-production is aimed to only roll forward 47 | mod 'syseleven/restic', :git => 'https://github.com/theforeman/puppet-restic', :branch => 'foreman-infra-production' 48 | -------------------------------------------------------------------------------- /puppet/modules/freight/templates/deb-HEADER.html.epp: -------------------------------------------------------------------------------- 1 | <%- | String $stable | -%> 2 |

The Debian Foreman repo

3 | 4 |

You will find them at:

5 | 6 |
deb http://deb.theforeman.org/ <codename> <component>
7 | 8 |

Currently we have

9 | 10 |
11 | deb http://deb.theforeman.org/ bookworm <version>
12 | deb http://deb.theforeman.org/ bookworm nightly
13 | deb http://deb.theforeman.org/ jammy <version>
14 | deb http://deb.theforeman.org/ jammy nightly
15 | 
16 | deb http://deb.theforeman.org/ plugins <version>
17 | deb http://deb.theforeman.org/ plugins nightly
18 | 
19 | 20 |

An example of how to add it:

21 | 22 |
23 | wget https://deb.theforeman.org/foreman.asc -O /etc/apt/trusted.gpg.d/foreman.asc
24 | echo "deb http://deb.theforeman.org/ bookworm <%= $stable %>" > /etc/apt/sources.list.d/foreman.list
25 | echo "deb http://deb.theforeman.org/ plugins <%= $stable %>" >> /etc/apt/sources.list.d/foreman.list
26 | 
27 | 28 |

Or dynamically which makes copy-paste easy:

29 | 30 |
31 | . /etc/os-release
32 | wget https://deb.theforeman.org/foreman.asc -O /etc/apt/trusted.gpg.d/foreman.asc
33 | echo "deb http://deb.theforeman.org/ $VERSION_CODENAME <%= $stable %>" > /etc/apt/sources.list.d/foreman.list
34 | echo "deb http://deb.theforeman.org/ plugins <%= $stable %>" >> /etc/apt/sources.list.d/foreman.list
35 | 
36 | 37 |

Older Foreman releases are archived to archivedeb.theforeman.org once they have been unsupported for two release cycles. 38 | 39 |

You can find the installation instructions here, but we recommend using our Installer 40 | 41 |

If you have any issues, you can find ways to reach us on our Support page.

42 | -------------------------------------------------------------------------------- /puppet/modules/profiles/manifests/backup/receiver.pp: -------------------------------------------------------------------------------- 1 | # @summary The backup receiver 2 | # 3 | # The receiver sets up a group and a directory where all backup targets can 4 | # live. 5 | # 6 | # @see profiles::backup::receiver::target 7 | # 8 | # @param targets 9 | # The various targets to ensure 10 | # @param group 11 | # The group which all targets belong to 12 | # @param directory 13 | # The parent directory for all targets 14 | # @param ensure 15 | # The backup receiver state to ensure. 16 | class profiles::backup::receiver ( 17 | Array[String[1]] $targets = [], 18 | String[1] $group = 'backup', 19 | Stdlib::Absolutepath $directory = '/srv/backup', 20 | Enum['present', 'absent'] $ensure = present, 21 | ) { 22 | group { $group: 23 | ensure => $ensure, 24 | } 25 | 26 | if $ensure == present { 27 | file { $directory: 28 | ensure => 'directory', 29 | owner => 'root', 30 | group => $group, 31 | mode => '0750', 32 | } 33 | } 34 | 35 | # Ensure $directory/.ssh is set to to ssh_home_t 36 | if $facts['os']['selinux']['enabled'] and $directory != '/home' { 37 | selinux::fcontext { 'backup-ssh': 38 | ensure => $ensure, 39 | seltype => 'ssh_home_t', 40 | pathspec => "${directory}/[^/]+/\\.ssh(/.*)?", 41 | } 42 | } 43 | 44 | include ssh 45 | 46 | $sshd_match = "Group ${group}" 47 | sshd_config_match { $sshd_match: 48 | ensure => $ensure, 49 | } 50 | 51 | # TODO: ChrootDirectory %h 52 | 53 | sshd_config { 'DisableForwarding': 54 | ensure => $ensure, 55 | condition => $sshd_match, 56 | value => 'yes', 57 | } 58 | 59 | sshd_config { 'ForceCommand': 60 | ensure => $ensure, 61 | condition => $sshd_match, 62 | value => 'internal-sftp', 63 | } 64 | 65 | $require = Sshd_config['DisableForwarding', 'ForceCommand'] 66 | 67 | $targets.each |$target| { 68 | profiles::backup::receiver::target { $target: 69 | ensure => $ensure, 70 | require => $require, 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/manifests/postgresql.pp: -------------------------------------------------------------------------------- 1 | # @api private 2 | class jenkins_node::postgresql { 3 | # only CentOS nodes are used to run unit tests 4 | if $facts['os']['family'] == 'RedHat' { 5 | # Necessary for PostgreSQL EVR extension 6 | yumrepo { 'pulpcore': 7 | baseurl => "http://yum.theforeman.org/pulpcore/3.39/el\$releasever/\$basearch/", 8 | descr => 'Pulpcore', 9 | enabled => true, 10 | gpgcheck => true, 11 | gpgkey => 'https://yum.theforeman.org/pulpcore/3.39/GPG-RPM-KEY-pulpcore', 12 | } -> 13 | package { ['postgresql-evr']: 14 | ensure => 'present', 15 | notify => Class['postgresql::server::service'], 16 | require => Class['postgresql::server::install'], 17 | } 18 | } 19 | 20 | # Tune DB settings for Jenkins nodes, this is UNSAFE for production! 21 | $settings = { 22 | 'fsync' => 'off', 23 | 'full_page_writes' => 'off', 24 | 'synchronous_commit' => 'off', 25 | 'autovacuum' => 'off', 26 | 'effective_cache_size' => '512MB', 27 | 'shared_buffers' => '256MB', 28 | 'checkpoint_completion_target' => '0.9', 29 | 'wal_level' => 'minimal', 30 | 'max_wal_senders' => '0', 31 | } 32 | 33 | $settings.each |$setting, $value| { 34 | postgresql::server::config_entry { $setting: 35 | value => $value, 36 | } 37 | } 38 | 39 | Postgresql_psql { 40 | cwd => '/', 41 | } 42 | 43 | include postgresql::client 44 | include postgresql::server 45 | include postgresql::lib::devel 46 | 47 | # Simple known user/pass that will allow Jenkins to create its 48 | # own databases when required 49 | postgresql::server::role { 'foreman': 50 | password_hash => postgresql::postgresql_password('foreman', 'foreman'), 51 | superuser => true, 52 | login => true, 53 | require => Service['postgresql'], 54 | } 55 | 56 | jenkins_node::db_config { 'postgresql': 57 | require => Postgresql::Server::Role['foreman'], 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/files/pbuilder_F66-add-nodesource-nodistro-repos: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Nodesource 4 | if [[ $FOREMAN_VERSION == 3.1[4-5] ]]; then 5 | nodesrcv="18.x" 6 | else 7 | nodesrcv="22.x" 8 | fi 9 | echo "deb http://deb.nodesource.com/node_${nodesrcv} nodistro main" >> /etc/apt/sources.list 10 | 11 | cat > /etc/apt/trusted.gpg.d/nodesource-nodistro.asc <-backend.website01.osuosl.theforeman.org` 35 | * Enable shielding: a central system fetches the assets and then distributes them across the CDN instead of each CDN node fetches them itself, this costs more CDN traffic, but is usually faster 36 | * Configure a health-check and serve stale content when it fails 37 | 38 | #### TLS 39 | 40 | Fastly provides a shared certificate which has `theforeman.org` and `*.theforeman.org` as DNSAltName. 41 | 42 | This certificate is signed by GlobalSign and we have a `_globalsign-domain-verification` TXT record in the `theforeman.org` DNS zone for verification of ownership. 43 | 44 | #### DNS 45 | 46 | Each vhost has a CNAME pointing at `dualstack.p2.shared.global.fastly.net` which is the Fastly global, dualstack loadbalancer. 47 | 48 | Alternatively one can use `p2.shared.global.fastly.net` for an IPv4-only setup. 49 | 50 | ## Volumes 51 | 52 | `/var/www` is mounted on a separate block device. `/var/www/vhosts` contains the web roots themselves. 53 | 54 | ## Firewall 55 | 56 | There is no firewall on the machine itself. OpenStack has the following ports open: 57 | 58 | * 22/tcp (SSH) 59 | * 80/tcp (HTTP) 60 | * 443/tcp (HTTPS) 61 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/manifests/packaging/debian.pp: -------------------------------------------------------------------------------- 1 | # @api private 2 | class jenkins_node::packaging::debian ( 3 | String $user, 4 | Stdlib::Absolutepath $workspace, 5 | Boolean $uploader = true, 6 | Stdlib::HTTPUrl $debian_mirror = 'http://deb.debian.org/debian/', 7 | Stdlib::HTTPUrl $ubuntu_mirror = 'http://archive.ubuntu.com/ubuntu/', 8 | ) { 9 | package { 'gem2deb': 10 | ensure => present, 11 | } 12 | 13 | stdlib::ensure_packages(['python3-pip', 'python3-setuptools', 'zstd']) 14 | 15 | if $facts['os']['name'] == 'Debian' { 16 | include apt::backports 17 | 18 | Class['Apt::Backports'] -> 19 | apt::pin { 'debootstrap': 20 | packages => 'debootstrap', 21 | priority => 500, 22 | release => "${facts['os']['distro']['codename']}-backports", 23 | } -> 24 | Class['Pbuilder::Common'] 25 | } 26 | 27 | jenkins_node::pbuilder_setup { 28 | 'bookworm64': 29 | ensure => present, 30 | arch => 'amd64', 31 | release => 'bookworm', 32 | apturl => $debian_mirror, 33 | aptcontent => "deb ${debian_mirror} bookworm main non-free contrib\ndeb-src ${debian_mirror} bookworm main non-free contrib\n", 34 | nodesource => true; 35 | 'jammy64': 36 | ensure => present, 37 | arch => 'amd64', 38 | release => 'jammy', 39 | apturl => $ubuntu_mirror, 40 | aptcontent => "deb ${ubuntu_mirror} jammy main restricted universe\ndeb-src ${ubuntu_mirror} jammy main restricted universe\n"; 41 | } 42 | 43 | include sudo 44 | sudo::conf { 'sudo-puppet-pbuilder-envkeep': 45 | ensure => 'present', 46 | content => file('jenkins_node/pbuilder_sudoers'), 47 | } 48 | 49 | shellvar { 'extend_pbuilder_path': 50 | ensure => present, 51 | target => '/etc/pbuilderrc', 52 | variable => 'PATH', 53 | value => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', 54 | } 55 | 56 | if $uploader { 57 | # Add freight setup 58 | class { 'freight::uploader': 59 | user => $user, 60 | workspace => $workspace, 61 | } 62 | contain freight::uploader 63 | } 64 | 65 | # TODO: Cleanup failed pbuilder mounts as a cron 66 | } 67 | -------------------------------------------------------------------------------- /puppet/modules/web/manifests/vhost/stagingyum.pp: -------------------------------------------------------------------------------- 1 | # @summary Set up the yum vhost 2 | # @api private 3 | class web::vhost::stagingyum ( 4 | Array[String[1]] $usernames, 5 | Stdlib::Fqdn $servername = 'stagingyum.theforeman.org', 6 | Stdlib::Absolutepath $yum_directory = '/var/www/vhosts/stagingyum/htdocs', 7 | String $user = 'yumrepostage', 8 | Stdlib::Absolutepath $home = "/home/${user}", 9 | ) { 10 | $yum_directory_config = [ 11 | { 12 | path => $yum_directory, 13 | options => ['Indexes', 'FollowSymLinks', 'MultiViews'], 14 | expires_active => 'on', 15 | expires_default => 'access plus 2 minutes', 16 | }, 17 | { 18 | path => '.+\.(bz2|gz|rpm|xz)$', 19 | provider => 'filesmatch', 20 | expires_active => 'on', 21 | expires_default => 'access plus 30 days', 22 | }, 23 | { 24 | path => 'repomd.xml', 25 | provider => 'files', 26 | expires_active => 'on', 27 | expires_default => 'access plus 2 minutes', 28 | }, 29 | ] 30 | 31 | $authorized_keys = flatten($usernames.map |$name| { 32 | split(file("users/${name}-authorized_keys"), "\n") 33 | }) 34 | 35 | secure_ssh::rsync::receiver_setup { $user: 36 | user => $user, 37 | homedir => $home, 38 | homedir_mode => '0750', 39 | foreman_search => 'host ~ node*.jenkins.*.theforeman.org and (name = external_ip4 or name = external_ip6)', 40 | script_content => template('web/deploy-stagingyum.sh.erb'), 41 | authorized_keys => $authorized_keys, 42 | } 43 | 44 | web::vhost { 'stagingyum': 45 | servername => "stagingyum-backend.${facts['networking']['fqdn']}", 46 | docroot => $yum_directory, 47 | docroot_owner => $user, 48 | docroot_group => $user, 49 | docroot_mode => '0755', 50 | directories => $yum_directory_config, 51 | } 52 | 53 | ['HEADER.html', 'robots.txt'].each |$filename| { 54 | file { "${yum_directory}/${filename}": 55 | ensure => file, 56 | owner => 'root', 57 | group => 'root', 58 | mode => '0644', 59 | content => file("web/stagingyum/${filename}"), 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /puppet/modules/freight/templates/cron.erb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Basic plan: 4 | # * Figure out where all the matching debs are 5 | # * Group by package name 6 | # * Get a list of all packages by that name older than 7 days 7 | # * Watch out for architecture suffixes 8 | # * Remove the last entry (sorted by mtime) to keep the newest package 9 | # * Unlink the rest 10 | 11 | # Source the conffile 12 | unless ENV['VARLIB'] 13 | key = `source <%= @home %>/freight.conf 2> /dev/null && echo $VARLIB`.chomp 14 | ENV['VARLIB'] = key unless key.empty? 15 | end 16 | exit unless ENV['VARLIB'] 17 | 18 | Dir.chdir(ENV['VARLIB']) 19 | $time = Time.now - (60*60*24*3) # 3 days ago 20 | 21 | raw_files = [] 22 | # Globs 23 | <% if @cron_matches == 'all' then -%> 24 | raw_files += Dir.glob('**/*deb') 25 | <% else -%> 26 | <% @cron_matches.each do |cron| -%> 27 | raw_files += Dir.glob('**/*<%= cron %>*deb') # Input from Freight class 28 | <% end -%> 29 | <% end -%> 30 | 31 | raw_files += Dir.glob('**/nightly/*deb') # Always clean all of the nightly repo 32 | # Always keep the files in theforeman-* except theforeman-nightly 33 | raw_files -= Dir.glob('**/theforeman*/*deb') 34 | raw_files += Dir.glob('**/theforeman-nightly/*deb') 35 | raw_files.uniq! 36 | 37 | package_names = {} 38 | raw_files.map do |f| 39 | f.match(/^(.*?)_.*_(.*?).deb/) 40 | package_names[$1] ||= Array.new 41 | package_names[$1] << $2 42 | end 43 | package_names.each { |pn,archs| package_names[pn] = archs.uniq.compact } 44 | 45 | files_to_remove = [] 46 | 47 | keep = 0 48 | package_names.each do |pn,archs| 49 | # pn is something like "apt/wheezy/nightly/foreman-proxy" 50 | # archs is ['all'] or ['amd64', 'i386'] 51 | 52 | archs.each do |arch| 53 | packages = raw_files.map { |f| f if f.match /^#{pn}_.*_#{arch}.deb/ }.compact 54 | 55 | packages.sort! {|x,y| File.mtime(x) <=> File.mtime(y)} 56 | packages.delete_at(-1) # Always leave at least one file 57 | packages.delete_if { |f| File.mtime(f) > $time } 58 | 59 | files_to_remove += packages 60 | end 61 | end 62 | 63 | files_to_remove.uniq! 64 | File.unlink(*files_to_remove) 65 | 66 | system "sudo -u <%= @user %> /usr/bin/freight-cache -c <%= @home %>/freight.conf" 67 | system "chown -R <%= @user %> <%= @home %>" 68 | -------------------------------------------------------------------------------- /puppet/modules/redmine/files/git_repos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -o pipefail 4 | 5 | CODE_DIR=$1 6 | DATA_DIR=$2 7 | REDMINE_REPOS=https://raw.githubusercontent.com/theforeman/prprocessor/app/prprocessor/config/repos.yaml 8 | 9 | update_repo() { 10 | pushd $DATA_DIR >/dev/null 11 | dir=$1; shift; 12 | repo=$1; shift; 13 | [ -e git ] || mkdir git 14 | 15 | if [ ! -e git/${dir}.git ]; then 16 | git clone --quiet --bare ${repo} git/${dir}.git 17 | cd git/${dir}.git 18 | # Only follow HEAD 19 | git branch | grep -v '*' | xargs --no-run-if-empty git branch -D 20 | else 21 | cd git/${dir}.git 22 | fi 23 | 24 | if [ -z $1 ] ; then 25 | git fetch --quiet $repo HEAD:$(git rev-parse --abbrev-ref HEAD) 26 | else 27 | git fetch --quiet $repo $* 28 | fi 29 | 30 | popd >/dev/null 31 | } 32 | 33 | rails_runner() { 34 | RUBYOPT=-W0 bin/rails runner -e production "$@" 35 | } 36 | 37 | if [[ ! -n $CODE_DIR ]] || [[ ! -n $DATA_DIR ]] ; then 38 | echo "Usage: $0 CODE_DIR DATA_DIR" 39 | exit 1 40 | fi 41 | 42 | # Sync repositories for all known git repos 43 | curl --fail --silent $REDMINE_REPOS | ruby -ryaml -e ' 44 | YAML.safe_load(STDIN).each do |repo,config| 45 | branches = config["branches"] 46 | org_name, repo_name = repo.split("/", 2) 47 | puts "#{repo_name} https://github.com/#{repo} #{branches.nil? ? "" : branches.map { |branch| "#{branch}:#{branch}" }.join(" ")}" 48 | end' | while read repo; do 49 | update_repo $repo || echo "Failed to update $repo" > /dev/stderr 50 | done 51 | 52 | cd $CODE_DIR 53 | 54 | # Create repositories in the Redmine projects for all known git repos 55 | curl --fail --silent $REDMINE_REPOS | rails_runner ' 56 | YAML.safe_load(STDIN).each do |repo,config| 57 | project_name = config["redmine"] 58 | next if project_name.nil? 59 | org_name, repo_name = repo.split("/", 2) 60 | repo_path = File.join("'$DATA_DIR'", "git", "#{repo_name}.git") + File::SEPARATOR 61 | project = Project.find_by_identifier(project_name) or raise("cannot find project #{project_name}") 62 | Repository::Git.create!(:identifier => repo_name, :project => project, :url => repo_path) unless Repository.find_by_url(repo_path) 63 | end' 64 | 65 | # Import the changesets 66 | rails_runner "Repository.fetch_changesets" 67 | -------------------------------------------------------------------------------- /puppet/modules/profiles/manifests/base/monitoring.pp: -------------------------------------------------------------------------------- 1 | class profiles::base::monitoring ( 2 | Optional[String[1]] $prometheus_username = undef, 3 | Optional[String[1]] $prometheus_password = undef, 4 | Optional[Stdlib::HTTPUrl] $prometheus_url = undef, 5 | Array[Hash] $blackbox_targets = [], 6 | Array[String] $scrape_targets = [], 7 | ) { 8 | if $facts['os']['family'] == 'RedHat' { 9 | yumrepo { 'copr:copr.fedorainfracloud.org:group_theforeman:infra': 10 | descr => 'Copr repo for infra owned by @theforeman', 11 | baseurl => 'https://download.copr.fedorainfracloud.org/results/@theforeman/infra/centos-stream-$releasever-$basearch/', 12 | gpgcheck => '1', 13 | gpgkey => 'https://download.copr.fedorainfracloud.org/results/@theforeman/infra/pubkey.gpg', 14 | enabled => '1', 15 | notify => Package['node-exporter-textfile-collector-scripts'], 16 | } 17 | 18 | # node-exporter-textfile-collector-scripts needs moreutils, and that needs perl(IPC::Run) from CRB 19 | require crb 20 | 21 | # yum-utils contains /usr/bin/needs-restarting which is used by the yum collector 22 | stdlib::ensure_packages(['node-exporter-textfile-collector-scripts', 'yum-utils']) 23 | 24 | service { 'prometheus-node-exporter-yum.timer': 25 | ensure => 'running', 26 | enable => true, 27 | require => Package['node-exporter-textfile-collector-scripts'], 28 | } 29 | } elsif $facts['os']['family'] == 'Debian' { 30 | stdlib::ensure_packages(['prometheus-node-exporter-collectors']) 31 | } 32 | 33 | file { '/usr/local/bin/restic-prometheus-exporter': 34 | mode => '0755', 35 | owner => 'root', 36 | group => 'root', 37 | content => file("${module_name}/base/restic-prometheus-exporter.py"), 38 | } 39 | 40 | file { '/var/lib/prometheus/node-exporter/': 41 | ensure => directory, 42 | owner => 'prometheus', 43 | group => 'prometheus', 44 | mode => '0775', 45 | } 46 | 47 | $prom_context = { 48 | 'prometheus_username' => $prometheus_username, 49 | 'prometheus_password' => $prometheus_password, 50 | 'prometheus_url' => $prometheus_url, 51 | 'blackbox_targets' => $blackbox_targets, 52 | 'scrape_targets' => $scrape_targets, 53 | } 54 | 55 | class { 'grafana_alloy': 56 | config => epp("${module_name}/base/alloy-config.epp", $prom_context), 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /docs/bootstrap.md: -------------------------------------------------------------------------------- 1 | # Boostrap a new environment 2 | 3 | To rebuild the whole Foreman Infrastructure from scratch, these are the steps to get started. 4 | 5 | ## Build a Puppetserver 6 | 7 | Install a minimal EL9 install. Then: 8 | 9 | ```sh 10 | dnf install https://yum.voxpupuli.org/openvox8-release-el-9.noarch.rpm 11 | dnf install openvox-server 12 | . /etc/profile.d/puppet-agent.sh 13 | puppetserver ca setup --ca-name 'Foreman Puppet CA' --certname $HOSTNAME --subject-alt-names puppet.theforeman.org 14 | puppet config set --section agent server puppet.theforeman.org 15 | puppet config set --section main dns_alt_names puppet.theforeman.org 16 | # To allow the foreman node a SAN 17 | sed -i '/allow-subject-alt-names/ s/false/true/' /etc/puppetlabs/puppetserver/conf.d/ca.conf 18 | systemctl enable --now puppetserver puppet 19 | firewall-cmd --add-port 8140/tcp 20 | firewall-cmd --add-port 8140/tcp --permanent 21 | mkdir /etc/puppetlabs/code/environments/bootstrap 22 | chown YOURUSER: /etc/puppetlabs/code/environments/bootstrap 23 | ``` 24 | 25 | With a basic Puppetserver running, deploy the environment. From your local machine: 26 | 27 | ``` 28 | # Download the latest from https://github.com/xorpaul/g10k/releases and place it in ~/bin 29 | git clone https://github.com/theforeman/foreman-infra 30 | cd foreman-infra/puppet 31 | g10k -cachedir ~/.cache/.g10k -puppetfile 32 | rsync -av --delete --exclude={Gem,Rake,Puppet}file*,test_modules,spec,check_dependencies ./ SERVER.EXAMPLE.COM:/etc/puppetlabs/code/environments/bootstrap/ 33 | ``` 34 | 35 | ## Build a Foreman server 36 | 37 | Install a minimal EL9 install. Then: 38 | 39 | ```sh 40 | dnf install https://yum.voxpupuli.org/openvox8-release-el-9.noarch.rpm 41 | dnf install openvox-agent 42 | . /etc/profile.d/puppet-agent.sh 43 | puppet config set --section agent environment bootstrap 44 | puppet config set --section agent server puppet.theforeman.org 45 | puppet config set --section main dns_alt_names foreman.theforeman.org 46 | puppet ssl bootstrap 47 | systemctl enable --now puppet 48 | ``` 49 | 50 | In case it should become a production setup, import the database dump: 51 | ``` 52 | puppet agent --disable 53 | systemctl stop foreman\* dynflow\* 54 | sudo -u postgres dropdb foreman 55 | sudo -u postgres createdb -O foreman foreman 56 | sudo -u postgres psql foreman < /path/to/dump.sql 57 | foreman-rake db:migrate 58 | foreman-rake db:seed 59 | puppet agent --enable 60 | puppet agent -t 61 | ``` 62 | -------------------------------------------------------------------------------- /puppet/modules/freight/templates/deploy_debs.erb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Script to sync debs from staging to prod 4 | # Basic plan: 5 | # * Figure out where all the debs are 6 | # * Strip what is already in prod 7 | # * freight-add the rest 8 | # * freight-cache to finish 9 | # 10 | # Takes: 11 | # $1 - OS (eg wheezy, precise) 12 | # $2 - version (eg nightly, 1.6) 13 | # 14 | # Assumes debs are sourced from $os/theforeman-$version 15 | 16 | exit 1 unless ARGV.size == 2 17 | os=ARGV[0] 18 | version=ARGV[1] 19 | 20 | # Source the conffiles 21 | unless ENV['SOURCELIB'] 22 | key = `source /home/freightstage/freight.conf 2> /dev/null && echo $VARLIB`.chomp 23 | ENV['SOURCELIB'] = key unless key.empty? 24 | end 25 | unless ENV['TARGETLIB'] 26 | key = `source /home/freight/freight.conf 2> /dev/null && echo $VARLIB`.chomp 27 | ENV['TARGETLIB'] = key unless key.empty? 28 | end 29 | unless ENV['TARGETCACHE'] 30 | key = `source /home/freight/freight.conf 2> /dev/null && echo $VARCACHE`.chomp 31 | ENV['TARGETCACHE'] = key unless key.empty? 32 | end 33 | exit 2 unless ( ENV['SOURCELIB'] && ENV['TARGETLIB'] && ENV['TARGETCACHE'] ) 34 | 35 | puts "Deb deploy from #{os}/theforeman-#{version} to #{os}/#{version} starting: #{Time.now}" 36 | 37 | Dir.chdir("#{ENV['SOURCELIB']}/apt/#{os}/theforeman-#{version}") 38 | files = Dir.glob('*deb') 39 | 40 | if File.exist?("#{ENV['TARGETLIB']}/apt/#{os}/#{version}") 41 | Dir.chdir("#{ENV['TARGETLIB']}/apt/#{os}/#{version}") 42 | files -= Dir.glob('*deb') 43 | end 44 | 45 | files.each do |file| 46 | system "/usr/bin/freight-add -v -c /home/freight/freight.conf #{ENV['SOURCELIB']}/apt/#{os}/theforeman-#{version}/#{file} apt/#{os}/#{version}" 47 | end 48 | system "/usr/bin/freight-cache -v -c /home/freight/freight.conf apt/#{os}" 49 | 50 | foreman_release_pool = "pool/#{os}/#{version}/f/foreman-release" 51 | foreman_release_dir = "#{ENV['TARGETCACHE']}/#{foreman_release_pool}" 52 | release_files = Dir.glob("#{foreman_release_dir}/foreman-release_*.deb") 53 | latest_release_file = release_files.max_by { |f| File.mtime(f) } 54 | if latest_release_file 55 | release_symlink = "#{foreman_release_dir}/foreman-release.deb" 56 | File.unlink(release_symlink) if File.exist?(release_symlink) 57 | File.symlink(latest_release_file, release_symlink) 58 | system "fastly-purge https://deb.theforeman.org/#{foreman_release_pool} foreman-release.deb" 59 | end 60 | 61 | puts "Deb deploy complete: #{Time.now}" 62 | # ERB highlighting looks terrible in this script... 63 | # vim: set ft=ruby : # 64 | -------------------------------------------------------------------------------- /docs/repo-rpm.md: -------------------------------------------------------------------------------- 1 | # RPM repository server 2 | 3 | | | repo-rpm01.osuosl.theforeman.org | 4 | | - | - | 5 | | type | OpenStack VM | 6 | | OS | CentOS Stream 9 | 7 | | CPUs | 2 | 8 | | RAM | 4GB | 9 | | Storage | /dev/sda (30GB): root, /dev/sdb (100GB): data (LVM) | 10 | | Managed by | [rpm.pp](https://github.com/theforeman/foreman-infra/blob/master/puppet/modules/profiles/manifests/repo/rpm.pp) | 11 | 12 | ## Domains 13 | 14 | These domains are all hosted on the server. 15 | 16 | * rpm.theforeman.org 17 | * stagingrpm.theforeman.org 18 | * yum.theforeman.org 19 | * stagingyum.theforeman.org 20 | 21 | ### Backends 22 | 23 | This server does not host the domains directly, but has the following backend vhosts configured: 24 | 25 | * rpm-backend.repo-rpm01.osuosl.theforeman.org 26 | * stagingrpm-backend.repo-rpm01.osuosl.theforeman.org 27 | * yum-backend.repo-rpm01.osuosl.theforeman.org 28 | * stagingyum-backend.repo-rpm01.osuosl.theforeman.org 29 | 30 | #### TLS 31 | 32 | The backends have TLS certificates from Let's Encrypt, using the HTTP challenge. 33 | 34 | This allows the frontend to talk securely to the backends. 35 | 36 | ### Fastly CDN 37 | 38 | The frontend is served by the Fastly CDN. 39 | 40 | The configuration happens through the `ansible/fastly.yml` Ansible playbook in this repository. 41 | 42 | The major points of the configuration are: 43 | 44 | * Set the backend to `-backend.repo-rpm01.osuosl.theforeman.org` 45 | * Enable shielding: a central system fetches the assets and then distributes them across the CDN instead of each CDN node fetches them itself, this costs more CDN traffic, but is usually faster 46 | * Configure a health-check and serve stale content when it fails 47 | 48 | #### TLS 49 | 50 | Fastly provides a shared certificate which has `theforeman.org` and `*.theforeman.org` as DNSAltName. 51 | 52 | This certificate is signed by GlobalSign and we have a `_globalsign-domain-verification` TXT record in the `theforeman.org` DNS zone for verification of ownership. 53 | 54 | #### DNS 55 | 56 | Each vhost has a CNAME pointing at `dualstack.p2.shared.global.fastly.net` which is the Fastly global, dualstack loadbalancer. 57 | 58 | Alternatively one can use `p2.shared.global.fastly.net` for an IPv4-only setup. 59 | 60 | ## Volumes 61 | 62 | `/var/www` is mounted on a separate block device. `/var/www/vhosts` contains the web roots themselves. 63 | 64 | ## Firewall 65 | 66 | There is no firewall on the machine itself. OpenStack has the following ports open: 67 | 68 | * 22/tcp (SSH) 69 | * 80/tcp (HTTP) 70 | * 443/tcp (HTTPS) 71 | -------------------------------------------------------------------------------- /puppet/modules/ssh/lib/puppet/functions/ssh/keygen.rb: -------------------------------------------------------------------------------- 1 | # Forked from https://github.com/fup/puppet-ssh @ 59684a8ae174 2 | # Rewritten to the modern Puppet functions API 3 | Puppet::Functions.create_function(:'ssh::keygen') do 4 | # @param name (the name of the key - e.g 'my_ssh_key') 5 | # Optional parameters: 6 | # @param type (the key type - default: 'rsa') 7 | # @param dir (the subdir of Puppet's vardir to store the key in - default: 'ssh') 8 | # @param size (the key size - default 2048) 9 | # @param public (if specified, reads the public key instead of the private key) 10 | dispatch :keygen do 11 | param 'String[1]', :name 12 | optional_param 'Boolean', :is_public 13 | # Taken from man ssh-keygen with openssh-clients-8.8p1-9.fc37.x86_64 14 | optional_param "Enum['dsa', 'ecdsa', 'ecdsa-sk', 'ed25519', 'ed25519-sk', 'rsa']", :type 15 | optional_param 'Integer[256]', :size 16 | optional_param 'String[1]', :dir 17 | return_type 'String' 18 | end 19 | 20 | def keygen(name, is_public = false, type = 'rsa', size = 2048, dir = 'ssh') 21 | require 'fileutils' 22 | 23 | case type 24 | when 'dsa' 25 | # Ensure dsa uses keylength 1024 26 | size = 1024 27 | when 'ecdsa' 28 | # ECDSA must be 256, 384 or 521 bits 29 | # Ensure ecdsa uses keylength 521 30 | size = 521 31 | end 32 | 33 | parent_dir = File.join(Puppet[:vardir], dir) 34 | absolute_path = File.join(parent_dir, name) 35 | 36 | # Make sure to write out a directory to init if necessary 37 | begin 38 | FileUtils.mkdir_p(parent_dir) 39 | rescue => e 40 | # TODO: other exception? 41 | raise Puppet::ParseError, "ssh::keygen(): Unable to setup ssh keystore directory (#{e}) #{%x[whoami]}" 42 | end 43 | 44 | # Do my keys exist? Well, keygen if they don't! 45 | unless File.exist?(absolute_path) then 46 | command = ['/usr/bin/ssh-keygen', '-t', type, '-b', size, '-P', '', '-f', absolute_path] 47 | Puppet::Util::Execution.execute(command, failonfail: true) 48 | end 49 | 50 | # Return ssh key content based on request 51 | begin 52 | if is_public 53 | request = 'public' 54 | pub_key = File.read("#{absolute_path}.pub") 55 | pub_key.scan(/^.* (.*) .*$/)[0][0] 56 | else 57 | request = 'private' 58 | File.read(absolute_path) 59 | end 60 | rescue => e 61 | # TODO: other exception? 62 | raise Puppet::ParseError, "ssh::keygen(): Unable to read ssh #{request} key (#{e})" 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /puppet/modules/web/manifests/jenkins.pp: -------------------------------------------------------------------------------- 1 | class web::jenkins ( 2 | Stdlib::Fqdn $hostname = 'ci.theforeman.org', 3 | Stdlib::Absolutepath $webroot = '/var/www/vhosts/jenkins/htdocs', 4 | Boolean $https = false, 5 | ) { 6 | include web::base 7 | 8 | $proxy_pass = { 9 | 'path' => '/', 10 | 'url' => 'http://localhost:8080/', 11 | 'keywords' => ['nocanon'], 12 | 'no_proxy_uris' => ['/.well-known'], 13 | } 14 | 15 | if $https { 16 | include web::letsencrypt 17 | 18 | letsencrypt::certonly { $hostname: 19 | plugin => 'webroot', 20 | domains => [$hostname], 21 | webroot_paths => [$webroot], 22 | } 23 | } 24 | 25 | if $facts['os']['selinux']['enabled'] { 26 | selboolean { 'httpd_can_network_connect': 27 | persistent => true, 28 | value => 'on', 29 | } 30 | } 31 | 32 | file { dirname($webroot): 33 | ensure => directory, 34 | owner => 'root', 35 | group => 'root', 36 | mode => '0755', 37 | } 38 | 39 | if $https { 40 | $url = "https://${hostname}" 41 | 42 | apache::vhost { 'jenkins': 43 | port => 80, 44 | servername => $hostname, 45 | docroot => $webroot, 46 | docroot_owner => $apache::user, 47 | docroot_group => $apache::group, 48 | redirect_dest => "https://${hostname}/", 49 | } 50 | apache::vhost { 'jenkins-https': 51 | port => 443, 52 | servername => $hostname, 53 | docroot => $webroot, 54 | docroot_owner => $apache::user, 55 | docroot_group => $apache::group, 56 | proxy_pass => $proxy_pass, 57 | allow_encoded_slashes => 'nodecode', 58 | request_headers => ['set X-Forwarded-Proto "https"'], 59 | ssl => true, 60 | ssl_cert => "/etc/letsencrypt/live/${hostname}/fullchain.pem", 61 | ssl_chain => "/etc/letsencrypt/live/${hostname}/chain.pem", 62 | ssl_key => "/etc/letsencrypt/live/${hostname}/privkey.pem", 63 | require => Letsencrypt::Certonly[$hostname], 64 | } 65 | } else { 66 | $url = "http://${hostname}" 67 | 68 | apache::vhost { 'jenkins': 69 | port => 80, 70 | servername => $hostname, 71 | docroot => $webroot, 72 | docroot_owner => $apache::user, 73 | docroot_group => $apache::group, 74 | proxy_pass => $proxy_pass, 75 | allow_encoded_slashes => 'nodecode', 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /docs/virt.md: -------------------------------------------------------------------------------- 1 | # Virtualization host 2 | 3 | | | virt01.conova.theforeman.org | 4 | |-|-| 5 | | type | HPE ProLiant DL325 Gen10 | 6 | | OS | CentOS Stream 9 | 7 | | CPUs | AMD EPYC 7402P 24-Core Processor | 8 | | RAM | 192GB | 9 | | Storage | 2 * 1TB SSD NVMe | 10 | 11 | ## Installation 12 | 13 | Set up networking: 14 | 15 | ``` 16 | nmcli connection add type bridge con-name "Bridge connection 1" ifname br0 17 | nmcli connection modify bridge0 ipv4.addresses '195.192.212.25/29' 18 | nmcli connection modify bridge0 ipv4.gateway '195.192.212.30' 19 | nmcli connection modify bridge0 ipv4.dns '217.196.144.129' 20 | nmcli connection modify bridge0 ipv4.dns-search 'conova.theforeman.org' 21 | nmcli connection modify bridge0 ipv4.method manual 22 | nmcli connection modify bridge0 ipv6.method auto 23 | nmcli connection add type bond con-name Bond connection 1" ifname bond0 bond.options "mode=802.3ad,downdelay=0,miimon=1,updelay=0" ipv4.method disabled ipv6.method ignore master "Bridge connection 1" 24 | nmcli connection add type ethernet ifname eno5np0 master bond0 25 | nmcli connection add type ethernet ifname eno6np1 master bond0 26 | nmcli connection up bridge0 27 | ``` 28 | 29 | Note the options were derived after the fact. They may not be 100% correct. See [RHEL 8 networking documentation](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/configuring-a-network-bridge_configuring-and-managing-networking) for more. 30 | 31 | Now install libvirt: 32 | 33 | ``` 34 | dnf group install 'Virtualization Host' 35 | sed -i '/unix_sock_group/ s/^#//' /etc/libvirt/libvirtd.conf 36 | systemctl enable --now libvirtd 37 | ``` 38 | 39 | Now bootstrap Puppet: 40 | ``` 41 | dnf install https://yum.voxpupuli.org/openvox8-release-el-9.noarch.rpm 42 | dnf install openvox-agent 43 | . /etc/profile.d/puppet-agent.sh 44 | puppet config set server puppet.theforeman.org 45 | puppet ssl bootstrap 46 | puppet agent -t 47 | ``` 48 | 49 | ## Storage 50 | 51 | The system has 2 1TB NVMe drives, which are configured as individual drives, not as RAID in the HP firmware. 52 | 53 | The OS and the virt guests are residing on LVM, with select LVs in RAID1 mode. 54 | 55 | ### converting an existing LV to RAID1 56 | 57 | ``` 58 | lvconvert --type raid1 -m 1 cs_node01/ 59 | ``` 60 | 61 | See [Converting a Linear device to a RAID logical volume](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_and_managing_logical_volumes/configuring-raid-logical-volumes_configuring-and-managing-logical-volumes#converting-a-linear-device-to-a-raid-logical-volume_configuring-raid-logical-volumes). 62 | -------------------------------------------------------------------------------- /docs/repo-deb.md: -------------------------------------------------------------------------------- 1 | # DEB repository server 2 | 3 | | | repo-deb01.osuosl.theforeman.org | 4 | | - | - | 5 | | type | OpenStack VM | 6 | | OS | CentOS Stream 9 | 7 | | CPUs | 2 | 8 | | RAM | 4GB | 9 | | Storage | /dev/sda (30GB): root, /dev/sdb (150GB): data (LVM) | 10 | | Managed by | [deb.pp](https://github.com/theforeman/foreman-infra/blob/master/puppet/modules/profiles/manifests/repo/deb.pp) | 11 | 12 | ## Domains 13 | 14 | These domains are all hosted on the server. 15 | 16 | * deb.theforeman.org 17 | * stagingdeb.theforeman.org 18 | * archivedeb.theforeman.org 19 | 20 | ### Backends 21 | 22 | This server does not host the domains directly, but has the following backend vhosts configured: 23 | 24 | * deb-backend.repo-deb01.osuosl.theforeman.org 25 | * stagingdeb-backend.repo-deb01.osuosl.theforeman.org 26 | * archivedeb-backend.repo-deb01.osuosl.theforeman.org 27 | 28 | #### TLS 29 | 30 | The backends have TLS certificates from Let's Encrypt, using the HTTP challenge. 31 | 32 | This allows the frontend to talk securely to the backends. 33 | 34 | ### Fastly CDN 35 | 36 | The frontend is served by the Fastly CDN. 37 | 38 | The configuration happens through the `ansible/fastly.yml` Ansible playbook in this repository. 39 | 40 | The major points of the configuration are: 41 | 42 | * Set the backend to `-backend.repo-deb01.osuosl.theforeman.org` 43 | * Enable shielding: a central system fetches the assets and then distributes them across the CDN instead of each CDN node fetches them itself, this costs more CDN traffic, but is usually faster 44 | * Configure a health-check and serve stale content when it fails 45 | 46 | #### TLS 47 | 48 | Fastly provides a shared certificate which has `theforeman.org` and `*.theforeman.org` as DNSAltName. 49 | 50 | This certificate is signed by GlobalSign and we have a `_globalsign-domain-verification` TXT record in the `theforeman.org` DNS zone for verification of ownership. 51 | 52 | #### DNS 53 | 54 | Each vhost has a CNAME pointing at `dualstack.p2.shared.global.fastly.net` which is the Fastly global, dualstack loadbalancer. 55 | 56 | Alternatively one can use `p2.shared.global.fastly.net` for an IPv4-only setup. 57 | 58 | ## Volumes 59 | 60 | `/var/www` is mounted on a separate block device. `/var/www/freight*` contains the staging areas for freight (deb), and `/var/www/vhosts` contains the web roots themselves. 61 | 62 | ## Firewall 63 | 64 | There is no firewall on the machine itself. OpenStack has the following ports open: 65 | 66 | * 22/tcp (SSH) 67 | * 80/tcp (HTTP) 68 | * 443/tcp (HTTPS) 69 | 70 | ## Other 71 | 72 | * freight and freightstage users have private auto-signing GPG key imported manually (non-puppetized) 73 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/manifests/pbuilder_setup.pp: -------------------------------------------------------------------------------- 1 | # @summary Set up pbuilder on a Debian package builder 2 | # @api private 3 | define jenkins_node::pbuilder_setup ( 4 | String[1] $arch, 5 | String[1] $release, 6 | String[1] $apturl, 7 | String[1] $aptcontent, 8 | Enum['present', 'absent'] $ensure = present, 9 | Boolean $backports = false, 10 | Boolean $nodesource = true, 11 | Boolean $puppetlabs = true, 12 | ) { 13 | pbuilder { $name: 14 | ensure => $ensure, 15 | arch => $arch, 16 | release => $release, 17 | methodurl => $apturl, 18 | } 19 | 20 | file { "/etc/pbuilder/${name}/apt.config/sources.list.d": 21 | ensure => bool2str($ensure == present, 'directory', 'absent'), 22 | } 23 | 24 | file { "/etc/pbuilder/${name}/apt.config/sources.list.d/debian.list": 25 | ensure => $ensure, 26 | content => $aptcontent, 27 | } 28 | if $ensure == present { 29 | File["/etc/pbuilder/${name}/apt.config/sources.list.d/debian.list"] ~> Exec["update_pbuilder_${name}"] 30 | } 31 | 32 | file { "/usr/local/bin/pdebuild-${name}": 33 | ensure => $ensure, 34 | mode => '0775', 35 | content => template('jenkins_node/pbuilder_pdebuild.erb'), 36 | } 37 | 38 | $hooks = { 39 | 'A10nozstd' => $release == 'jammy', 40 | 'C10foremanlog' => true, 41 | 'D80no-man-db-rebuild' => true, 42 | 'F60addforemanrepo' => true, 43 | 'F65-add-backport-repos' => $backports, 44 | 'F66-add-nodesource-nodistro-repos' => $nodesource, 45 | 'F67-add-puppet-repos' => $puppetlabs, 46 | 'F70aptupdate' => true, 47 | 'F99printrepos' => true, 48 | } 49 | 50 | $hooks.each |$hook, $enabled| { 51 | $hook_path = "/etc/pbuilder/${name}/hooks/${hook}" 52 | if $enabled { 53 | file { $hook_path: 54 | ensure => $ensure, 55 | mode => '0775', 56 | content => file("jenkins_node/pbuilder_${hook}"), 57 | } 58 | } else { 59 | file { $hook_path: 60 | ensure => absent, 61 | } 62 | } 63 | } 64 | 65 | # the result cache gets huge after a while - trim it to the last ~2 days at 5am 66 | file { "/etc/cron.d/cleanup-${name}": 67 | ensure => bool2str($ensure == present, 'file', 'absent'), 68 | mode => '0644', 69 | content => "11 5 * * * root find /var/cache/pbuilder/${name}/result -mindepth 1 -mtime +1 -delete\n", 70 | } 71 | 72 | file { "/etc/cron.d/update-${name}": 73 | ensure => bool2str($ensure == present, 'file', 'absent'), 74 | mode => '0644', 75 | content => "11 4 * * * root /usr/local/bin/pbuilder-${name} update\n", 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /puppet/modules/secure_ssh/README.md: -------------------------------------------------------------------------------- 1 | # Puppet module for managing secure ways to SSH or copy files using easily revokable ssh keys 2 | 3 | This module uses Puppet to set up ssh and optionally rsync in such a way that: 4 | 5 | * The key is revokable from the puppetmaster 6 | * The uploader cannot use the key for anything other than running a single script, or 7 | optionally rsyncing files 8 | * The uploader cannot upload to anywhere other than the intended location 9 | * The receiver can run an arbitrary script upon receiving the files to sanitize 10 | and distribute them to the correct place 11 | 12 | As an example, this module is used in the TheForeman infrastructure to 13 | 14 | * Build the `theforeman.org` website on jenkins 15 | * Rsync the compiled static site to `theforeman.org:~website/rsync_cache/` 16 | * The webserver then chmod's all the files correctly and copies them to the vhost, 17 | thus allowing SELinux to prevent direct scp to the vhost. 18 | 19 | ## Requirements 20 | 21 | Depends upon https://github.com/GregSutcliffe/puppet-ssh_statickeys 22 | 23 | ## Example config 24 | 25 | There are two ways to use this module 26 | 27 | ### Adding resources to your manifests 28 | 29 | If you wish to simply add extra resources to your manifests, the defines in 30 | this module can be called directly. For example, if you have a website and a 31 | node which pushes content to the site, you might already have a "website" 32 | class and a "jenkins_node" class. In which case you might add the following: 33 | 34 | ``` 35 | class jenkins_node { 36 | secure_ssh::uploader_key { 'website': 37 | user => 'jenkins', 38 | dir => '/home/jenkins', 39 | manage_dir => true, 40 | } 41 | # rest of your jenkins_node class here... 42 | } 43 | 44 | class website { 45 | secure_ssh::receiver_setup { 'website': 46 | user => 'website', 47 | allowed_ips => [ '1.2.3.4' ], 48 | } 49 | # rest of your website class here ... 50 | } 51 | 52 | ### Specifying all config from the ENC 53 | 54 | Alternatively, if you wish to define all the appropriate config from your ENC, 55 | there is a set of wrapper classes you can use to do this: 56 | 57 | On the uploader, apply a hash of private & public keys to create: 58 | 59 | ``` 60 | $keys = { 61 | "test" => { 62 | "user" => "jenkins", 63 | "dir" => "/home/jenkins", 64 | "manage_dir" => "true", 65 | } 66 | } 67 | class { 'secure_ssh::uploader': keys => $keys } 68 | ``` 69 | 70 | On the receiver, apply a hash containing the key names and actions to take: 71 | 72 | ``` 73 | $conf = { 74 | "test" => { 75 | "user" => "website", 76 | "allowed_ips" => ['1.2.3.4','5.6.7.8'] 77 | } 78 | } 79 | class { 'secure_ssh::receiver': keys => $conf } 80 | ``` 81 | -------------------------------------------------------------------------------- /ansible/fastly.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | gather_facts: False 4 | roles: 5 | - Jimdo.fastly 6 | vars_files: 7 | - secrets.yml 8 | tasks: 9 | - name: configure fastly service 10 | fastly_service: 11 | name: "{{ item.service }}.theforeman.org" 12 | fastly_api_key: "{{ fastly_api_key }}" 13 | domains: "{{ domains }}" 14 | backends: 15 | - name: "{{ item.backend }}.theforeman.org" 16 | address: "{{ item.backend }}.theforeman.org" 17 | port: 443 18 | shield: iad-va-us 19 | ssl_cert_hostname: "{{ item.backend }}.theforeman.org" 20 | ssl_sni_hostname: "{{ item.backend }}.theforeman.org" 21 | override_host: "{{ item.backend }}.theforeman.org" 22 | healthcheck: "{{ item.healthcheck | default('HEADER.html') }}" 23 | healthchecks: 24 | - name: "{{ item.healthcheck | default('HEADER.html') }}" 25 | host: "{{ item.backend }}.theforeman.org" 26 | path: "/{{ item.healthcheck | default('HEADER.html') }}" 27 | threshold: 1 28 | timeout: 5000 29 | window: 2 30 | initial: 1 31 | check_interval: 60000 32 | headers: 33 | - name: Location 34 | action: regex 35 | dst: http.Location 36 | ignore_if_set: 0 37 | priority: 10 38 | regex: "{{ item.backend }}.theforeman.org" 39 | src: resp.http.Location 40 | substitution: "{{ domains[0].name }}" 41 | type: response 42 | request_settings: "{{ item.request_settings | default(omit) }}" 43 | vars: 44 | domains: "{{ item.domains | default([{'name': item.service ~ '.theforeman.org'}]) }}" 45 | with_items: 46 | - service: archivedeb 47 | backend: archivedeb-backend.repo-deb01.osuosl 48 | - service: deb 49 | backend: deb-backend.repo-deb01.osuosl 50 | - service: downloads 51 | backend: downloads-backend.website01.osuosl 52 | - service: stagingdeb 53 | backend: stagingdeb-backend.repo-deb01.osuosl 54 | - service: stagingrpm 55 | backend: stagingrpm-backend.repo-rpm01.osuosl 56 | - service: rpm 57 | backend: rpm-backend.repo-rpm01.osuosl 58 | - service: stagingyum 59 | backend: stagingyum-backend.repo-rpm01.osuosl 60 | - service: yum 61 | backend: yum-backend.repo-rpm01.osuosl 62 | - service: www 63 | backend: web-backend.website01.osuosl 64 | healthcheck: index.html 65 | domains: 66 | - name: theforeman.org 67 | - name: www.theforeman.org 68 | request_settings: 69 | - name: force TLS 70 | force_ssl: 1 71 | -------------------------------------------------------------------------------- /puppet/modules/profiles/templates/base/alloy-config.epp: -------------------------------------------------------------------------------- 1 | <%- | 2 | Optional[String[1]] $prometheus_username, 3 | Optional[String[1]] $prometheus_password, 4 | Optional[Stdlib::HTTPUrl] $prometheus_url, 5 | Array[Hash] $blackbox_targets, 6 | Array[String] $scrape_targets, 7 | | -%> 8 | logging { 9 | level = "info" 10 | } 11 | 12 | prometheus.exporter.unix "default" { 13 | // we don't use it, but it produces log entries 14 | disable_collectors = ["zfs"] 15 | 16 | textfile { 17 | directory = "/var/lib/prometheus/node-exporter/" 18 | } 19 | 20 | // ignore virtual devices from libvirt, docker and friends 21 | netclass { 22 | ignored_devices = "^(vnet.*|veth.*|cali.*|[a-f0-9]{15})$" 23 | } 24 | 25 | netdev { 26 | device_exclude = "^(vnet.*|veth.*|cali.*|[a-f0-9]{15})$" 27 | } 28 | 29 | // add "tmpfs" to the default exclude 30 | filesystem { 31 | fs_types_exclude = "^(autofs|binfmt_misc|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|tmpfs|fusectl|hugetlbfs|iso9660|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tracefs)$" 32 | } 33 | } 34 | 35 | discovery.relabel "node_exporter" { 36 | targets = prometheus.exporter.unix.default.targets 37 | 38 | rule { 39 | target_label = "job" 40 | replacement = "integrations/node_exporter" 41 | } 42 | } 43 | 44 | prometheus.exporter.blackbox "blackbox" { 45 | config = "{ modules: { http: { prober: http, http: {ip_protocol_fallback: false, preferred_ip_protocol: ip4} }, http6: { prober: http, http: { ip_protocol_fallback: false, preferred_ip_protocol: ip6 } } } }" 46 | 47 | <% $blackbox_targets.each |$target| { %> 48 | target { 49 | name = "<%= $target['name'] %>" 50 | address = "<%= $target['address'] %>" 51 | module = "<%= $target['module'] %>" 52 | } 53 | <% } %> 54 | } 55 | 56 | discovery.relabel "blackbox" { 57 | targets = prometheus.exporter.blackbox.blackbox.targets 58 | 59 | rule { 60 | source_labels = ["job"] 61 | regex = "integrations/blackbox/(.*)" 62 | target_label = "instance" 63 | } 64 | } 65 | 66 | prometheus.scrape "default" { 67 | targets = array.concat( 68 | discovery.relabel.node_exporter.output, 69 | discovery.relabel.blackbox.output, 70 | <% $scrape_targets.each |$target| { %> 71 | [<%= $target %>], 72 | <% } %> 73 | ) 74 | 75 | forward_to = [prometheus.remote_write.grafanacloud.receiver] 76 | } 77 | 78 | <% if $prometheus_url { -%> 79 | prometheus.remote_write "grafanacloud" { 80 | endpoint { 81 | url = "<%= $prometheus_url %>" 82 | 83 | <%- if $prometheus_username and $prometheus_password { -%> 84 | basic_auth { 85 | username = "<%= $prometheus_username %>" 86 | password = "<%= $prometheus_password %>" 87 | } 88 | <%- } -%> 89 | } 90 | } 91 | <% } -%> 92 | -------------------------------------------------------------------------------- /puppet/modules/web/manifests/vhost/stagingrpm.pp: -------------------------------------------------------------------------------- 1 | # @summary Set up the rpm staging vhost 2 | # @api private 3 | class web::vhost::stagingrpm ( 4 | Array[String[1]] $usernames, 5 | Stdlib::Fqdn $servername = 'stagingrpm.theforeman.org', 6 | Stdlib::Absolutepath $rpm_staging_directory = '/var/www/vhosts/stagingrpm/htdocs', 7 | String $user = 'rpmrepostage', 8 | Stdlib::Absolutepath $home = "/home/${user}", 9 | ) { 10 | $rpm_staging_directory_config = [ 11 | { 12 | path => $rpm_staging_directory, 13 | options => ['Indexes', 'FollowSymLinks'], 14 | expires_active => 'on', 15 | expires_default => 'access plus 2 minutes', 16 | }, 17 | { 18 | path => '.+\.(bz2|gz|rpm|xz)$', 19 | provider => 'filesmatch', 20 | expires_active => 'on', 21 | expires_default => 'access plus 30 days', 22 | }, 23 | { 24 | path => 'repomd.xml', 25 | provider => 'files', 26 | expires_active => 'on', 27 | expires_default => 'access plus 2 minutes', 28 | }, 29 | ] 30 | 31 | include apache::mod::alias 32 | include apache::mod::autoindex 33 | include apache::mod::dir 34 | include apache::mod::expires 35 | include apache::mod::mime 36 | 37 | $authorized_keys = flatten($usernames.map |$name| { 38 | split(file("users/${name}-authorized_keys"), "\n") 39 | }) 40 | 41 | secure_ssh::rsync::receiver_setup { $user: 42 | user => $user, 43 | homedir => $home, 44 | homedir_mode => '0750', 45 | foreman_search => 'host ~ node*.jenkins.*.theforeman.org and (name = external_ip4 or name = external_ip6)', 46 | authorized_keys => $authorized_keys, 47 | script_content => epp("${module_name}/deploy-stagingrpm.sh.epp", { 48 | 'home' => $home, 49 | 'rpm_staging_directory' => $rpm_staging_directory, 50 | }), 51 | } 52 | 53 | web::vhost { 'stagingrpm': 54 | servername => "stagingrpm-backend.${facts['networking']['fqdn']}", 55 | docroot => $rpm_staging_directory, 56 | docroot_owner => $user, 57 | docroot_group => $user, 58 | docroot_mode => '0755', 59 | directories => $rpm_staging_directory_config, 60 | } 61 | 62 | file { "${rpm_staging_directory}/robots.txt": 63 | ensure => file, 64 | owner => 'root', 65 | group => 'root', 66 | mode => '0644', 67 | content => file('web/stagingrpm/robots.txt'), 68 | } 69 | 70 | file { "${rpm_staging_directory}/HEADER.html": 71 | ensure => file, 72 | owner => 'root', 73 | group => 'root', 74 | mode => '0644', 75 | content => epp("${module_name}/stagingrpm/HEADER.html.epp", { 76 | 'servername' => $servername, 77 | }), 78 | } 79 | 80 | ['candlepin', 'foreman', 'pulpcore'].each |$directory| { 81 | file { ["${rpm_staging_directory}/${directory}"]: 82 | ensure => directory, 83 | owner => $user, 84 | group => $user, 85 | mode => '0755', 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /puppet/modules/rbenv/manifests/build.pp: -------------------------------------------------------------------------------- 1 | # Calling this define will install Ruby in your default rbenv 2 | # installs directory. Additionally, it can define the installed 3 | # ruby as the global interpretter. It will install the bundler gem. 4 | # 5 | # @param install_dir 6 | # This is set when you declare the rbenv class. There is no 7 | # need to overrite it when calling the rbenv::build define. 8 | # 9 | # @param global 10 | # This is used to set the ruby to be the global interpreter. 11 | # 12 | # @param env 13 | # This is used to set environment variables when compiling ruby. 14 | # 15 | # @param rubygems_version 16 | # This is used to set a specific version of rubygems. 17 | # 18 | # @param bundler_version 19 | # This is used to set a specific version of bundler. 20 | # 21 | # @example Install Ruby 2.7.8 as global 22 | # rbenv::build { '2.7.8': 23 | # global => true, 24 | # } 25 | # 26 | define rbenv::build ( 27 | Stdlib::Absolutepath $install_dir = $rbenv::install_dir, 28 | Boolean $global = false, 29 | Array[String[1]] $env = $rbenv::env, 30 | Optional[String] $rubygems_version = undef, 31 | Optional[String] $bundler_version = undef, 32 | Optional[String[1]] $user = $rbenv::user, 33 | ) { 34 | include rbenv 35 | 36 | $base_env = ["RBENV_ROOT=${install_dir}"] 37 | 38 | Exec { 39 | cwd => $install_dir, 40 | timeout => 1800, 41 | path => [ 42 | "${install_dir}/bin/", 43 | "${install_dir}/shims/", 44 | '/bin/', 45 | '/sbin/', 46 | '/usr/bin/', 47 | '/usr/sbin/', 48 | ], 49 | user => $user, 50 | } 51 | 52 | exec { "rbenv-install-${title}": 53 | command => ['rbenv', 'install', $title], 54 | environment => $base_env + $env, 55 | creates => "${install_dir}/versions/${title}", 56 | require => Class['rbenv'], 57 | } 58 | 59 | if $rubygems_version { 60 | exec { "rubygems-${rubygems_version}": 61 | command => "gem update --system ${rubygems_version}", 62 | environment => $base_env, 63 | require => Exec["rbenv-install-${title}"], 64 | unless => "gem --version | grep -q ${rubygems_version}", 65 | } 66 | 67 | # In case the rubygems version is set, it should be called before installing bundler. Otherwise you could run into 68 | # a series of issues like 69 | # https://bundler.io/blog/2019/05/14/solutions-for-cant-find-gem-bundler-with-executable-bundle.html. 70 | if $bundler_version { 71 | Exec["rubygems-${rubygems_version}"] -> Rbenv::Gem["bundler-${title}"] 72 | } 73 | } 74 | 75 | if $bundler_version { 76 | rbenv::gem { "bundler-${title}": 77 | gem => 'bundler', 78 | ruby_version => $title, 79 | skip_docs => true, 80 | version => $bundler_version, 81 | } 82 | } 83 | 84 | if $global { 85 | exec { "rbenv-global-${title}": 86 | command => ['rbenv', 'global', $title], 87 | environment => $base_env, 88 | require => Exec["rbenv-install-${title}"], 89 | refreshonly => true, 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /puppet/modules/freight/manifests/user.pp: -------------------------------------------------------------------------------- 1 | define freight::user ( 2 | String $user, 3 | Stdlib::Absolutepath $home, 4 | Stdlib::Absolutepath $webdir, 5 | Stdlib::Absolutepath $stagedir, 6 | String $vhost, 7 | Variant[String, Array[String]] $cron_matches, 8 | Optional[Boolean] $cron_enable = true, #lint:ignore:optional_default 9 | Optional[String[1]] $stable = undef, 10 | ) { 11 | require freight 12 | 13 | stdlib::ensure_packages(['ruby']) 14 | 15 | file { "${home}/freight.conf": 16 | ensure => file, 17 | owner => 'root', 18 | group => $user, 19 | mode => '0644', 20 | content => template('freight/freight.conf.erb'), 21 | } 22 | 23 | # $webdir should be created too, but since we normally override to point at 24 | # the vhost, we'll get a duplicate definition 25 | file { $stagedir: 26 | ensure => directory, 27 | owner => $user, 28 | group => $user, 29 | } 30 | 31 | # Cleanup old stuff 32 | file { "/etc/cron.daily/${user}": 33 | ensure => bool2str($cron_enable, 'file', 'absent'), 34 | mode => '0755', 35 | owner => 'root', 36 | group => 'root', 37 | content => template('freight/cron.erb'), 38 | require => Package['ruby'], 39 | } 40 | 41 | # Website resources 42 | $directory_config = [ 43 | { 44 | path => $webdir, 45 | options => ['Indexes', 'FollowSymLinks'], 46 | }, 47 | { 48 | path => "${webdir}/dists", 49 | expires_active => 'on', 50 | expires_default => 'access plus 2 minutes', 51 | }, 52 | { 53 | path => "${webdir}/dists/*/.refs/", 54 | require => 'all denied', 55 | }, 56 | { 57 | path => "${webdir}/pool", 58 | expires_active => 'on', 59 | expires_default => 'access plus 30 days', 60 | }, 61 | ] 62 | 63 | include apache::mod::alias 64 | include apache::mod::autoindex 65 | include apache::mod::dir 66 | include apache::mod::expires 67 | include apache::mod::mime 68 | 69 | web::vhost { $vhost: 70 | servername => "${vhost}-backend.${facts['networking']['fqdn']}", 71 | docroot => $webdir, 72 | docroot_owner => $user, 73 | docroot_group => $user, 74 | docroot_mode => '0755', 75 | directories => $directory_config, 76 | } 77 | 78 | file { "${webdir}/HEADER.html": 79 | ensure => file, 80 | owner => 'root', 81 | group => 'root', 82 | mode => '0644', 83 | content => epp("${module_name}/${vhost}-HEADER.html.epp", { 'stable' => $stable }), 84 | } 85 | file { "${webdir}/foreman.asc": 86 | ensure => link, 87 | target => 'pubkey.gpg', 88 | owner => 'root', 89 | group => 'root', 90 | } 91 | 92 | if $facts['os']['selinux']['enabled'] { 93 | include selinux 94 | 95 | # Ensure contexts are correct for content copied between webroot and staging area 96 | selinux::fcontext { "fcontext-${user}": 97 | seltype => 'public_content_t', 98 | pathspec => "${stagedir}(/.*)?", 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /puppet/modules/web/manifests/vhost.pp: -------------------------------------------------------------------------------- 1 | # @summary Define a vhost 2 | # 3 | # Defines a vhost on port 80 and a https vhost on port 443 if enabled in the 4 | # web class. 5 | # 6 | # @param servername 7 | # The servername on the vhost 8 | # @param serveraliases 9 | # Alternative names for this vhost 10 | # @param directories 11 | # The directories to set. This maps to apache::vhost's directories parameter 12 | # and can also be used to set locations. 13 | # @param docroot 14 | # The docroot 15 | # @param docroot_owner 16 | # The docroot owner, if any 17 | # @param docroot_group 18 | # The docroot group, if any 19 | # @param docroot_mode 20 | # The docroot mode, if any 21 | # @param ensure 22 | # Whether the vhost should be present or absent 23 | # @param attrs 24 | # Attributes that should be passed to the vhost and https vhost 25 | define web::vhost ( 26 | Enum['present', 'absent'] $ensure = 'present', 27 | Stdlib::Fqdn $servername = "${title}.theforeman.org", 28 | Array[Stdlib::Fqdn] $serveraliases = [], 29 | Optional[Array[Hash]] $directories = undef, 30 | Stdlib::Absolutepath $docroot = "/var/www/vhosts/${title}/htdocs", 31 | Optional[String] $docroot_owner = undef, 32 | Optional[String] $docroot_group = undef, 33 | Optional[Stdlib::Filemode] $docroot_mode = undef, 34 | Hash[String, Any] $attrs = {}, 35 | ) { 36 | require web 37 | 38 | $directory_ensure = $ensure ? { 39 | 'present' => 'directory', 40 | 'absent' => 'absent', 41 | } 42 | 43 | file { dirname($docroot): 44 | ensure => $directory_ensure, 45 | owner => 'root', 46 | group => 'root', 47 | mode => '0755', 48 | } 49 | 50 | apache::vhost { $title: 51 | ensure => $ensure, 52 | servername => $servername, 53 | serveraliases => $serveraliases, 54 | port => 80, 55 | directories => $directories, 56 | docroot => $docroot, 57 | docroot_owner => $docroot_owner, 58 | docroot_group => $docroot_group, 59 | docroot_mode => $docroot_mode, 60 | * => $attrs, 61 | } 62 | 63 | if $web::https { 64 | include web::letsencrypt 65 | 66 | letsencrypt::certonly { $servername: 67 | ensure => $ensure, 68 | plugin => 'webroot', 69 | domains => [$servername] + $serveraliases, 70 | webroot_paths => [$docroot], 71 | } 72 | 73 | apache::vhost { "${title}-https": 74 | ensure => $ensure, 75 | servername => $servername, 76 | serveraliases => $serveraliases, 77 | directories => $directories, 78 | docroot => $docroot, 79 | docroot_owner => $docroot_owner, 80 | docroot_group => $docroot_group, 81 | docroot_mode => $docroot_mode, 82 | port => 443, 83 | ssl => true, 84 | ssl_cert => "${letsencrypt::config_dir}/live/${servername}/cert.pem", 85 | ssl_chain => "${letsencrypt::config_dir}/live/${servername}/chain.pem", 86 | ssl_key => "${letsencrypt::config_dir}/live/${servername}/privkey.pem", 87 | require => Letsencrypt::Certonly[$servername], 88 | * => $attrs, 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/files/pbuilder_F60addforemanrepo: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Add the right theforeman.org repositories 3 | 4 | set -eu 5 | 6 | if [[ -n ${FOREMAN_VERSION:-} ]]; then 7 | cat > /etc/apt/sources.list.d/foreman.list <<-EOF 8 | deb http://deb.theforeman.org/ ${DISTRIBUTION} ${FOREMAN_VERSION} 9 | deb http://deb.theforeman.org/ plugins ${FOREMAN_VERSION} 10 | deb http://stagingdeb.theforeman.org/ ${DISTRIBUTION} theforeman-${FOREMAN_VERSION} 11 | EOF 12 | 13 | # The empty line after BEGIN PGP PUBLIC is needed by apt 14 | cat > /etc/apt/trusted.gpg.d/foreman.asc <<-EOF 15 | -----BEGIN PGP PUBLIC KEY BLOCK----- 16 | 17 | mQINBGD6Y/UBEADOhGOD74lvHdUAew1cHjAUTbopfoXvFvo5jXs4ra3IblnzPEOJ 18 | yOZr/Zt9Guf2w0F/1/4U5ppgqf3YjwKLCpIYM6JV/N2c+zBaQmQEYRFGJNzHRt/i 19 | v4cL67FRF2lxFNldW26/MFDF2ax/LD3qglDLgqgaWEA/2lg6XHBFskeQDToSdx4R 20 | VKP6svCkgNGcxXZMKwHa+/ruYHCu4chfZPGJHLWB351zchTwnw6k7F2ersXvAM4i 21 | YaN4NHv8C2uY2ku8v3uX0H8wC2nXEDBU3uHBun7E/lLABNJpyqAFQrioRAOAn0r7 22 | AYGLN2s4Ke6BMn1uVHarl3MN4kmPqkl7rG0q+bwv9e/D//lKGo6bJRZSeeFMpHiG 23 | kvgUIHoGBOKSbYwn0UBXXt3VHBFp/kaUOKlWYYEs/XHUBZ7fVXlD7UpjMMfNXXGv 24 | QjW/zRjZ5tIMBqMYibxHUExcRRytxSNX9JmVLaE2Hof2lQcEEzmVaSzpWT9/bsUx 25 | DGfwew5gptDDEKH+3z+heJ4b9/vwjIhdLOczkFSFAXamZfkGWjsrgXWpbT7+tNg6 26 | 8LPQtBpo8mgsE7clTZFKH/fF+Ng2Cy1M5zW3kshYuwPGxaQPnDq8HXCIpWA3aRRd 27 | AqJ9aAFCG2t4CpcjMIqfckbxqI77nbZEX82jCIceu2fpruvOjC4PtDsmdQARAQAB 28 | tD5Gb3JlbWFuIEF1dG9tYXRpYyBTaWduaW5nIEtleSAoMjAyMSkgPHBhY2thZ2Vz 29 | QHRoZWZvcmVtYW4ub3JnPokCVAQTAQgAPgIbLwULCQgHAgYVCgkICwIEFgIDAQIe 30 | AQIXgBYhBFt8PlpzW8tNYVgp3AvdqZH9eqyKBQJoegkEBQkLQgwPAAoJEAvdqZH9 31 | eqyKx2AQAMehO6TUNqg+2SONHKfwecb+r1Mmdnu/2Cfianhp16/uzBT64OcvEhzt 32 | w8iuOIRUcc2XZxUAy3+qZLOsMK6V5MGZuWccYmN/NpT5N/XD1MNbIxrOsuGsWoX1 33 | 4IlgmlZH8MpOZdmXozaxOE5rtE1Ra3RxN2K/8KVMFd8qGKJDXUY1SgInbb7X4MPo 34 | TJbM0siVmQ9w5F0VuJB35ZciVN5/fk9cqmeL4cVoVfqBfoFXoTyFHjanmz34aW4x 35 | Ce29uH4SZBn0d84Q+QK4GsReGlPqGEdyPLYNRh9U3XcmK48/E+uwNe0JXAm2loCm 36 | 2kxBhPvHJdLEosX9uzCVYvEAdP7WKEfKCEdPJrQYwq0Iyk/mki/HT9jKPByw0ber 37 | h7/lsnG0OXM7xF/lEkle8oVPTAdo7kAGowuLNbwK15LxdgXRofTfwvNGSeuMMWGH 38 | hfqIj1D3crnSuJUbV5jvbwU4UaMXjGV4ltEctlb9ctQJmWhUQTYku6xA9oFY7rYl 39 | 0dRVegPWg/rbk72fnMXWF3ryMGhRiOKsgvYnq+/TCAOZpbD+1TYkh1bSsgrQ+F95 40 | vD9Xy+CVilYpcXEcgpq8nVxahCKPVZXlVaR/QD0pPi6uqo5ukgBvrZ5B/MuO4FPT 41 | qLj9i/HRc9LdDD4DsTsMRJsc1cQ1oXhKUhLIYdYYmlqdvfSHJaJTiQIzBBABCAAd 42 | FiEExXWpV+gZuhi/B+dmobCbQjM5YegFAmETZtIACgkQobCbQjM5YegnSRAAxG1K 43 | qnJmLnoklOFrc0vLOMSsVg8TT2Fu/ZjVHpyxUCJJEezo0+W2qc5PgRT7cmlGHMj3 44 | CO9MjStCI9i6v+tTX2tDgSsQg9uvjEeAKURmlBoFLDbdJJy1rdBnUvkmYGU9vo9D 45 | xArf9isixG0Xw0JNe0JP6Svi3W69hsqPtt13bTaEK8NNc8fRxQw7RiNcOgQEmqGL 46 | smR0XMs4dIwfBnd1T7pyoqokJh27NA+O/kJAgwN/htP9Kr7WYdmalT/KfPVUCzrg 47 | HptO+LvN7cvF7qjIGh2kqipuUZ7oEqzlIr9AisHulboWyjC7GlMFigPOZrC6JL/n 48 | Gw5/mHsYAb378hVuf/PHu0m6BzJfGxDhYmgWk/R5e8PhJ3Aa9K7PuQZPiVXurJrZ 49 | 3TjR3WgCBKuqtPIYQbj9Afv4odBL2h229MS2O0FNA1NEMw+JWc3WBVMk6APU3tPR 50 | Aeml3uKtV5Tcarz8lG3s9C09dD8fplc76Suegi3nb+lK+7BzGPlUZGsVKTaJqb/f 51 | W6Sfu3QWqfey3h0S8VtVq4MVDzKmGDZICWkJ96N6ETije8xxH26LOPwQH0nlk3YT 52 | qx/+pBM9I6/4ko5QVSxJboQBgBD8OJfA4TPKnbJIbNogVZyEQesHxt7rq+065utJ 53 | HEGvDRcqtLCZDioKyOHFEMxYrrNeeV5Mz/kmjs0= 54 | =zgtx 55 | -----END PGP PUBLIC KEY BLOCK----- 56 | EOF 57 | fi 58 | -------------------------------------------------------------------------------- /puppet/modules/secure_ssh/manifests/receiver_setup.pp: -------------------------------------------------------------------------------- 1 | # @summary Define which deploys the key for a specific user 2 | # 3 | # @param user 4 | # User to own the key 5 | # 6 | # @param script_content 7 | # Content of a script that'll be run by sshd when the user connects with the 8 | # key 9 | # 10 | # @param groups 11 | # The groups the user belongs to 12 | # 13 | # @param homedir 14 | # Home directory the user 15 | # 16 | # @param homedir_mode 17 | # File mode of the user's home directory 18 | # 19 | # @param allowed_ips 20 | # List of allowed ips in the authorized keys file. Unused if $foreman_search 21 | # is provided 22 | # 23 | # @param foreman_search 24 | # String to search in the foreman API, unused by default if specified, uses 25 | # the foreman search puppet function to get IP addresses matching the 26 | # required string. 27 | # 28 | # @param ssh_key_name 29 | # The name of the SSH key 30 | # 31 | # @param ensure 32 | # Whether the SSH receiver setup should be present or absent 33 | # 34 | define secure_ssh::receiver_setup ( 35 | String $user, 36 | String $script_content, 37 | Enum['present', 'absent'] $ensure = 'present', 38 | Array[String] $groups = [], 39 | Stdlib::Absolutepath $homedir = "/home/${user}", 40 | Stdlib::Filemode $homedir_mode = '0700', 41 | Optional[String] $foreman_search = undef, 42 | Array[Stdlib::IP::Address] $allowed_ips = [], 43 | String $ssh_key_name = "${name}_key", 44 | Array[String] $authorized_keys = [], 45 | ) { 46 | $directory_ensure = $ensure ? { 47 | 'present' => 'directory', 48 | 'absent' => 'absent', 49 | } 50 | 51 | # Disable password, we want this to be keys only 52 | user { $user: 53 | ensure => $ensure, 54 | home => $homedir, 55 | managehome => true, 56 | password => '!', 57 | groups => $groups, 58 | } 59 | 60 | # Created above, but this ensures futher chaining is correct 61 | file { $homedir: 62 | ensure => $directory_ensure, 63 | owner => $user, 64 | mode => $homedir_mode, 65 | } 66 | 67 | file { "${homedir}/.ssh": 68 | ensure => $directory_ensure, 69 | owner => $user, 70 | mode => '0700', 71 | } 72 | 73 | # Read the public key from the puppetmaster 74 | $pub_key = ssh::keygen($ssh_key_name, true) 75 | 76 | $api_user = getvar('foreman_api_user') 77 | $api_pass = getvar('foreman_api_password') 78 | if $foreman_search and $api_user and $api_pass { 79 | # Get the IPs of the uploaders from foreman 80 | $ip_data = foreman::foreman('fact_values', $foreman_search, '20', lookup('foreman_url'), $api_user, $api_pass) 81 | } 82 | 83 | file { "${homedir}/.ssh/authorized_keys": 84 | ensure => $ensure, 85 | owner => $user, 86 | mode => '0700', 87 | content => template('secure_ssh/auth_keys.erb'), 88 | } 89 | 90 | # Create validation script for secure connections only 91 | file { "${homedir}/bin": 92 | ensure => $directory_ensure, 93 | owner => $user, 94 | mode => '0700', 95 | } 96 | 97 | file { "${homedir}/bin/secure_${name}": 98 | ensure => $ensure, 99 | owner => $user, 100 | mode => '0700', 101 | content => $script_content, 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /puppet/modules/profiles/manifests/jenkins/controller.pp: -------------------------------------------------------------------------------- 1 | # @summary The profile for a Jenkins controller (formerly master) 2 | # 3 | # @param hostname 4 | # The hostname to use in the Apache vhost 5 | # @param https 6 | # Whether to serve on HTTPS. If so, the HTTP vhost becomes a redirect to HTTPS. 7 | # @param jenkins_job_builder 8 | # Whether to run Jenkins Job Builder 9 | # @param jenkins_job_builder_username 10 | # The username Jenkins Job Builder should use if enabled 11 | # @param jenkins_job_builder_password 12 | # The password Jenkins Job Builder should use if enabled 13 | # @param packages 14 | # The (java) packages to install. OpenJDK Devel is needed for jar unpacking support. 15 | # @param plugins 16 | # The list of plugins. Get the list by going to /script on jenkins and run: 17 | # Jenkins.instance.pluginManager.plugins.toArray().sort { plugin -> plugin.getShortName()}.each { 18 | # plugin -> println (" '${plugin.getShortName()}' => {},") 19 | # } 20 | class profiles::jenkins::controller ( 21 | Stdlib::Fqdn $hostname = 'ci.theforeman.org', 22 | Boolean $https = true, 23 | Boolean $jenkins_job_builder = true, 24 | Optional[String] $jenkins_job_builder_username = undef, 25 | Optional[String] $jenkins_job_builder_password = undef, 26 | Array[String[1]] $packages = ['java-17-openjdk-headless', 'java-17-openjdk-devel', 'fontconfig'], 27 | Array[String[1]] $plugins = [], 28 | ) { 29 | stdlib::ensure_packages($packages) 30 | 31 | package { ['java-11-openjdk', 'java-11-openjdk-headless', 'java-11-openjdk-devel']: 32 | ensure => absent, 33 | } 34 | Package['java-11-openjdk-devel'] -> Package['java-11-openjdk'] -> Package['java-11-openjdk-headless'] 35 | 36 | class { 'jenkins': 37 | install_java => false, 38 | lts => true, 39 | default_plugins => [], 40 | plugin_hash => $plugins.reduce({}) |Hash $memo, String $plugin| { $memo + { $plugin => {} } }, 41 | config_hash => { 42 | 'JENKINS_JAVA_OPTIONS' => { 43 | 'value' => '-Djava.awt.headless=true -Djenkins.install.runSetupWizard=false -Xms2048m -Xmx2048m', 44 | }, 45 | }, 46 | require => Package[$packages], 47 | } 48 | 49 | class { 'web::jenkins': 50 | hostname => $hostname, 51 | https => $https, 52 | } 53 | 54 | include profiles::backup::sender 55 | 56 | $backup_path = $jenkins::localstatedir 57 | 58 | restic::repository { 'jenkins': 59 | backup_cap_dac_read_search => true, 60 | backup_path => $backup_path, 61 | backup_flags => [ 62 | '--exclude', "${backup_path}/jobs/*/workspace*", 63 | '--exclude', "${backup_path}/jobs/*/builds", 64 | '--exclude', "${backup_path}/plugins", 65 | ], 66 | backup_post_cmd => [ 67 | '-/bin/bash -c "/usr/local/bin/restic-prometheus-exporter | sponge /var/lib/prometheus/node-exporter/restic.prom"', 68 | ], 69 | } 70 | 71 | if $jenkins_job_builder { 72 | class { 'jenkins_job_builder': 73 | configs => { 74 | 'theforeman.org' => { 75 | url => $web::jenkins::url, 76 | username => $jenkins_job_builder_username, 77 | password => $jenkins_job_builder_password, 78 | }, 79 | }, 80 | require => [Class['jenkins', 'web::jenkins']], 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /puppet/spec/classes/jenkins_node_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'jenkins_node' do 4 | on_supported_os.each do |os, facts| 5 | context "on #{os}" do 6 | let :facts do 7 | facts 8 | end 9 | 10 | context "without uploader" do 11 | let(:params) do 12 | {uploader: false} 13 | end 14 | it { is_expected.to compile.with_all_deps } 15 | it { is_expected.to contain_users__account('jenkins').with_sudo(false) } 16 | if facts[:os]['family'] == 'Debian' 17 | it { is_expected.to contain_class('sudo') } 18 | it { is_expected.to contain_sudo__conf('sudo-puppet-jenkins').with_content('jenkins ALL=NOPASSWD: ALL') } 19 | else 20 | it { is_expected.not_to contain_class('sudo') } 21 | end 22 | 23 | if facts[:os]['family'] == 'Debian' 24 | it { is_expected.to contain_file('/etc/pbuilder/bookworm64/hooks/A10nozstd').with_ensure('absent') } 25 | it { is_expected.to contain_file('/etc/pbuilder/bookworm64/hooks/C10foremanlog').with_ensure('present') } 26 | it { is_expected.to contain_file('/etc/pbuilder/bookworm64/hooks/D80no-man-db-rebuild').with_ensure('present') } 27 | it { is_expected.to contain_file('/etc/pbuilder/bookworm64/hooks/F60addforemanrepo').with_ensure('present') } 28 | it { is_expected.to contain_file('/etc/pbuilder/bookworm64/hooks/F65-add-backport-repos').with_ensure('absent') } 29 | it { is_expected.to contain_file('/etc/pbuilder/bookworm64/hooks/F66-add-nodesource-nodistro-repos').with_ensure('present') } 30 | it { is_expected.to contain_file('/etc/pbuilder/bookworm64/hooks/F67-add-puppet-repos').with_ensure('present') } 31 | it { is_expected.to contain_file('/etc/pbuilder/bookworm64/hooks/F70aptupdate').with_ensure('present') } 32 | it { is_expected.to contain_file('/etc/pbuilder/bookworm64/hooks/F99printrepos').with_ensure('present') } 33 | it { is_expected.to contain_file('/etc/pbuilder/jammy64/hooks/A10nozstd').with_ensure('present') } 34 | it { is_expected.to contain_file('/etc/pbuilder/jammy64/hooks/F66-add-nodesource-nodistro-repos').with_ensure('present') } 35 | end 36 | end 37 | 38 | context "packaging only node" do 39 | let(:params) do 40 | {uploader: false, packaging: true, unittests: false} 41 | end 42 | it { is_expected.to compile.with_all_deps } 43 | it { is_expected.to contain_class('jenkins_node::packaging') } 44 | it { is_expected.not_to contain_class('jenkins_node::rbenv') } 45 | it { is_expected.to contain_package('jq') } 46 | end 47 | 48 | if facts[:os]['family'] == 'RedHat' 49 | context "unittest only node" do 50 | let(:params) do 51 | {uploader: false, packaging: false, unittests: true} 52 | end 53 | 54 | it { is_expected.to compile.with_all_deps } 55 | it { is_expected.not_to contain_class('jenkins_node::packaging') } 56 | it { is_expected.to contain_class('jenkins_node::postgresql') } 57 | it { is_expected.to contain_class('jenkins_node::rbenv') } 58 | it { is_expected.to contain_exec('rbenv-install-2.7.6').with_user('jenkins') } 59 | end 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /puppet/modules/web/templates/rpm/HEADER.html.epp: -------------------------------------------------------------------------------- 1 | <%- | 2 | String $stable_foreman, 3 | Stdlib::Fqdn $servername, 4 | | -%> 5 |

<%= $servername %>

6 | 7 |

Foreman

8 | 9 |

Foreman is available under /releases/VERSION/DIST/ARCH, e.g.

10 | 11 |
    12 |
  • /foreman/<%= $stable_foreman %>/foreman/el9/x86_64
  • 13 |
  • /foreman/nightly/foreman/el9/x86_64
  • 14 |
15 | 16 |

foreman-release RPMs containing an appropriate .repo file are available with fixed URLs:

17 | 18 | 22 | 23 |

Release packages are signed with a new key for each major release. The public key is available in the RPM-GPG-KEY-foreman file within each version directory or the foreman-release RPMs.

24 | 25 |

Nightly builds of Foreman are available under /foreman/nightly/foreman/DIST/ARCH and are refreshed a few times a day, but are not GPG signed.

26 | 27 |

Plugins

28 | 29 |

A number of Foreman plugins are available in the plugin repos, see List of Plugins for more information.

30 | 31 |

Plugin repos are structured by the Foreman version that they're compatible with in the format /foreman/VERSION/plugins/DIST/ARCH, e.g.

32 | 33 |
    34 |
  • /foreman/<%= $stable_foreman %>/plugins/el9/x86_64
  • 35 |
  • /foreman/nightly/plugins/el9/x86_64
  • 36 |
37 | 38 |

Plugin repos are not GPG signed.

39 | 40 |

Katello

41 | 42 |

Katello is available under /foreman/VERSION/katello/DIST/ARCH with Candlepin under /candlepin/CANDLEPIN_VERSION/DIST/ARCH. 43 | 44 |

katello-repos RPMs containing an appropriate .repo file are available:

45 | 46 | 50 | 51 |

Accessing this repo

52 | 53 | This repository is available over HTTP and HTTPS: 54 | 55 |
    56 |
  • http://<%= $servername %>
  • 57 |
  • https://<%= $servername %>
  • 58 |
59 | 60 |

Support

61 | 62 |

You can find the installation instructions here, but we strongly recommend using our Installer (which uses RPMs). 63 | 64 |

If you have any issues, you can find ways to reach us on our Support page.

65 | -------------------------------------------------------------------------------- /puppet/modules/jenkins_node/files/pbuilder_F67-add-puppet-repos: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "deb http://apt.puppet.com ${DISTRIBUTION} puppet8" >> /etc/apt/sources.list 4 | 5 | cat > /etc/apt/trusted.gpg.d/puppet.asc < 22 | gpg> expire 23 | Changing expiration time for the primary key. 24 | Please specify how long the key should be valid. 25 | 0 = key does not expire 26 | = key expires in n days 27 | w = key expires in n weeks 28 | m = key expires in n months 29 | y = key expires in n years 30 | Key is valid for? (0) 2y 31 | Key expires at Sun 20 Aug 2023 06:13:21 AM UTC 32 | Is this correct? (y/N) y 33 | gpg> save 34 | gpg> quit 35 | ``` 36 | 37 | You need to repeat that for every `freight` account (`freight{,stage,archive}@repo-deb01`). 38 | 39 | ## Distributing keys 40 | 41 | ### Release based keys 42 | 43 | RPM users are told in install & upgrade documentation to install foreman-release from the new release, which can contain the keys for that release, making distribution easy. 44 | 45 | ### Time based keys 46 | 47 | Debian archives can be signed with multiple keys (by setting those in `freight.conf`), but key distribution to users is manual right now. 48 | 49 | To make our infrastructure aware of the new keys: 50 | 51 | * Export private key to `freight{,stage,archive}@repo-deb01`: 52 | * Remove the passphrase: `gpg --homedir "releases/foreman-debian/2021/gnupg/" --edit-key KEY_ID` - enter `passwd`, this will prompt for the current passphrase, enter it, then, when asked for a new one, enter nothing. 53 | * Export the secret key: `gpg --homedir "releases/foreman-debian/2021/gnupg/" --export-secret-keys --armor > /tmp/debian-new.key` 54 | * Copy `/tmp/debian-new.key` to `repo-deb01` 55 | * Import the secret key with `gpg --import /tmp/debian-new.key` for each of the freight users: `freight`, `freightarchive`, `freightstage` 56 | * Configure it in `puppet/modules/freight/templates/freight.conf.erb`, examples: 57 | * [7680053](https://github.com/theforeman/foreman-infra/commit/7680053) - Add 2016 archive key, thus using two keys for a period of time 58 | * [9f50f62](https://github.com/theforeman/foreman-infra/commit/9f50f62) - Remove 2014 archive signing GPG key 59 | * Configure it in `puppet/modules/jenkins_node/files/pbuilder_F60addforemanrepo`, example: 60 | * [596ece6](https://github.com/theforeman/foreman-infra/commit/596ece6) - add new (2021) key to pbuilder 61 | 62 | To make our users aware of the new keys: 63 | 64 | * Freight exports the keyring to https://deb.theforeman.org/foreman.asc, so everyone who is regularly syncing that file, is automatically OK. 65 | * Announce new key on discourse, so that people who don't fetch the key regularly, know. 66 | -------------------------------------------------------------------------------- /puppet/modules/web/manifests/vhost/rpm.pp: -------------------------------------------------------------------------------- 1 | # @summary Set up the rpm vhost 2 | # @api private 3 | class web::vhost::rpm ( 4 | String[1] $stable_foreman, 5 | Stdlib::Fqdn $servername = 'rpm.theforeman.org', 6 | Stdlib::Absolutepath $rpm_directory = '/var/www/vhosts/rpm/htdocs', 7 | Stdlib::Absolutepath $rpm_staging_directory = '/var/www/vhosts/stagingrpm/htdocs/', 8 | String $user = 'rpmrepo', 9 | ) { 10 | include fastly_purge 11 | 12 | $rpm_directory_config = [ 13 | { 14 | path => $rpm_directory, 15 | options => ['+Indexes', '+FollowSymLinks'], 16 | expires_active => 'on', 17 | expires_default => 'access plus 2 minutes', 18 | }, 19 | { 20 | path => '.+\.(bz2|gz|rpm|xz)$', 21 | provider => 'filesmatch', 22 | expires_active => 'on', 23 | expires_default => 'access plus 30 days', 24 | }, 25 | { 26 | path => 'repomd.xml', 27 | provider => 'files', 28 | expires_active => 'on', 29 | expires_default => 'access plus 2 minutes', 30 | }, 31 | ] 32 | 33 | $deploy_rpmrepo_context = { 34 | 'servername' => $servername, 35 | 'rpm_directory' => $rpm_directory, 36 | 'rpm_staging_directory' => $rpm_staging_directory, 37 | } 38 | 39 | secure_ssh::receiver_setup { $user: 40 | user => $user, 41 | foreman_search => 'host ~ node*.jenkins.osuosl.theforeman.org and (name = external_ip4 or name = external_ip6)', 42 | script_content => epp('web/deploy-rpmrepo.sh.epp', $deploy_rpmrepo_context), 43 | } 44 | 45 | include apache::mod::alias 46 | include apache::mod::autoindex 47 | include apache::mod::dir 48 | include apache::mod::expires 49 | include apache::mod::mime 50 | 51 | web::vhost { 'rpm': 52 | servername => "rpm-backend.${facts['networking']['fqdn']}", 53 | docroot => $rpm_directory, 54 | docroot_owner => $user, 55 | docroot_group => $user, 56 | docroot_mode => '0755', 57 | directories => $rpm_directory_config, 58 | } 59 | 60 | if $facts['os']['family'] == 'RedHat' { 61 | stdlib::ensure_packages(['createrepo_c']) 62 | } 63 | 64 | file { "${rpm_directory}/robots.txt": 65 | ensure => file, 66 | owner => $user, 67 | group => $user, 68 | mode => '0644', 69 | content => file('web/rpm/robots.txt'), 70 | } 71 | 72 | file { "${rpm_directory}/HEADER.html": 73 | ensure => file, 74 | owner => $user, 75 | group => $user, 76 | mode => '0644', 77 | content => epp("${module_name}/rpm/HEADER.html.epp", { 78 | 'stable_foreman' => $stable_foreman, 79 | 'servername' => $servername, 80 | }), 81 | } 82 | 83 | ['candlepin', 'foreman', 'pulpcore'].each |$directory| { 84 | file { ["${rpm_directory}/${directory}"]: 85 | ensure => directory, 86 | owner => $user, 87 | group => $user, 88 | mode => '0755', 89 | } 90 | 91 | exec { "fastly-purge-${directory}-latest": 92 | command => "fastly-purge-find 'https://${servername}' ${rpm_directory} ${directory}/latest/", 93 | path => '/bin:/usr/bin:/usr/local/bin', 94 | require => File['/usr/local/bin/fastly-purge-find'], 95 | refreshonly => true, 96 | } 97 | } 98 | 99 | file { "${rpm_directory}/pulpcore/HEADER.html": 100 | ensure => file, 101 | owner => $user, 102 | group => $user, 103 | mode => '0644', 104 | content => file('web/rpm/pulpcore-HEADER.html'), 105 | } 106 | } 107 | --------------------------------------------------------------------------------