From 120f9abaa15ce0feec1dc457ad3dc3787e4fbfc6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 3 Nov 2015 21:28:07 +0100 Subject: [PATCH 001/183] Add GitLab Pages - The pages are created when build artifacts for `pages` job are uploaded - Pages serve the content under: http://group.pages.domain.com/project - Pages can be used to serve the group page, special project named as host: group.pages.domain.com - User can provide own 403 and 404 error pages by creating 403.html and 404.html in group page project - Pages can be explicitly removed from the project by clicking Remove Pages in Project Settings - The size of pages is limited by Application Setting: max pages size, which limits the maximum size of unpacked archive (default: 100MB) - The public/ is extracted from artifacts and content is served as static pages - Pages asynchronous worker use `dd` to limit the unpacked tar size - Pages needs to be explicitly enabled and domain needs to be specified in gitlab.yml - Pages are part of backups - Pages notify the deployment status using Commit Status API - Pages use a new sidekiq queue: pages - Pages use a separate nginx config which needs to be explicitly added --- .../admin/application_settings_controller.rb | 1 + app/controllers/projects_controller.rb | 10 ++ app/models/ci/build.rb | 3 +- app/models/project.rb | 25 ++++ app/policies/project_policy.rb | 1 + app/services/update_pages_service.rb | 15 +++ .../application_settings/_form.html.haml | 8 ++ app/views/projects/edit.html.haml | 35 +++++ app/workers/pages_worker.rb | 123 ++++++++++++++++++ config/gitlab.yml.example | 11 ++ config/initializers/1_settings.rb | 6 + config/routes/project.rb | 1 + ..._add_pages_size_to_application_settings.rb | 5 + db/schema.rb | 1 + doc/README.md | 1 + doc/install/installation.md | 13 ++ doc/pages/README.md | 12 ++ lib/backup/pages.rb | 13 ++ lib/support/nginx/gitlab-pages | 27 ++++ lib/tasks/gitlab/backup.rake | 21 +++ shared/pages/.gitkeep | 0 spec/fixtures/pages.tar.gz | Bin 0 -> 1795 bytes spec/fixtures/pages_empty.tar.gz | Bin 0 -> 128 bytes spec/services/update_pages_service_spec.rb | 43 ++++++ spec/tasks/gitlab/backup_rake_spec.rb | 14 +- spec/workers/pages_worker_spec.rb | 58 +++++++++ 26 files changed, 441 insertions(+), 6 deletions(-) create mode 100644 app/services/update_pages_service.rb create mode 100644 app/workers/pages_worker.rb create mode 100644 db/migrate/20151215132013_add_pages_size_to_application_settings.rb create mode 100644 doc/pages/README.md create mode 100644 lib/backup/pages.rb create mode 100644 lib/support/nginx/gitlab-pages create mode 100644 shared/pages/.gitkeep create mode 100644 spec/fixtures/pages.tar.gz create mode 100644 spec/fixtures/pages_empty.tar.gz create mode 100644 spec/services/update_pages_service_spec.rb create mode 100644 spec/workers/pages_worker_spec.rb diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 543d5eac504..df8682e246e 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -109,6 +109,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :plantuml_url, :max_artifacts_size, :max_attachment_size, + :max_pages_size, :metrics_enabled, :metrics_host, :metrics_method_call_threshold, diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 444ff837bb3..123dc179e73 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -151,6 +151,16 @@ class ProjectsController < Projects::ApplicationController end end + def remove_pages + return access_denied! unless can?(current_user, :remove_pages, @project) + + @project.remove_pages + + respond_to do |format| + format.html { redirect_to project_path(@project) } + end + end + def housekeeping ::Projects::HousekeepingService.new(@project).execute diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5fe8ddf69d7..095a346f337 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -256,7 +256,7 @@ module Ci end def project_id - pipeline.project_id + gl_project_id end def project_name @@ -457,6 +457,7 @@ module Ci build_data = Gitlab::DataBuilder::Build.build(self) project.execute_hooks(build_data.dup, :build_hooks) project.execute_services(build_data.dup, :build_hooks) + UpdatePagesService.new(build_data).execute project.running_or_pending_build_count(force: true) end diff --git a/app/models/project.rb b/app/models/project.rb index 37f4705adbd..48ff5ec7fc7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -53,6 +53,8 @@ class Project < ActiveRecord::Base update_column(:last_activity_at, self.created_at) end + after_destroy :remove_pages + # update visibility_level of forks after_update :update_forks_visibility_level def update_forks_visibility_level @@ -1160,6 +1162,29 @@ class Project < ActiveRecord::Base ensure_runners_token! end + def pages_url + if Dir.exist?(public_pages_path) + host = "#{namespace.path}.#{Settings.pages.domain}" + + # If the project path is the same as host, leave the short version + return "http://#{host}" if host == path + + "http://#{host}/#{path}" + end + end + + def pages_path + File.join(Settings.pages.path, path_with_namespace) + end + + def public_pages_path + File.join(pages_path, 'public') + end + + def remove_pages + FileUtils.rm_r(pages_path, force: true) + end + def wiki @wiki ||= ProjectWiki.new(self, self.owner) end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 71ef8901932..63bc639688d 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -136,6 +136,7 @@ class ProjectPolicy < BasePolicy can! :remove_fork_project can! :destroy_merge_request can! :destroy_issue + can! :remove_pages end def team_member_owner_access! diff --git a/app/services/update_pages_service.rb b/app/services/update_pages_service.rb new file mode 100644 index 00000000000..818bb94a293 --- /dev/null +++ b/app/services/update_pages_service.rb @@ -0,0 +1,15 @@ +class UpdatePagesService + attr_reader :data + + def initialize(data) + @data = data + end + + def execute + return unless Settings.pages.enabled + return unless data[:build_name] == 'pages' + return unless data[:build_status] == 'success' + + PagesWorker.perform_async(data[:build_id]) + end +end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 558bbe07b16..125a805a897 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -186,6 +186,14 @@ = f.text_area :help_page_text, class: 'form-control', rows: 4 .help-block Markdown enabled + %fieldset + %legend Pages + .form-group + = f.label :max_pages_size, 'Maximum size of pages (MB)', class: 'control-label col-sm-2' + .col-sm-10 + = f.number_field :max_pages_size, class: 'form-control' + .help-block Zero for unlimited + %fieldset %legend Continuous Integration .form-group diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index ec944d4ffb7..89e2d4046b8 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -133,6 +133,41 @@ %hr = link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" = f.submit 'Save changes', class: "btn btn-save" + + - if Settings.pages.enabled + .pages-settings + .panel.panel-default + .panel-heading Pages + .errors-holder + .panel-body + - if @project.pages_url + %strong + Congratulations. Your pages are served at: + %p= link_to @project.pages_url, @project.pages_url + - else + %p + To publish pages create .gitlab-ci.yml with + %strong pages job + and send public/ folder to GitLab. + %p + Use existing tools: + %ul + %li + %pre + :plain + pages: + image: jekyll + script: jekyll build + artifacts: + paths: + - public + + - if @project.pages_url && can?(current_user, :remove_pages, @project) + .form-actions + = link_to 'Remove pages', remove_pages_namespace_project_path(@project.namespace, @project), + data: { confirm: "Are you sure that you want to remove pages for this project?" }, + method: :post, class: "btn btn-warning" + .row.prepend-top-default %hr .row.prepend-top-default diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb new file mode 100644 index 00000000000..9aa3030264b --- /dev/null +++ b/app/workers/pages_worker.rb @@ -0,0 +1,123 @@ +class PagesWorker + include Sidekiq::Worker + include Gitlab::CurrentSettings + + BLOCK_SIZE = 32.kilobytes + MAX_SIZE = 1.terabyte + + sidekiq_options queue: :pages + + def perform(build_id) + @build_id = build_id + return unless valid? + + # Create status notifying the deployment of pages + @status = GenericCommitStatus.new( + project: project, + commit: build.commit, + user: build.user, + ref: build.ref, + stage: 'deploy', + name: 'pages:deploy' + ) + @status.run! + + FileUtils.mkdir_p(tmp_path) + + # Calculate dd parameters: we limit the size of pages + max_size = current_application_settings.max_pages_size.megabytes + max_size ||= MAX_SIZE + blocks = 1 + max_size / BLOCK_SIZE + + # Create temporary directory in which we will extract the artifacts + Dir.mktmpdir(nil, tmp_path) do |temp_path| + # We manually extract the archive and limit the archive size with dd + results = Open3.pipeline(%W(gunzip -c #{artifacts}), + %W(dd bs=#{BLOCK_SIZE} count=#{blocks}), + %W(tar -x -C #{temp_path} public/)) + return unless results.compact.all?(&:success?) + + # Check if we did extract public directory + temp_public_path = File.join(temp_path, 'public') + return unless Dir.exists?(temp_public_path) + + FileUtils.mkdir_p(pages_path) + + # Lock file for time of deployment to prevent the two processes from doing the concurrent deployment + File.open(lock_path, File::RDWR|File::CREAT, 0644) do |f| + f.flock(File::LOCK_EX) + return unless valid? + + # Do atomic move of pages + # Move and removal may not be atomic, but they are significantly faster then extracting and removal + # 1. We move deployed public to previous public path (file removal is slow) + # 2. We move temporary public to be deployed public + # 3. We remove previous public path + if File.exists?(public_path) + FileUtils.move(public_path, previous_public_path) + end + FileUtils.move(temp_public_path, public_path) + end + + if File.exists?(previous_public_path) + FileUtils.rm_r(previous_public_path, force: true) + end + + @status.success + end + ensure + @status.drop if @status && @status.active? + end + + private + + def valid? + # check if sha for the ref is still the most recent one + # this helps in case when multiple deployments happens + build && build.artifacts_file? && sha == latest_sha + end + + def build + @build ||= Ci::Build.find_by(id: @build_id) + end + + def project + @project ||= build.project + end + + def tmp_path + @tmp_path ||= File.join(Settings.pages.path, 'tmp') + end + + def pages_path + @pages_path ||= project.pages_path + end + + def public_path + @public_path ||= File.join(pages_path, 'public') + end + + def previous_public_path + @previous_public_path ||= File.join(pages_path, "public.#{SecureRandom.hex}") + end + + def lock_path + @lock_path ||= File.join(pages_path, 'deploy.lock') + end + + def ref + build.ref + end + + def artifacts + build.artifacts_file.path + end + + def latest_sha + project.commit(build.ref).try(:sha).to_s + end + + def sha + build.sha + end +end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 42e5f105d46..d41280624ae 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -153,6 +153,17 @@ production: &base # The location where LFS objects are stored (default: shared/lfs-objects). # storage_path: shared/lfs-objects + ## GitLab Pages + pages: + enabled: false + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + # The domain under which the pages are served: + # http://group.example.com/project + # or project path can be a group page: group.example.com + domain: example.com + ## Mattermost ## For enabling Add to Mattermost button mattermost: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 4f33aad8693..0c06b73ba36 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -254,6 +254,12 @@ Settings.registry['issuer'] ||= nil Settings.registry['host_port'] ||= [Settings.registry['host'], Settings.registry['port']].compact.join(':') Settings.registry['path'] = File.expand_path(Settings.registry['path'] || File.join(Settings.shared['path'], 'registry'), Rails.root) +# Pages +Settings['pages'] ||= Settingslogic.new({}) +Settings.pages['enabled'] = false if Settings.pages['enabled'].nil? +Settings.pages['path'] = File.expand_path('shared/pages/', Rails.root) +Settings.pages['domain'] ||= "example.com" + # # Git LFS # diff --git a/config/routes/project.rb b/config/routes/project.rb index f36febc6e04..cd56f6281f5 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -329,6 +329,7 @@ constraints(ProjectUrlConstrainer.new) do post :archive post :unarchive post :housekeeping + post :remove_pages post :toggle_star post :preview_markdown post :export diff --git a/db/migrate/20151215132013_add_pages_size_to_application_settings.rb b/db/migrate/20151215132013_add_pages_size_to_application_settings.rb new file mode 100644 index 00000000000..e7fb73190e8 --- /dev/null +++ b/db/migrate/20151215132013_add_pages_size_to_application_settings.rb @@ -0,0 +1,5 @@ +class AddPagesSizeToApplicationSettings < ActiveRecord::Migration + def up + add_column :application_settings, :max_pages_size, :integer, default: 100, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 5efb4f6595c..15f378b28ff 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -61,6 +61,7 @@ ActiveRecord::Schema.define(version: 20170130204620) do t.boolean "shared_runners_enabled", default: true, null: false t.integer "max_artifacts_size", default: 100, null: false t.string "runners_registration_token" + t.integer "max_pages_size", default: 100, null: false t.boolean "require_two_factor_authentication", default: false t.integer "two_factor_grace_period", default: 48 t.boolean "metrics_enabled", default: false diff --git a/doc/README.md b/doc/README.md index 909740211a6..f036febb7b9 100644 --- a/doc/README.md +++ b/doc/README.md @@ -12,6 +12,7 @@ - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. - [Container Registry](user/project/container_registry.md) Learn how to use GitLab Container Registry. - [GitLab basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. +- [GitLab Pages](pages/README.md) Using GitLab Pages. - [Importing to GitLab](workflow/importing/README.md) Import your projects from GitHub, Bitbucket, GitLab.com, FogBugz and SVN into GitLab. - [Importing and exporting projects between instances](user/project/settings/import_export.md). - [Markdown](user/markdown.md) GitLab's advanced formatting system. diff --git a/doc/install/installation.md b/doc/install/installation.md index 425c5d93efb..c78e469055d 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -313,6 +313,9 @@ sudo usermod -aG redis git # Change the permissions of the directory where CI artifacts are stored sudo chmod -R u+rwX shared/artifacts/ + # Change the permissions of the directory where CI artifacts are stored + sudo chmod -R ug+rwX shared/pages/ + # Copy the example Unicorn config sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb @@ -484,6 +487,16 @@ Make sure to edit the config file to match your setup. Also, ensure that you mat # or else sudo rm -f /etc/nginx/sites-enabled/default sudo editor /etc/nginx/sites-available/gitlab +Copy the GitLab pages site config: + + sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages + sudo ln -s /etc/nginx/sites-available/gitlab-pages /etc/nginx/sites-enabled/gitlab-pages + + # Change YOUR_GITLAB_PAGES\.DOMAIN to the fully-qualified + # domain name under which the pages will be served. + # The . (dot) replace with \. (backslash+dot) + sudo editor /etc/nginx/sites-available/gitlab-pages + **Note:** If you want to use HTTPS, replace the `gitlab` Nginx config with `gitlab-ssl`. See [Using HTTPS](#using-https) for HTTPS configuration details. ### Test Configuration diff --git a/doc/pages/README.md b/doc/pages/README.md new file mode 100644 index 00000000000..d08c34c3ebc --- /dev/null +++ b/doc/pages/README.md @@ -0,0 +1,12 @@ +# GitLab Pages + +To start using GitLab Pages add to your project .gitlab-ci.yml with special pages job. + + pages: + image: jekyll + script: jekyll build + artifacts: + paths: + - public + +TODO diff --git a/lib/backup/pages.rb b/lib/backup/pages.rb new file mode 100644 index 00000000000..215ded93bfe --- /dev/null +++ b/lib/backup/pages.rb @@ -0,0 +1,13 @@ +require 'backup/files' + +module Backup + class Pages < Files + def initialize + super('pages', Gitlab.config.pages.path) + end + + def create_files_dir + Dir.mkdir(app_files_dir, 0700) + end + end +end diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages new file mode 100644 index 00000000000..0eeb0cd1917 --- /dev/null +++ b/lib/support/nginx/gitlab-pages @@ -0,0 +1,27 @@ +## Pages serving host +server { + listen 0.0.0.0:80; + listen [::]:80 ipv6only=on; + + ## Replace this with something like pages.gitlab.com + server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + root /home/git/gitlab/shared/pages/${group}; + + ## Individual nginx logs for GitLab pages + access_log /var/log/nginx/gitlab_pages_access.log; + error_log /var/log/nginx/gitlab_pages_error.log; + + # 1. Try to get /project/ to => shared/pages/${group}/public/ or index.html + # 2. Try to get / to => shared/pages/${group}/${host}/public/ or index.html + location ~ ^/([^/]*)(/.*)?$ { + try_files "/$1/public$2" + "/$1/public$2/index.html" + "/${host}/public/${uri}" + "/${host}/public/${uri}/index.html" + =404; + } + + # Define custom error pages + error_page 403 /403.html; + error_page 404 /404.html; +} diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index a9f1255e8cf..ffab6f492fb 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -13,6 +13,7 @@ namespace :gitlab do Rake::Task["gitlab:backup:uploads:create"].invoke Rake::Task["gitlab:backup:builds:create"].invoke Rake::Task["gitlab:backup:artifacts:create"].invoke + Rake::Task["gitlab:backup:pages:create"].invoke Rake::Task["gitlab:backup:lfs:create"].invoke Rake::Task["gitlab:backup:registry:create"].invoke @@ -56,6 +57,7 @@ namespace :gitlab do Rake::Task['gitlab:backup:uploads:restore'].invoke unless backup.skipped?('uploads') Rake::Task['gitlab:backup:builds:restore'].invoke unless backup.skipped?('builds') Rake::Task['gitlab:backup:artifacts:restore'].invoke unless backup.skipped?('artifacts') + Rake::Task["gitlab:backup:pages:restore"].invoke unless backup.skipped?("pages") Rake::Task['gitlab:backup:lfs:restore'].invoke unless backup.skipped?('lfs') Rake::Task['gitlab:backup:registry:restore'].invoke unless backup.skipped?('registry') Rake::Task['gitlab:shell:setup'].invoke @@ -159,6 +161,25 @@ namespace :gitlab do end end + namespace :pages do + task create: :environment do + $progress.puts "Dumping pages ... ".blue + + if ENV["SKIP"] && ENV["SKIP"].include?("pages") + $progress.puts "[SKIPPED]".cyan + else + Backup::Pages.new.dump + $progress.puts "done".green + end + end + + task restore: :environment do + $progress.puts "Restoring pages ... ".blue + Backup::Pages.new.restore + $progress.puts "done".green + end + end + namespace :lfs do task create: :environment do $progress.puts "Dumping lfs objects ... ".color(:blue) diff --git a/shared/pages/.gitkeep b/shared/pages/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/spec/fixtures/pages.tar.gz b/spec/fixtures/pages.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..d0e89378b3e20545a343b709556dd93f5093dff4 GIT binary patch literal 1795 zcmb2|=3uxUToA^<{PwDMwz9hb!vovr;a~U-x>8PGQe3{_q;89Y!uM}0a?Q^;Tk}tT za((%ij&kQ4duOUX`E4L2akV5obo#O5PZzE|yZ!yWOI3UC-M_#5bLn>b`0LwmpZ=|a0c`MTO>+ePxb-+tTteE-e7Jo|b7)8wD;zx#gM`%Axgi+)c3|MTa6lb)sC zUoCWFt$5xVEL(X_vpjX5=h+MXOJ|YNMa;+xbD6ulIQE*Soy&OS_e?s_gAM zQw+~%${qXR{dDCKDc{W{!S90eUbm~3um1A+lKINA<3@5>EG3naucs7QUAeP#)0LId zd@EyTRn821*}hQUw`ygvo}lG|ru z+YbV*bNB7LQhxH!o|_*n`%~qLt*-x%_+63WX$6veo$TmTV?Tep(jQnkROVaXK=iGg# z10&;q)tve!RG52c>Eztns9Sry?9VNq_uS-ZvTm)4z0dl&wJY^gd%70QyYVCBNpj@8 zEIZjMOP9yWmzPb8*gvOF>uBIHzrf0WB}ri~rx$iSo5$U^&Sv>@iRl}AOx0GOH7t8B zIAd82-_D&qo_DHup1H36_I%*uQkVYZx35;d>d%!uwk&S>vni)k_nvrX;o~joZSs5J zwu!fYr=EGb`}(Q)`TMsdd_8;ppK6`#_3!`Z8#3l^J3js1|J%2B*Z=u{|NgzBzyIfL hkFWmE%rFW@Ltr!nMniz&5U6>j?`lx=h#^9d0RSz&g9rcs literal 0 HcmV?d00001 diff --git a/spec/fixtures/pages_empty.tar.gz b/spec/fixtures/pages_empty.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..5c2afa1a8f6893ffeeed860bcb8ef98a48e87869 GIT binary patch literal 128 zcmb2|=3wadEC^#@etW^1?~s82>j9ag<_C^Uv*Z_=rlF+N%CgS?(DDW)(U)nnmQUSQ zv3q&d?bv1Ovdu)2b(XfMrtkkL^)1)a@>xt0XQ3pYuSfK>Uo+M{E}XHNr`CSm+q>tt fuB&}n=~g9wwe9~j76vG2$mdU+D!PF|gMk46Xy!C% literal 0 HcmV?d00001 diff --git a/spec/services/update_pages_service_spec.rb b/spec/services/update_pages_service_spec.rb new file mode 100644 index 00000000000..ed392cd94ee --- /dev/null +++ b/spec/services/update_pages_service_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe UpdatePagesService, services: true do + let(:build) { create(:ci_build) } + let(:data) { Gitlab::BuildDataBuilder.build(build) } + let(:service) { UpdatePagesService.new(data) } + + context 'execute asynchronously for pages job' do + before { build.name = 'pages' } + + context 'on success' do + before { build.success } + + it 'should execute worker' do + expect(PagesWorker).to receive(:perform_async) + service.execute + end + end + + %w(pending running failed canceled).each do |status| + context "on #{status}" do + before { build.status = status } + + it 'should not execute worker' do + expect(PagesWorker).to_not receive(:perform_async) + service.execute + end + end + end + end + + context 'for other jobs' do + before do + build.name = 'other job' + build.success + end + + it 'should not execute worker' do + expect(PagesWorker).to_not receive(:perform_async) + service.execute + end + end +end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index bc751d20ce1..df8a47893f9 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -28,7 +28,7 @@ describe 'gitlab:app namespace rake task' do end def reenable_backup_sub_tasks - %w{db repo uploads builds artifacts lfs registry}.each do |subtask| + %w{db repo uploads builds artifacts pages lfs registry}.each do |subtask| Rake::Task["gitlab:backup:#{subtask}:create"].reenable end end @@ -71,6 +71,7 @@ describe 'gitlab:app namespace rake task' do expect(Rake::Task['gitlab:backup:builds:restore']).to receive(:invoke) expect(Rake::Task['gitlab:backup:uploads:restore']).to receive(:invoke) expect(Rake::Task['gitlab:backup:artifacts:restore']).to receive(:invoke) + expect(Rake::Task['gitlab:backup:pages:restore']).to receive(:invoke) expect(Rake::Task['gitlab:backup:lfs:restore']).to receive(:invoke) expect(Rake::Task['gitlab:backup:registry:restore']).to receive(:invoke) expect(Rake::Task['gitlab:shell:setup']).to receive(:invoke) @@ -202,7 +203,7 @@ describe 'gitlab:app namespace rake task' do it 'sets correct permissions on the tar contents' do tar_contents, exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz registry.tar.gz} + %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz registry.tar.gz} ) expect(exit_status).to eq(0) expect(tar_contents).to match('db/') @@ -210,14 +211,15 @@ describe 'gitlab:app namespace rake task' do expect(tar_contents).to match('repositories/') expect(tar_contents).to match('builds.tar.gz') expect(tar_contents).to match('artifacts.tar.gz') + expect(tar_contents).to match('pages.tar.gz') expect(tar_contents).to match('lfs.tar.gz') expect(tar_contents).to match('registry.tar.gz') - expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.tar.gz|registry.tar.gz)\/$/) + expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|pages.tar.gz|artifacts.tar.gz|registry.tar.gz)\/$/) end it 'deletes temp directories' do temp_dirs = Dir.glob( - File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,lfs,registry}') + File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,pages,lfs,registry}') ) expect(temp_dirs).to be_empty @@ -304,7 +306,7 @@ describe 'gitlab:app namespace rake task' do it "does not contain skipped item" do tar_contents, _exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz registry.tar.gz} + %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz registry.tar.gz} ) expect(tar_contents).to match('db/') @@ -312,6 +314,7 @@ describe 'gitlab:app namespace rake task' do expect(tar_contents).to match('builds.tar.gz') expect(tar_contents).to match('artifacts.tar.gz') expect(tar_contents).to match('lfs.tar.gz') + expect(tar_contents).to match('pages.tar.gz') expect(tar_contents).to match('registry.tar.gz') expect(tar_contents).not_to match('repositories/') end @@ -327,6 +330,7 @@ describe 'gitlab:app namespace rake task' do expect(Rake::Task['gitlab:backup:uploads:restore']).not_to receive :invoke expect(Rake::Task['gitlab:backup:builds:restore']).to receive :invoke expect(Rake::Task['gitlab:backup:artifacts:restore']).to receive :invoke + expect(Rake::Task['gitlab:backup:pages:restore']).to receive :invoke expect(Rake::Task['gitlab:backup:lfs:restore']).to receive :invoke expect(Rake::Task['gitlab:backup:registry:restore']).to receive :invoke expect(Rake::Task['gitlab:shell:setup']).to receive :invoke diff --git a/spec/workers/pages_worker_spec.rb b/spec/workers/pages_worker_spec.rb new file mode 100644 index 00000000000..158a4b3ba8d --- /dev/null +++ b/spec/workers/pages_worker_spec.rb @@ -0,0 +1,58 @@ +require "spec_helper" + +describe PagesWorker do + let(:project) { create :project } + let(:commit) { create :ci_commit, project: project, sha: project.commit('HEAD').sha } + let(:build) { create :ci_build, commit: commit, ref: 'HEAD' } + let(:worker) { PagesWorker.new } + let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/pages.tar.gz', 'application/octet-stream') } + let(:empty_file) { fixture_file_upload(Rails.root + 'spec/fixtures/pages_empty.tar.gz', 'application/octet-stream') } + let(:invalid_file) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'application/octet-stream') } + + before do + project.remove_pages + end + + context 'for valid file' do + before { build.update_attributes(artifacts_file: file) } + + it 'succeeds' do + expect(project.pages_url).to be_nil + expect(worker.perform(build.id)).to be_truthy + expect(project.pages_url).to_not be_nil + end + + it 'limits pages size' do + stub_application_setting(max_pages_size: 1) + expect(worker.perform(build.id)).to_not be_truthy + end + + it 'removes pages after destroy' do + expect(project.pages_url).to be_nil + expect(worker.perform(build.id)).to be_truthy + expect(project.pages_url).to_not be_nil + project.destroy + expect(Dir.exist?(project.public_pages_path)).to be_falsey + end + end + + it 'fails if no artifacts' do + expect(worker.perform(build.id)).to_not be_truthy + end + + it 'fails for empty file fails' do + build.update_attributes(artifacts_file: empty_file) + expect(worker.perform(build.id)).to_not be_truthy + end + + it 'fails for invalid archive' do + build.update_attributes(artifacts_file: invalid_file) + expect(worker.perform(build.id)).to_not be_truthy + end + + it 'fails if sha on branch is not latest' do + commit.update_attributes(sha: 'old_sha') + build.update_attributes(artifacts_file: file) + expect(worker.perform(build.id)).to_not be_truthy + end +end From 732a821d4f00f9812d014b6c58eae2f9a18f7ddd Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 15 Dec 2015 22:48:23 +0100 Subject: [PATCH 002/183] Fix specs --- app/workers/pages_worker.rb | 3 ++- config/sidekiq_queues.yml | 1 + lib/backup/manager.rb | 2 +- spec/services/update_pages_service_spec.rb | 4 ++++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index 9aa3030264b..c51ec81c9da 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -34,7 +34,8 @@ class PagesWorker # We manually extract the archive and limit the archive size with dd results = Open3.pipeline(%W(gunzip -c #{artifacts}), %W(dd bs=#{BLOCK_SIZE} count=#{blocks}), - %W(tar -x -C #{temp_path} public/)) + %W(tar -x -C #{temp_path} public/), + err: '/dev/null') return unless results.compact.all?(&:success?) # Check if we did extract public directory diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 022b0e80917..56bf4e6b1de 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -50,3 +50,4 @@ - [reactive_caching, 1] - [cronjob, 1] - [default, 1] + - [pages, 1] diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index cefbfdce3bb..f099c0651ac 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -1,6 +1,6 @@ module Backup class Manager - ARCHIVES_TO_BACKUP = %w[uploads builds artifacts lfs registry] + ARCHIVES_TO_BACKUP = %w[uploads builds artifacts pages lfs registry] FOLDERS_TO_BACKUP = %w[repositories db] FILE_NAME_SUFFIX = '_gitlab_backup.tar' diff --git a/spec/services/update_pages_service_spec.rb b/spec/services/update_pages_service_spec.rb index ed392cd94ee..cf1ca15da44 100644 --- a/spec/services/update_pages_service_spec.rb +++ b/spec/services/update_pages_service_spec.rb @@ -5,6 +5,10 @@ describe UpdatePagesService, services: true do let(:data) { Gitlab::BuildDataBuilder.build(build) } let(:service) { UpdatePagesService.new(data) } + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(true) + end + context 'execute asynchronously for pages job' do before { build.name = 'pages' } From ac09f857cd9edd4a18280f617b48fe436109ceaa Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 16 Dec 2015 12:53:35 +0100 Subject: [PATCH 003/183] Remove locking and add force to FileUtils methods --- app/workers/pages_worker.rb | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index c51ec81c9da..59f4b4f16f4 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -5,7 +5,7 @@ class PagesWorker BLOCK_SIZE = 32.kilobytes MAX_SIZE = 1.terabyte - sidekiq_options queue: :pages + sidekiq_options queue: :pages, retry: false def perform(build_id) @build_id = build_id @@ -44,25 +44,17 @@ class PagesWorker FileUtils.mkdir_p(pages_path) - # Lock file for time of deployment to prevent the two processes from doing the concurrent deployment - File.open(lock_path, File::RDWR|File::CREAT, 0644) do |f| - f.flock(File::LOCK_EX) - return unless valid? + # Ignore deployment if the HEAD changed when we were extracting the archive + return unless valid? - # Do atomic move of pages - # Move and removal may not be atomic, but they are significantly faster then extracting and removal - # 1. We move deployed public to previous public path (file removal is slow) - # 2. We move temporary public to be deployed public - # 3. We remove previous public path - if File.exists?(public_path) - FileUtils.move(public_path, previous_public_path) - end - FileUtils.move(temp_public_path, public_path) - end - - if File.exists?(previous_public_path) - FileUtils.rm_r(previous_public_path, force: true) - end + # Do atomic move of pages + # Move and removal may not be atomic, but they are significantly faster then extracting and removal + # 1. We move deployed public to previous public path (file removal is slow) + # 2. We move temporary public to be deployed public + # 3. We remove previous public path + FileUtils.move(public_path, previous_public_path, force: true) + FileUtils.move(temp_public_path, public_path) + FileUtils.rm_r(previous_public_path, force: true) @status.success end From fa68e403e0d5539b19ceb5396394d634babdc2b9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 16 Dec 2015 12:53:54 +0100 Subject: [PATCH 004/183] Support https and custom port for pages --- app/models/project.rb | 7 +++++-- config/gitlab.yml.example | 2 ++ config/initializers/1_settings.rb | 28 +++++++++++++++++++--------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 48ff5ec7fc7..aa16b055e81 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1165,11 +1165,14 @@ class Project < ActiveRecord::Base def pages_url if Dir.exist?(public_pages_path) host = "#{namespace.path}.#{Settings.pages.domain}" + url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix| + "#{prefix}#{namespace.path}." + end # If the project path is the same as host, leave the short version - return "http://#{host}" if host == path + return url if host == path - "http://#{host}/#{path}" + "#{url}/#{path}" end end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index d41280624ae..fa764844cb9 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -163,6 +163,8 @@ production: &base # http://group.example.com/project # or project path can be a group page: group.example.com domain: example.com + port: 80 # Set to 443 if you serve the pages with HTTPS + https: false # Set to true if you serve the pages with HTTPS ## Mattermost ## For enabling Add to Mattermost button diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 0c06b73ba36..3932745e20c 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -5,8 +5,8 @@ class Settings < Settingslogic namespace Rails.env class << self - def gitlab_on_standard_port? - gitlab.port.to_i == (gitlab.https ? 443 : 80) + def on_standard_port?(config) + config.port.to_i == (config.https ? 443 : 80) end def host_without_www(url) @@ -14,7 +14,7 @@ class Settings < Settingslogic end def build_gitlab_ci_url - if gitlab_on_standard_port? + if on_standard_port?(gitlab) custom_port = nil else custom_port = ":#{gitlab.port}" @@ -27,6 +27,10 @@ class Settings < Settingslogic ].join('') end + def build_pages_url + base_url(pages).join('') + end + def build_gitlab_shell_ssh_path_prefix user_host = "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}" @@ -42,11 +46,11 @@ class Settings < Settingslogic end def build_base_gitlab_url - base_gitlab_url.join('') + base_url(gitlab).join('') end def build_gitlab_url - (base_gitlab_url + [gitlab.relative_url_root]).join('') + (base_url(gitlab) + [gitlab.relative_url_root]).join('') end # check that values in `current` (string or integer) is a contant in `modul`. @@ -74,11 +78,11 @@ class Settings < Settingslogic private - def base_gitlab_url - custom_port = gitlab_on_standard_port? ? nil : ":#{gitlab.port}" - [ gitlab.protocol, + def base_url(config) + custom_port = on_standard_port?(config) ? nil : ":#{config.port}" + [ config.protocol, "://", - gitlab.host, + config.host, custom_port ] end @@ -254,11 +258,17 @@ Settings.registry['issuer'] ||= nil Settings.registry['host_port'] ||= [Settings.registry['host'], Settings.registry['port']].compact.join(':') Settings.registry['path'] = File.expand_path(Settings.registry['path'] || File.join(Settings.shared['path'], 'registry'), Rails.root) +# # Pages +# Settings['pages'] ||= Settingslogic.new({}) Settings.pages['enabled'] = false if Settings.pages['enabled'].nil? Settings.pages['path'] = File.expand_path('shared/pages/', Rails.root) Settings.pages['domain'] ||= "example.com" +Settings.pages['https'] = false if Settings.pages['https'].nil? +Settings.pages['port'] ||= Settings.pages.https ? 443 : 80 +Settings.pages['protocol'] ||= Settings.pages.https ? "https" : "http" +Settings.pages['url'] ||= Settings.send(:build_pages_url) # # Git LFS From b27371d89848b35a1be06e253d0958f723fc242f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 16 Dec 2015 12:59:01 +0100 Subject: [PATCH 005/183] Change pages domain to host --- app/models/project.rb | 2 +- config/gitlab.yml.example | 2 +- config/initializers/1_settings.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index aa16b055e81..a1888c089ce 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1164,7 +1164,7 @@ class Project < ActiveRecord::Base def pages_url if Dir.exist?(public_pages_path) - host = "#{namespace.path}.#{Settings.pages.domain}" + host = "#{namespace.path}.#{Settings.pages.host}" url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix| "#{prefix}#{namespace.path}." end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index fa764844cb9..c6f06d43d07 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -162,7 +162,7 @@ production: &base # The domain under which the pages are served: # http://group.example.com/project # or project path can be a group page: group.example.com - domain: example.com + host: example.com port: 80 # Set to 443 if you serve the pages with HTTPS https: false # Set to true if you serve the pages with HTTPS diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 3932745e20c..b938a9cdb2a 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -264,7 +264,7 @@ Settings.registry['path'] = File.expand_path(Settings.registry['path' Settings['pages'] ||= Settingslogic.new({}) Settings.pages['enabled'] = false if Settings.pages['enabled'].nil? Settings.pages['path'] = File.expand_path('shared/pages/', Rails.root) -Settings.pages['domain'] ||= "example.com" +Settings.pages['host'] ||= "example.com" Settings.pages['https'] = false if Settings.pages['https'].nil? Settings.pages['port'] ||= Settings.pages.https ? 443 : 80 Settings.pages['protocol'] ||= Settings.pages.https ? "https" : "http" From adc1a9abb5adbf746b492938cb11576753edcc7e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 16 Dec 2015 13:04:05 +0100 Subject: [PATCH 006/183] Re-add missing gitlab_on_standard_port --- config/initializers/1_settings.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index b938a9cdb2a..842ea94c5a4 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -5,8 +5,8 @@ class Settings < Settingslogic namespace Rails.env class << self - def on_standard_port?(config) - config.port.to_i == (config.https ? 443 : 80) + def gitlab_on_standard_port? + on_standard_port?(gitlab) end def host_without_www(url) @@ -87,6 +87,10 @@ class Settings < Settingslogic ] end + def on_standard_port?(config) + config.port.to_i == (config.https ? 443 : 80) + end + # Extract the host part of the given +url+. def host(url) url = url.downcase From d28f1a7f4aa4bdf664e04a43022667e4e7637e73 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 16 Dec 2015 16:29:53 +0100 Subject: [PATCH 007/183] Split PagesWorker --- app/workers/pages_worker.rb | 107 +++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 43 deletions(-) diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index 59f4b4f16f4..c34259c15f1 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -12,7 +12,31 @@ class PagesWorker return unless valid? # Create status notifying the deployment of pages - @status = GenericCommitStatus.new( + @status = create_status + @status.run! + raise 'pages are outdated' unless latest? + + # Create temporary directory in which we will extract the artifacts + Dir.mktmpdir(nil, tmp_path) do |archive_path| + results = extract_archive(archive_path) + raise 'pages failed to extract' unless results.all?(&:success?) + + # Check if we did extract public directory + archive_public_path = File.join(archive_path, 'public') + raise 'pages miss the public folder' unless Dir.exists?(archive_public_path) + raise 'pages are outdated' unless latest? + deploy_page!(archive_public_path) + + @status.success + end + rescue => e + fail(e.message, !latest?) + end + + private + + def create_status + GenericCommitStatus.new( project: project, commit: build.commit, user: build.user, @@ -20,54 +44,51 @@ class PagesWorker stage: 'deploy', name: 'pages:deploy' ) - @status.run! + end - FileUtils.mkdir_p(tmp_path) + def extract_archive(temp_path) + results = Open3.pipeline(%W(gunzip -c #{artifacts}), + %W(dd bs=#{BLOCK_SIZE} count=#{blocks}), + %W(tar -x -C #{temp_path} public/), + err: '/dev/null') + results.compact + end + def deploy_page!(archive_public_path) + # Do atomic move of pages + # Move and removal may not be atomic, but they are significantly faster then extracting and removal + # 1. We move deployed public to previous public path (file removal is slow) + # 2. We move temporary public to be deployed public + # 3. We remove previous public path + FileUtils.mkdir_p(pages_path) + FileUtils.move(public_path, previous_public_path, force: true) + FileUtils.move(archive_public_path, public_path) + ensure + FileUtils.rm_r(previous_public_path, force: true) + end + + def fail(message, allow_failure = true) + @status.allow_failure = allow_failure + @status.description = message + @status.drop + end + + def valid? + build && build.artifacts_file? + end + + def latest? + # check if sha for the ref is still the most recent one + # this helps in case when multiple deployments happens + sha == latest_sha + end + + def blocks # Calculate dd parameters: we limit the size of pages max_size = current_application_settings.max_pages_size.megabytes max_size ||= MAX_SIZE blocks = 1 + max_size / BLOCK_SIZE - - # Create temporary directory in which we will extract the artifacts - Dir.mktmpdir(nil, tmp_path) do |temp_path| - # We manually extract the archive and limit the archive size with dd - results = Open3.pipeline(%W(gunzip -c #{artifacts}), - %W(dd bs=#{BLOCK_SIZE} count=#{blocks}), - %W(tar -x -C #{temp_path} public/), - err: '/dev/null') - return unless results.compact.all?(&:success?) - - # Check if we did extract public directory - temp_public_path = File.join(temp_path, 'public') - return unless Dir.exists?(temp_public_path) - - FileUtils.mkdir_p(pages_path) - - # Ignore deployment if the HEAD changed when we were extracting the archive - return unless valid? - - # Do atomic move of pages - # Move and removal may not be atomic, but they are significantly faster then extracting and removal - # 1. We move deployed public to previous public path (file removal is slow) - # 2. We move temporary public to be deployed public - # 3. We remove previous public path - FileUtils.move(public_path, previous_public_path, force: true) - FileUtils.move(temp_public_path, public_path) - FileUtils.rm_r(previous_public_path, force: true) - - @status.success - end - ensure - @status.drop if @status && @status.active? - end - - private - - def valid? - # check if sha for the ref is still the most recent one - # this helps in case when multiple deployments happens - build && build.artifacts_file? && sha == latest_sha + blocks end def build From 35dd2e12837ea507a02aca239fc7f78f949e9e99 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 16 Dec 2015 17:09:11 +0100 Subject: [PATCH 008/183] Fix tests --- app/workers/pages_worker.rb | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index c34259c15f1..6c6bb7ed13f 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -14,23 +14,26 @@ class PagesWorker # Create status notifying the deployment of pages @status = create_status @status.run! + raise 'pages are outdated' unless latest? # Create temporary directory in which we will extract the artifacts + FileUtils.mkdir_p(tmp_path) Dir.mktmpdir(nil, tmp_path) do |archive_path| - results = extract_archive(archive_path) - raise 'pages failed to extract' unless results.all?(&:success?) + extract_archive!(archive_path) # Check if we did extract public directory archive_public_path = File.join(archive_path, 'public') raise 'pages miss the public folder' unless Dir.exists?(archive_public_path) raise 'pages are outdated' unless latest? + deploy_page!(archive_public_path) @status.success end rescue => e fail(e.message, !latest?) + return false end private @@ -46,12 +49,12 @@ class PagesWorker ) end - def extract_archive(temp_path) + def extract_archive!(temp_path) results = Open3.pipeline(%W(gunzip -c #{artifacts}), %W(dd bs=#{BLOCK_SIZE} count=#{blocks}), %W(tar -x -C #{temp_path} public/), err: '/dev/null') - results.compact + raise 'pages failed to extract' unless results.compact.all?(&:success?) end def deploy_page!(archive_public_path) @@ -61,7 +64,10 @@ class PagesWorker # 2. We move temporary public to be deployed public # 3. We remove previous public path FileUtils.mkdir_p(pages_path) - FileUtils.move(public_path, previous_public_path, force: true) + begin + FileUtils.move(public_path, previous_public_path) + rescue + end FileUtils.move(archive_public_path, public_path) ensure FileUtils.rm_r(previous_public_path, force: true) From 0f2274cc14f1a4071d5b4ab258209e8c1b14d698 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 11:03:58 +0200 Subject: [PATCH 009/183] First draft of pages documentation --- doc/pages/README.md | 63 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index d08c34c3ebc..548a24c4cc4 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -1,12 +1,59 @@ # GitLab Pages -To start using GitLab Pages add to your project .gitlab-ci.yml with special pages job. +_**Note:** This feature was introduced in GitLab EE 8.3_ - pages: - image: jekyll - script: jekyll build - artifacts: - paths: - - public +To start using GitLab Pages add to your project `.gitlab-ci.yml` the special +`pages` job. The example below is using [jekyll][] and assumes the created +HTML files are generated under the `public/` directory which resides under the +root directory of your Git repository. -TODO +```yaml +pages: + image: jekyll + script: jekyll build + artifacts: + paths: + - public +``` + +- The pages are created when build artifacts for `pages` job are uploaded +- Pages serve the content under: http://group.pages.domain.com/project +- Pages can be used to serve the group page, special project named as host: group.pages.domain.com +- User can provide own 403 and 404 error pages by creating 403.html and 404.html in group page project +- Pages can be explicitly removed from the project by clicking Remove Pages in Project Settings +- The size of pages is limited by Application Setting: max pages size, which limits the maximum size of unpacked archive (default: 100MB) +- The public/ is extracted from artifacts and content is served as static pages +- Pages asynchronous worker use `dd` to limit the unpacked tar size +- Pages needs to be explicitly enabled and domain needs to be specified in gitlab.yml +- Pages are part of backups +- Pages notify the deployment status using Commit Status API +- Pages use a new sidekiq queue: pages +- Pages use a separate nginx config which needs to be explicitly added + +## Examples + +- Add example with stages. `test` using a linter tool, `deploy` in `pages` +- Add examples of more static tool generators + +```yaml +image: jekyll + +stages: + - test + - deploy + +lint: + script: jekyll build + stage: test + +pages: + script: jekyll build + stage: deploy + artifacts: + paths: + - public +``` + +## Current limitations + +- We currently support only http and port 80. It will be extended in the future. From c57881395bd7ba92b8b985545b239b00f3100284 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 11:07:35 +0200 Subject: [PATCH 010/183] Add missing jekyll website link --- doc/pages/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/pages/README.md b/doc/pages/README.md index 548a24c4cc4..3353561b9a9 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -57,3 +57,5 @@ pages: ## Current limitations - We currently support only http and port 80. It will be extended in the future. + +[jekyll]: http://jekyllrb.com/ From f934460e81d954afb10049d4d596744e1e7b0cab Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 11:09:52 +0200 Subject: [PATCH 011/183] Fix wrong assumption that the public dir must be present in your git repo --- doc/pages/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 3353561b9a9..ceda2a38915 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -4,8 +4,7 @@ _**Note:** This feature was introduced in GitLab EE 8.3_ To start using GitLab Pages add to your project `.gitlab-ci.yml` the special `pages` job. The example below is using [jekyll][] and assumes the created -HTML files are generated under the `public/` directory which resides under the -root directory of your Git repository. +HTML files are generated under the `public/` directory. ```yaml pages: From f8b0d06b3d341b822c6a2b7f54c979f7c0e86b94 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 17 Dec 2015 15:33:43 +0100 Subject: [PATCH 012/183] Fix pages storage path --- config/initializers/1_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 842ea94c5a4..e52171f0d64 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -267,7 +267,7 @@ Settings.registry['path'] = File.expand_path(Settings.registry['path' # Settings['pages'] ||= Settingslogic.new({}) Settings.pages['enabled'] = false if Settings.pages['enabled'].nil? -Settings.pages['path'] = File.expand_path('shared/pages/', Rails.root) +Settings.pages['path'] ||= File.expand_path('shared/pages/', Rails.root) Settings.pages['host'] ||= "example.com" Settings.pages['https'] = false if Settings.pages['https'].nil? Settings.pages['port'] ||= Settings.pages.https ? 443 : 80 From ab220022f40b526ce5b937caed2a13b4b1ca239b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 18:03:38 +0200 Subject: [PATCH 013/183] Add GitLab Pages administration guide --- doc/README.md | 1 + doc/pages/administration.md | 146 ++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 doc/pages/administration.md diff --git a/doc/README.md b/doc/README.md index f036febb7b9..951a302f8ba 100644 --- a/doc/README.md +++ b/doc/README.md @@ -54,6 +54,7 @@ - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE. - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. +- [GitLab Pages configuration](pages/administration.md) - [GitLab performance monitoring with InfluxDB](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. - [GitLab performance monitoring with Prometheus](administration/monitoring/performance/prometheus.md) Configure GitLab and Prometheus for measuring performance metrics. - [Request Profiling](administration/monitoring/performance/request_profiling.md) Get a detailed profile on slow requests. diff --git a/doc/pages/administration.md b/doc/pages/administration.md new file mode 100644 index 00000000000..e754d4cbd96 --- /dev/null +++ b/doc/pages/administration.md @@ -0,0 +1,146 @@ +# GitLab Pages Administration + +_**Note:** This feature was [introduced][ee-80] in GitLab EE 8.3_ + +If you are looking for ways to upload your static content in GitLab Pages, you +probably want to read the [user documentation](README.md). + +## Configuration + +There are a couple of things to consider before enabling GitLab pages in your +GitLab EE instance. + +1. You need to properly configure your DNS to point to the domain that pages + will be served +1. Pages use a separate nginx configuration file which needs to be explicitly + added in the server under which GitLab EE runs + +Both of these settings are described in detail in the sections below. + +### DNS configuration + +GitLab Pages expect to run on their own virtual host. In your DNS you need to +add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the host that +GitLab runs. For example, an entry would look like this: + +``` +*.gitlabpages.com. 60 IN A 1.2.3.4 +``` + +where `gitlabpages.com` is the domain under which GitLab Pages will be served +and `1.2.3.4` is the IP address of your GitLab instance. + +It is strongly advised to **not** use the GitLab domain to serve user pages. +See [security](#security). + +### Omnibus package installations + +See the relevant documentation at . + +### Installations from source + +1. Go to the GitLab installation directory: + + ```bash + cd /home/git/gitlab + ``` + +1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` in + order to enable the pages feature: + + ```bash + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + # The domain under which the pages are served: + # http://group.example.com/project + # or project path can be a group page: group.example.com + host: example.com + port: 80 # Set to 443 if you serve the pages with HTTPS + https: false # Set to true if you serve the pages with HTTPS + ``` + +1. Make sure you have copied the new `gitlab-pages` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf + ``` + + Don't forget to add your domain name in the Nginx config. For example if your + GitLab pages domain is `gitlabpages.com`, replace + + ```bash + server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + ``` + + with + + ``` + server_name ~^(?.*)\.gitlabpages\.com$; + ``` + + You must be extra careful to not remove the backslashes. + +1. Restart Nginx and GitLab: + + ```bash + sudo service nginx restart + sudo service gitlab restart + ``` + +### Running GitLab pages with HTTPS + +If you want the pages to be served under HTTPS, a wildcard SSL certificate is +required. + +1. In `gitlab.yml`, set the port to `443` and https to `true`: + + ```bash + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + # The domain under which the pages are served: + # http://group.example.com/project + # or project path can be a group page: group.example.com + host: gitlabpages.com + port: 443 # Set to 443 if you serve the pages with HTTPS + https: true # Set to true if you serve the pages with HTTPS + ``` +1. Use the `gitlab-pages-ssl` Nginx configuration file + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf + ``` + + Make sure to edit the config and add your domain as well as correctly point + to the right location where the SSL certificates reside. + +## Set maximum pages size + +The maximum size of the unpacked archive can be configured in the Admin area +under the Application settings in the **Maximum size of pages (MB)**. +The default is 100MB. + +## Security + +You should strongly consider running GitLab pages under a different hostname +than GitLab to prevent XSS. + +## How it works + +- The public/ is extracted from artifacts and content is served as static pages +- Pages asynchronous worker use `dd` to limit the unpacked tar size +- Pages are part of backups +- Pages notify the deployment status using Commit Status API +- Pages use a new sidekiq queue: pages + +[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 +[wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record From a60f5f6cb429ff232647665ab288b3df251669e5 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 19:38:21 +0200 Subject: [PATCH 014/183] GitLab Pages admin guide clean up [ci skip] - Fix markdown - Remove how it works section, maybe add it at a later point --- doc/pages/administration.md | 121 +++++++++++++++++------------------- 1 file changed, 58 insertions(+), 63 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index e754d4cbd96..02e575c851a 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -41,56 +41,55 @@ See the relevant documentation at .*)\.YOUR_GITLAB_PAGES\.DOMAIN$; - ``` + ```bash + server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + ``` - with + with - ``` - server_name ~^(?.*)\.gitlabpages\.com$; - ``` + ``` + server_name ~^(?.*)\.gitlabpages\.com$; + ``` - You must be extra careful to not remove the backslashes. + You must be extra careful to not remove the backslashes. 1. Restart Nginx and GitLab: - ```bash - sudo service nginx restart - sudo service gitlab restart - ``` + ```bash + sudo service nginx restart + sudo service gitlab restart + ``` ### Running GitLab pages with HTTPS @@ -99,29 +98,29 @@ required. 1. In `gitlab.yml`, set the port to `443` and https to `true`: - ```bash - ## GitLab Pages - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages + ```bash + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages - # The domain under which the pages are served: - # http://group.example.com/project - # or project path can be a group page: group.example.com - host: gitlabpages.com - port: 443 # Set to 443 if you serve the pages with HTTPS - https: true # Set to true if you serve the pages with HTTPS + # The domain under which the pages are served: + # http://group.example.com/project + # or project path can be a group page: group.example.com + host: gitlabpages.com + port: 443 # Set to 443 if you serve the pages with HTTPS + https: true # Set to true if you serve the pages with HTTPS ``` -1. Use the `gitlab-pages-ssl` Nginx configuration file +1. Copy the `gitlab-pages-ssl` Nginx configuration file: - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf - ``` + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf + ``` - Make sure to edit the config and add your domain as well as correctly point - to the right location where the SSL certificates reside. + Make sure to edit the config to add your domain as well as correctly point + to the right location where the SSL certificates reside. ## Set maximum pages size @@ -129,18 +128,14 @@ The maximum size of the unpacked archive can be configured in the Admin area under the Application settings in the **Maximum size of pages (MB)**. The default is 100MB. +## Backup + +Pages are part of the regular backup so there is nothing to configure. + ## Security You should strongly consider running GitLab pages under a different hostname -than GitLab to prevent XSS. - -## How it works - -- The public/ is extracted from artifacts and content is served as static pages -- Pages asynchronous worker use `dd` to limit the unpacked tar size -- Pages are part of backups -- Pages notify the deployment status using Commit Status API -- Pages use a new sidekiq queue: pages +than GitLab to prevent XSS attacks. [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 [wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record From 38028d92539afb70ab6c1d5ec0d03299a9d2c1db Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 19:56:07 +0200 Subject: [PATCH 015/183] Add example when using a subdomain [ci skip] --- doc/pages/administration.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 02e575c851a..6e242efae9f 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -45,7 +45,8 @@ See the relevant documentation at .*)\.gitlabpages\.com$; ``` - You must be extra careful to not remove the backslashes. + You must be extra careful to not remove the backslashes. If you are using + a subdomain, make sure to escape all dots (`.`) with a backslash (\). + For example `pages.gitlab.io` would be: + + ``` + server_name ~^(?.*)\.pages\.gitlab\.io$; + ``` 1. Restart Nginx and GitLab: @@ -108,7 +115,7 @@ required. # The domain under which the pages are served: # http://group.example.com/project # or project path can be a group page: group.example.com - host: gitlabpages.com + host: example.com port: 443 # Set to 443 if you serve the pages with HTTPS https: true # Set to true if you serve the pages with HTTPS ``` @@ -120,7 +127,8 @@ required. ``` Make sure to edit the config to add your domain as well as correctly point - to the right location where the SSL certificates reside. + to the right location where the SSL certificates reside. After all changes + restart Nginx. ## Set maximum pages size From 94fdf58a87f8f2cc54c0482a2fce9e3fa425d4b9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 17 Dec 2015 19:25:28 +0100 Subject: [PATCH 016/183] Store pages in shared/pages/fqdn/fqdn/public or shared/pages/fqdn/subpath/public - makes it simpler to implement CNAMEs in future --- app/models/project.rb | 14 +++--- app/workers/pages_worker.rb | 4 -- doc/pages/administration.md | 12 ++--- lib/support/nginx/gitlab-pages | 11 ++-- lib/support/nginx/gitlab-pages-ssl | 80 ++++++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 23 deletions(-) create mode 100644 lib/support/nginx/gitlab-pages-ssl diff --git a/app/models/project.rb b/app/models/project.rb index a1888c089ce..9cdd01e433d 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1137,6 +1137,7 @@ class Project < ActiveRecord::Base issues.opened.count end +<<<<<<< HEAD def visibility_level_allowed_as_fork?(level = self.visibility_level) return true unless forked? @@ -1162,22 +1163,23 @@ class Project < ActiveRecord::Base ensure_runners_token! end + def pages_host + "#{namespace.path}.#{Settings.pages.host}" + end + def pages_url if Dir.exist?(public_pages_path) - host = "#{namespace.path}.#{Settings.pages.host}" - url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix| - "#{prefix}#{namespace.path}." - end + url = Gitlab.config.pages.url.sub(Settings.pages.host, pages_host) # If the project path is the same as host, leave the short version - return url if host == path + return url if pages_host == path "#{url}/#{path}" end end def pages_path - File.join(Settings.pages.path, path_with_namespace) + File.join(Settings.pages.path, pages_host, path) end def public_pages_path diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index 6c6bb7ed13f..836e8d8ad9d 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -121,10 +121,6 @@ class PagesWorker @previous_public_path ||= File.join(pages_path, "public.#{SecureRandom.hex}") end - def lock_path - @lock_path ||= File.join(pages_path, 'deploy.lock') - end - def ref build.ref end diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 6e242efae9f..28e975e5621 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -74,22 +74,16 @@ See the relevant documentation at .*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + server_name *.YOUR_GITLAB_PAGES.DOMAIN; ``` with ``` - server_name ~^(?.*)\.gitlabpages\.com$; + server_name *.gitlabpages.com; ``` - You must be extra careful to not remove the backslashes. If you are using - a subdomain, make sure to escape all dots (`.`) with a backslash (\). - For example `pages.gitlab.io` would be: - - ``` - server_name ~^(?.*)\.pages\.gitlab\.io$; - ``` + You must be add `*` in front of your domain, this is required to catch all subdomains of gitlabpages.com. 1. Restart Nginx and GitLab: diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages index 0eeb0cd1917..6300c268521 100644 --- a/lib/support/nginx/gitlab-pages +++ b/lib/support/nginx/gitlab-pages @@ -1,18 +1,21 @@ +## GitLab +## + ## Pages serving host server { listen 0.0.0.0:80; listen [::]:80 ipv6only=on; ## Replace this with something like pages.gitlab.com - server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; - root /home/git/gitlab/shared/pages/${group}; + server_name *.YOUR_GITLAB_PAGES.DOMAIN; + root /home/git/gitlab/shared/pages/${host}; ## Individual nginx logs for GitLab pages access_log /var/log/nginx/gitlab_pages_access.log; error_log /var/log/nginx/gitlab_pages_error.log; - # 1. Try to get /project/ to => shared/pages/${group}/public/ or index.html - # 2. Try to get / to => shared/pages/${group}/${host}/public/ or index.html + # 1. Try to get /project/ from => shared/pages/${host}/${project}/public/ + # 2. Try to get / from => shared/pages/${host}/${host}/public/ location ~ ^/([^/]*)(/.*)?$ { try_files "/$1/public$2" "/$1/public$2/index.html" diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl new file mode 100644 index 00000000000..d3e8379ed29 --- /dev/null +++ b/lib/support/nginx/gitlab-pages-ssl @@ -0,0 +1,80 @@ +## GitLab +## + +## Redirects all HTTP traffic to the HTTPS host +server { + ## Either remove "default_server" from the listen line below, + ## or delete the /etc/nginx/sites-enabled/default file. This will cause gitlab + ## to be served if you visit any address that your server responds to, eg. + ## the ip address of the server (http://x.x.x.x/) + listen 0.0.0.0:80; + listen [::]:80 ipv6only=on; + + server_name *.YOUR_GITLAB_PAGES.DOMAIN; + server_tokens off; ## Don't show the nginx version number, a security best practice + + return 301 https://$http_host$request_uri; + + access_log /var/log/nginx/gitlab_pages_access.log; + error_log /var/log/nginx/gitlab_pages_access.log; +} + +## Pages serving host +server { + listen 0.0.0.0:443 ssl; + listen [::]:443 ipv6only=on ssl; + + ## Replace this with something like pages.gitlab.com + server_name *.YOUR_GITLAB_PAGES.DOMAIN; + server_tokens off; ## Don't show the nginx version number, a security best practice + root /home/git/gitlab/shared/pages/${host}; + + ## Strong SSL Security + ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/ + ssl on; + ssl_certificate /etc/nginx/ssl/gitlab-pages.crt; + ssl_certificate_key /etc/nginx/ssl/gitlab-pages.key; + + # GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs + ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 5m; + + ## See app/controllers/application_controller.rb for headers set + + ## [Optional] If your certficate has OCSP, enable OCSP stapling to reduce the overhead and latency of running SSL. + ## Replace with your ssl_trusted_certificate. For more info see: + ## - https://medium.com/devops-programming/4445f4862461 + ## - https://www.ruby-forum.com/topic/4419319 + ## - https://www.digitalocean.com/community/tutorials/how-to-configure-ocsp-stapling-on-apache-and-nginx + # ssl_stapling on; + # ssl_stapling_verify on; + # ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt; + # resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired + # resolver_timeout 5s; + + ## [Optional] Generate a stronger DHE parameter: + ## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096 + ## + # ssl_dhparam /etc/ssl/certs/dhparam.pem; + + ## Individual nginx logs for GitLab pages + access_log /var/log/nginx/gitlab_pages_access.log; + error_log /var/log/nginx/gitlab_pages_error.log; + + # 1. Try to get /project/ from => shared/pages/${host}/${project}/public/ + # 2. Try to get / from => shared/pages/${host}/${host}/public/ + location ~ ^/([^/]*)(/.*)?$ { + try_files "/$1/public$2" + "/$1/public$2/index.html" + "/${host}/public/${uri}" + "/${host}/public/${uri}/index.html" + =404; + } + + # Define custom error pages + error_page 403 /403.html; + error_page 404 /404.html; +} From 6fd06943f9135fcaa406f8df0c016981067638a5 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 21:03:14 +0200 Subject: [PATCH 017/183] Point to GP administration guide, no need to duplicate things [ci skip] --- doc/install/installation.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index c78e469055d..4496243da25 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -313,7 +313,7 @@ sudo usermod -aG redis git # Change the permissions of the directory where CI artifacts are stored sudo chmod -R u+rwX shared/artifacts/ - # Change the permissions of the directory where CI artifacts are stored + # Change the permissions of the directory where GitLab Pages are stored sudo chmod -R ug+rwX shared/pages/ # Copy the example Unicorn config @@ -487,16 +487,10 @@ Make sure to edit the config file to match your setup. Also, ensure that you mat # or else sudo rm -f /etc/nginx/sites-enabled/default sudo editor /etc/nginx/sites-available/gitlab -Copy the GitLab pages site config: +If you intend to enable GitLab pages, there is a separate Nginx config you need +to use. Read all about the needed configuration at the +[GitLab Pages administration guide](../pages/administration.md). - sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages - sudo ln -s /etc/nginx/sites-available/gitlab-pages /etc/nginx/sites-enabled/gitlab-pages - - # Change YOUR_GITLAB_PAGES\.DOMAIN to the fully-qualified - # domain name under which the pages will be served. - # The . (dot) replace with \. (backslash+dot) - sudo editor /etc/nginx/sites-available/gitlab-pages - **Note:** If you want to use HTTPS, replace the `gitlab` Nginx config with `gitlab-ssl`. See [Using HTTPS](#using-https) for HTTPS configuration details. ### Test Configuration From 4e03436befb2c912549440236456c063aa628e51 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 21:29:24 +0200 Subject: [PATCH 018/183] Small lines improvement [ci skip] --- doc/pages/administration.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 28e975e5621..7feeddd496b 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -83,7 +83,8 @@ See the relevant documentation at Date: Fri, 18 Dec 2015 00:44:11 +0200 Subject: [PATCH 019/183] Finish GitLab Pages user documentation --- doc/pages/README.md | 137 +++++++++++++++++++++++++++++++------------- 1 file changed, 98 insertions(+), 39 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index ceda2a38915..1d6ea1991f8 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -1,60 +1,119 @@ # GitLab Pages -_**Note:** This feature was introduced in GitLab EE 8.3_ +_**Note:** This feature was [introduced][ee-80] in GitLab EE 8.3_ -To start using GitLab Pages add to your project `.gitlab-ci.yml` the special -`pages` job. The example below is using [jekyll][] and assumes the created -HTML files are generated under the `public/` directory. +GitLab Pages allow you to host static content + +## Enable the pages feature in your GitLab EE instance + +The administrator guide is located at [administration](administration.md). + +## Understanding how GitLab Pages work + +GitLab Pages rely heavily on GitLab CI and its ability to upload artifacts. +The steps that are performed from the initialization of a project to the +creation of the static content, can be summed up to: + +1. Create project (its name could be specific according to the case) +1. Enable the GitLab Pages feature under the project's settings +1. Provide a specific job in `.gitlab-ci.yml` +1. GitLab Runner builds the project +1. GitLab CI uploads the artifacts +1. Nginx serves the content + +As a user, you should normally be concerned only with the first three items. + +In general there are four kinds of pages one might create. This is better +explained with an example so let's make some assumptions. + +The domain under which the pages are hosted is named `gitlab.io`. There is a +user with the username `walter` and they are the owner of an organization named +`therug`. The personal project of `walter` is named `area51` and don't forget +that the organization has also a project under its namespace, called +`welovecats`. + +The following table depicts what the project's name should be and under which +URL it will be accessible. + +| Pages type | Repository name | URL schema | +| ---------- | --------------- | ---------- | +| User page | `walter/walter.gitlab.io` | `https://walter.gitlab.io` | +| Group page | `therug/therug.gitlab.io` | `https://therug.gitlab.io` | +| Specific project under a user's page | `walter/area51` | `https://walter.gitlab.io/area51` | +| Specific project under a group's page | `therug/welovecats` | `https://therug.gitlab.io/welovecats` | + +## Enable the pages feature in your project + +The GitLab Pages feature needs to be explicitly enabled for each project +under its **Settings**. + +## Remove the contents of your pages + +Pages can be explicitly removed from a project by clicking **Remove Pages** +in a project's **Settings**. + +## Explore the contents of .gitlab-ci.yml + +Before reading this section, make sure you familiarize yourself with GitLab CI +and the specific syntax of[`.gitlab-ci.yml`](../ci/yaml/README.md) by +following our [quick start guide](../ci/quick_start/README.md). + +--- + +To make use of GitLab Pages your `.gitlab-ci.yml` must follow the rules below: + +1. A special `pages` job must be defined +1. Any static content must be placed under a `public/` directory +1. `artifacts` with a path to the `public/` directory must be defined + +The pages are created after the build completes successfully and the artifacts +for the `pages` job are uploaded to GitLab. + +The example below is using [Jekyll][] and assumes that the created HTML files +are generated under the `public/` directory. ```yaml +image: ruby:2.1 + pages: - image: jekyll - script: jekyll build + script: + - gem install jekyll + - jekyll build -d public/ artifacts: paths: - public ``` -- The pages are created when build artifacts for `pages` job are uploaded -- Pages serve the content under: http://group.pages.domain.com/project -- Pages can be used to serve the group page, special project named as host: group.pages.domain.com -- User can provide own 403 and 404 error pages by creating 403.html and 404.html in group page project -- Pages can be explicitly removed from the project by clicking Remove Pages in Project Settings -- The size of pages is limited by Application Setting: max pages size, which limits the maximum size of unpacked archive (default: 100MB) -- The public/ is extracted from artifacts and content is served as static pages -- Pages asynchronous worker use `dd` to limit the unpacked tar size -- Pages needs to be explicitly enabled and domain needs to be specified in gitlab.yml -- Pages are part of backups -- Pages notify the deployment status using Commit Status API -- Pages use a new sidekiq queue: pages -- Pages use a separate nginx config which needs to be explicitly added +## Example projects -## Examples +Below is a list of example projects that make use of static generators. +Contributions are very welcome. -- Add example with stages. `test` using a linter tool, `deploy` in `pages` -- Add examples of more static tool generators +* [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) -```yaml -image: jekyll +## Custom error codes pages -stages: - - test - - deploy +You can provide your own 403 and 404 error pages by creating the `403.html` and +`404.html` files respectively in the `public/` directory that will be included +in the artifacts. -lint: - script: jekyll build - stage: test +## Frequently Asked Questions -pages: - script: jekyll build - stage: deploy - artifacts: - paths: - - public -``` +**Q:** Where are my generated pages stored? -## Current limitations +**A:** All content is located by default under `shared/pages/` in the root +directory of the GitLab installation. To be exact, all specific projects under +a namespace are stored ind `shared/pages/${namespace}/${project}/public/` and +all user/group pages in `shared/pages/${namespace}/${namespace}/public/`. -- We currently support only http and port 80. It will be extended in the future. +--- + +**Q:** Can I download my generated pages? + +**A:** Sure. All you need is to download the artifacts archive from the build + page. + +--- [jekyll]: http://jekyllrb.com/ +[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 From 139ebce691fd9575ebde46111accb86d03c12b21 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 Dec 2015 01:30:16 +0200 Subject: [PATCH 020/183] Clean up the text in pages view --- app/views/projects/edit.html.haml | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 89e2d4046b8..f9c6809b903 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -142,25 +142,32 @@ .panel-body - if @project.pages_url %strong - Congratulations. Your pages are served at: + Congratulations! Your pages are served at: %p= link_to @project.pages_url, @project.pages_url - else %p - To publish pages create .gitlab-ci.yml with - %strong pages job - and send public/ folder to GitLab. + Learn how to upload your static site and have it served by + GitLab by following the #{link_to "documentation on GitLab Pages", "http://doc.gitlab.com/pages/README.html", target: :blank}. %p - Use existing tools: + In the example below we define a special job named + %code pages + which is using Jekyll to build a static site. The generated + HTML will be stored in the + %code public/ + directory which will then be archived and uploaded to GitLab. + The name of the directory should not be different than + %code public/ + in order for the pages to work. %ul %li %pre :plain pages: - image: jekyll - script: jekyll build + image: jekyll/jekyll + script: jekyll build -d public/ artifacts: paths: - - public + - public/ - if @project.pages_url && can?(current_user, :remove_pages, @project) .form-actions From cab3ea0044c4b072fa0fd41eb21ea664830cac1f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 Dec 2015 01:57:09 +0200 Subject: [PATCH 021/183] Add missing intro [ci skip] --- doc/pages/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 1d6ea1991f8..1afe2a97036 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -2,7 +2,9 @@ _**Note:** This feature was [introduced][ee-80] in GitLab EE 8.3_ -GitLab Pages allow you to host static content +With GitLab Pages you can host for free your static websites on GitLab. +Combined with the power of GitLab CI and the help of GitLab Runner you can +deploy static pages for your individual projects your user or your group. ## Enable the pages feature in your GitLab EE instance From 31454eb2f0f6bf960be5a51f0d8073672ea621a9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 Dec 2015 01:57:32 +0200 Subject: [PATCH 022/183] Capitalize Pages [ci skip] --- doc/pages/administration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 7feeddd496b..02d737aedbb 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -93,7 +93,7 @@ See the relevant documentation at Date: Fri, 18 Dec 2015 02:20:29 +0200 Subject: [PATCH 023/183] Add section on storage path [ci skip] --- doc/pages/administration.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 02d737aedbb..8df861f70ec 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -48,7 +48,7 @@ See the relevant documentation at Date: Fri, 18 Dec 2015 12:18:00 +0100 Subject: [PATCH 024/183] Revert "Small lines improvement [ci skip]" This reverts commit 0d73bd93bab349d84910cf14773633b8a66df466. --- doc/pages/administration.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 8df861f70ec..29762829819 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -83,8 +83,7 @@ See the relevant documentation at Date: Fri, 18 Dec 2015 12:18:11 +0100 Subject: [PATCH 025/183] Revert "Store pages in shared/pages/fqdn/fqdn/public or shared/pages/fqdn/subpath/public - makes it simpler to implement CNAMEs in future" This reverts commit 86a2a78f0d13a678899460638add6b862059433e. --- app/models/project.rb | 14 +++--- app/workers/pages_worker.rb | 4 ++ doc/pages/administration.md | 12 +++-- lib/support/nginx/gitlab-pages | 11 ++-- lib/support/nginx/gitlab-pages-ssl | 80 ------------------------------ 5 files changed, 23 insertions(+), 98 deletions(-) delete mode 100644 lib/support/nginx/gitlab-pages-ssl diff --git a/app/models/project.rb b/app/models/project.rb index 9cdd01e433d..a1888c089ce 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1137,7 +1137,6 @@ class Project < ActiveRecord::Base issues.opened.count end -<<<<<<< HEAD def visibility_level_allowed_as_fork?(level = self.visibility_level) return true unless forked? @@ -1163,23 +1162,22 @@ class Project < ActiveRecord::Base ensure_runners_token! end - def pages_host - "#{namespace.path}.#{Settings.pages.host}" - end - def pages_url if Dir.exist?(public_pages_path) - url = Gitlab.config.pages.url.sub(Settings.pages.host, pages_host) + host = "#{namespace.path}.#{Settings.pages.host}" + url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix| + "#{prefix}#{namespace.path}." + end # If the project path is the same as host, leave the short version - return url if pages_host == path + return url if host == path "#{url}/#{path}" end end def pages_path - File.join(Settings.pages.path, pages_host, path) + File.join(Settings.pages.path, path_with_namespace) end def public_pages_path diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index 836e8d8ad9d..6c6bb7ed13f 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -121,6 +121,10 @@ class PagesWorker @previous_public_path ||= File.join(pages_path, "public.#{SecureRandom.hex}") end + def lock_path + @lock_path ||= File.join(pages_path, 'deploy.lock') + end + def ref build.ref end diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 29762829819..98a26ec7be9 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -74,16 +74,22 @@ See the relevant documentation at .*)\.YOUR_GITLAB_PAGES\.DOMAIN$; ``` with ``` - server_name *.gitlabpages.com; + server_name ~^(?.*)\.gitlabpages\.com$; ``` - You must be add `*` in front of your domain, this is required to catch all subdomains of gitlabpages.com. + You must be extra careful to not remove the backslashes. If you are using + a subdomain, make sure to escape all dots (`.`) with a backslash (\). + For example `pages.gitlab.io` would be: + + ``` + server_name ~^(?.*)\.pages\.gitlab\.io$; + ``` 1. Restart Nginx and GitLab: diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages index 6300c268521..0eeb0cd1917 100644 --- a/lib/support/nginx/gitlab-pages +++ b/lib/support/nginx/gitlab-pages @@ -1,21 +1,18 @@ -## GitLab -## - ## Pages serving host server { listen 0.0.0.0:80; listen [::]:80 ipv6only=on; ## Replace this with something like pages.gitlab.com - server_name *.YOUR_GITLAB_PAGES.DOMAIN; - root /home/git/gitlab/shared/pages/${host}; + server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + root /home/git/gitlab/shared/pages/${group}; ## Individual nginx logs for GitLab pages access_log /var/log/nginx/gitlab_pages_access.log; error_log /var/log/nginx/gitlab_pages_error.log; - # 1. Try to get /project/ from => shared/pages/${host}/${project}/public/ - # 2. Try to get / from => shared/pages/${host}/${host}/public/ + # 1. Try to get /project/ to => shared/pages/${group}/public/ or index.html + # 2. Try to get / to => shared/pages/${group}/${host}/public/ or index.html location ~ ^/([^/]*)(/.*)?$ { try_files "/$1/public$2" "/$1/public$2/index.html" diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl deleted file mode 100644 index d3e8379ed29..00000000000 --- a/lib/support/nginx/gitlab-pages-ssl +++ /dev/null @@ -1,80 +0,0 @@ -## GitLab -## - -## Redirects all HTTP traffic to the HTTPS host -server { - ## Either remove "default_server" from the listen line below, - ## or delete the /etc/nginx/sites-enabled/default file. This will cause gitlab - ## to be served if you visit any address that your server responds to, eg. - ## the ip address of the server (http://x.x.x.x/) - listen 0.0.0.0:80; - listen [::]:80 ipv6only=on; - - server_name *.YOUR_GITLAB_PAGES.DOMAIN; - server_tokens off; ## Don't show the nginx version number, a security best practice - - return 301 https://$http_host$request_uri; - - access_log /var/log/nginx/gitlab_pages_access.log; - error_log /var/log/nginx/gitlab_pages_access.log; -} - -## Pages serving host -server { - listen 0.0.0.0:443 ssl; - listen [::]:443 ipv6only=on ssl; - - ## Replace this with something like pages.gitlab.com - server_name *.YOUR_GITLAB_PAGES.DOMAIN; - server_tokens off; ## Don't show the nginx version number, a security best practice - root /home/git/gitlab/shared/pages/${host}; - - ## Strong SSL Security - ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/ - ssl on; - ssl_certificate /etc/nginx/ssl/gitlab-pages.crt; - ssl_certificate_key /etc/nginx/ssl/gitlab-pages.key; - - # GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs - ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_prefer_server_ciphers on; - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 5m; - - ## See app/controllers/application_controller.rb for headers set - - ## [Optional] If your certficate has OCSP, enable OCSP stapling to reduce the overhead and latency of running SSL. - ## Replace with your ssl_trusted_certificate. For more info see: - ## - https://medium.com/devops-programming/4445f4862461 - ## - https://www.ruby-forum.com/topic/4419319 - ## - https://www.digitalocean.com/community/tutorials/how-to-configure-ocsp-stapling-on-apache-and-nginx - # ssl_stapling on; - # ssl_stapling_verify on; - # ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt; - # resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired - # resolver_timeout 5s; - - ## [Optional] Generate a stronger DHE parameter: - ## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096 - ## - # ssl_dhparam /etc/ssl/certs/dhparam.pem; - - ## Individual nginx logs for GitLab pages - access_log /var/log/nginx/gitlab_pages_access.log; - error_log /var/log/nginx/gitlab_pages_error.log; - - # 1. Try to get /project/ from => shared/pages/${host}/${project}/public/ - # 2. Try to get / from => shared/pages/${host}/${host}/public/ - location ~ ^/([^/]*)(/.*)?$ { - try_files "/$1/public$2" - "/$1/public$2/index.html" - "/${host}/public/${uri}" - "/${host}/public/${uri}/index.html" - =404; - } - - # Define custom error pages - error_page 403 /403.html; - error_page 404 /404.html; -} From 2c2447771f098f7c8d692e7318d8f822df468b48 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 18 Dec 2015 12:40:34 +0100 Subject: [PATCH 026/183] Rename pages namespace or project path when changed - Move UploadsTransfer to ProjectTransfer and inherit from this to UploadsTransfer and PagesTransfer --- app/models/namespace.rb | 1 + app/models/project.rb | 1 + app/services/projects/transfer_service.rb | 3 ++ app/workers/pages_worker.rb | 4 --- doc/pages/administration.md | 1 + lib/gitlab/pages_transfer.rb | 7 ++++ lib/gitlab/project_transfer.rb | 35 +++++++++++++++++++ lib/gitlab/uploads_transfer.rb | 30 +--------------- ...nsfer_spec.rb => project_transfer_spec.rb} | 11 +++--- .../projects/transfer_service_spec.rb | 2 ++ 10 files changed, 57 insertions(+), 38 deletions(-) create mode 100644 lib/gitlab/pages_transfer.rb create mode 100644 lib/gitlab/project_transfer.rb rename spec/lib/gitlab/{uploads_transfer_spec.rb => project_transfer_spec.rb} (75%) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 67d8c1c2e4c..2fb2eb44aaa 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -130,6 +130,7 @@ class Namespace < ActiveRecord::Base end Gitlab::UploadsTransfer.new.rename_namespace(path_was, path) + Gitlab::PagesTransfer.new.rename_namespace(path_was, path) remove_exports! diff --git a/app/models/project.rb b/app/models/project.rb index a1888c089ce..e9c7108e805 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -961,6 +961,7 @@ class Project < ActiveRecord::Base Gitlab::AppLogger.info "Project was renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}" Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path) + Gitlab::PagesTransfer.new.rename_project(path_was, path, namespace.path) end # Expires various caches before a project is renamed. diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 34ec575e808..20b049b5973 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -64,6 +64,9 @@ module Projects # Move uploads Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path) + # Move pages + Gitlab::PagesTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path) + project.old_path_with_namespace = old_path SystemHooksService.new.execute_hooks_for(project, :transfer) diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index 6c6bb7ed13f..836e8d8ad9d 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -121,10 +121,6 @@ class PagesWorker @previous_public_path ||= File.join(pages_path, "public.#{SecureRandom.hex}") end - def lock_path - @lock_path ||= File.join(pages_path, 'deploy.lock') - end - def ref build.ref end diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 98a26ec7be9..2356a123fa3 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -119,6 +119,7 @@ required. port: 443 # Set to 443 if you serve the pages with HTTPS https: true # Set to true if you serve the pages with HTTPS ``` + 1. Copy the `gitlab-pages-ssl` Nginx configuration file: ```bash diff --git a/lib/gitlab/pages_transfer.rb b/lib/gitlab/pages_transfer.rb new file mode 100644 index 00000000000..fb215f27cbd --- /dev/null +++ b/lib/gitlab/pages_transfer.rb @@ -0,0 +1,7 @@ +module Gitlab + class PagesTransfer < ProjectTransfer + def root_dir + Gitlab.config.pages.path + end + end +end diff --git a/lib/gitlab/project_transfer.rb b/lib/gitlab/project_transfer.rb new file mode 100644 index 00000000000..1bba0b78e2f --- /dev/null +++ b/lib/gitlab/project_transfer.rb @@ -0,0 +1,35 @@ +module Gitlab + class ProjectTransfer + def move_project(project_path, namespace_path_was, namespace_path) + new_namespace_folder = File.join(root_dir, namespace_path) + FileUtils.mkdir_p(new_namespace_folder) unless Dir.exist?(new_namespace_folder) + from = File.join(root_dir, namespace_path_was, project_path) + to = File.join(root_dir, namespace_path, project_path) + move(from, to, "") + end + + def rename_project(path_was, path, namespace_path) + base_dir = File.join(root_dir, namespace_path) + move(path_was, path, base_dir) + end + + def rename_namespace(path_was, path) + move(path_was, path) + end + + def root_dir + raise NotImplementedError + end + + private + + def move(path_was, path, base_dir = nil) + base_dir = root_dir unless base_dir + from = File.join(base_dir, path_was) + to = File.join(base_dir, path) + FileUtils.mv(from, to) + rescue Errno::ENOENT + false + end + end +end diff --git a/lib/gitlab/uploads_transfer.rb b/lib/gitlab/uploads_transfer.rb index be8fcc7b2d2..81701831a6a 100644 --- a/lib/gitlab/uploads_transfer.rb +++ b/lib/gitlab/uploads_transfer.rb @@ -1,33 +1,5 @@ module Gitlab - class UploadsTransfer - def move_project(project_path, namespace_path_was, namespace_path) - new_namespace_folder = File.join(root_dir, namespace_path) - FileUtils.mkdir_p(new_namespace_folder) unless Dir.exist?(new_namespace_folder) - from = File.join(root_dir, namespace_path_was, project_path) - to = File.join(root_dir, namespace_path, project_path) - move(from, to, "") - end - - def rename_project(path_was, path, namespace_path) - base_dir = File.join(root_dir, namespace_path) - move(path_was, path, base_dir) - end - - def rename_namespace(path_was, path) - move(path_was, path) - end - - private - - def move(path_was, path, base_dir = nil) - base_dir = root_dir unless base_dir - from = File.join(base_dir, path_was) - to = File.join(base_dir, path) - FileUtils.mv(from, to) - rescue Errno::ENOENT - false - end - + class UploadsTransfer < ProjectTransfer def root_dir File.join(Rails.root, "public", "uploads") end diff --git a/spec/lib/gitlab/uploads_transfer_spec.rb b/spec/lib/gitlab/project_transfer_spec.rb similarity index 75% rename from spec/lib/gitlab/uploads_transfer_spec.rb rename to spec/lib/gitlab/project_transfer_spec.rb index 4092f7fb638..e2d6b1b9ab7 100644 --- a/spec/lib/gitlab/uploads_transfer_spec.rb +++ b/spec/lib/gitlab/project_transfer_spec.rb @@ -1,9 +1,10 @@ require 'spec_helper' -describe Gitlab::UploadsTransfer, lib: true do +describe Gitlab::ProjectTransfer, lib: true do before do @root_dir = File.join(Rails.root, "public", "uploads") - @upload_transfer = Gitlab::UploadsTransfer.new + @project_transfer = Gitlab::ProjectTransfer.new + allow(@project_transfer).to receive(:root_dir).and_return(@root_dir) @project_path_was = "test_project_was" @project_path = "test_project" @@ -21,7 +22,7 @@ describe Gitlab::UploadsTransfer, lib: true do describe '#move_project' do it "moves project upload to another namespace" do FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path)) - @upload_transfer.move_project(@project_path, @namespace_path_was, @namespace_path) + @project_transfer.move_project(@project_path, @namespace_path_was, @namespace_path) expected_path = File.join(@root_dir, @namespace_path, @project_path) expect(Dir.exist?(expected_path)).to be_truthy @@ -31,7 +32,7 @@ describe Gitlab::UploadsTransfer, lib: true do describe '#rename_project' do it "renames project" do FileUtils.mkdir_p(File.join(@root_dir, @namespace_path, @project_path_was)) - @upload_transfer.rename_project(@project_path_was, @project_path, @namespace_path) + @project_transfer.rename_project(@project_path_was, @project_path, @namespace_path) expected_path = File.join(@root_dir, @namespace_path, @project_path) expect(Dir.exist?(expected_path)).to be_truthy @@ -41,7 +42,7 @@ describe Gitlab::UploadsTransfer, lib: true do describe '#rename_namespace' do it "renames namespace" do FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path)) - @upload_transfer.rename_namespace(@namespace_path_was, @namespace_path) + @project_transfer.rename_namespace(@namespace_path_was, @namespace_path) expected_path = File.join(@root_dir, @namespace_path, @project_path) expect(Dir.exist?(expected_path)).to be_truthy diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 1540b90163a..5d5812c2c15 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -9,6 +9,8 @@ describe Projects::TransferService, services: true do before do allow_any_instance_of(Gitlab::UploadsTransfer). to receive(:move_project).and_return(true) + allow_any_instance_of(Gitlab::PagesTransfer). + to receive(:move_project).and_return(true) group.add_owner(user) @result = transfer_project(project, user, group) end From d26eadf305ecf40d320c3bca0baf9a58288de7a6 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 Dec 2015 13:40:13 +0200 Subject: [PATCH 027/183] Fix small typos in GP user guide --- doc/pages/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 1afe2a97036..552b3436d77 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -62,7 +62,7 @@ following our [quick start guide](../ci/quick_start/README.md). --- -To make use of GitLab Pages your `.gitlab-ci.yml` must follow the rules below: +To make use of GitLab Pages, your `.gitlab-ci.yml` must follow the rules below: 1. A special `pages` job must be defined 1. Any static content must be placed under a `public/` directory @@ -105,7 +105,7 @@ in the artifacts. **A:** All content is located by default under `shared/pages/` in the root directory of the GitLab installation. To be exact, all specific projects under -a namespace are stored ind `shared/pages/${namespace}/${project}/public/` and +a namespace are stored in `shared/pages/${namespace}/${project}/public/` and all user/group pages in `shared/pages/${namespace}/${namespace}/public/`. --- From a691e9f494c7a661f9fa8ba199dbe6180d8d0329 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 Dec 2015 13:40:55 +0200 Subject: [PATCH 028/183] Minor cleanup, use gitlab.io as an example domain for GP --- doc/pages/administration.md | 39 ++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 2356a123fa3..b1ef2cebb14 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -12,26 +12,26 @@ GitLab EE instance. 1. You need to properly configure your DNS to point to the domain that pages will be served -1. Pages use a separate nginx configuration file which needs to be explicitly +1. Pages use a separate Nginx configuration file which needs to be explicitly added in the server under which GitLab EE runs Both of these settings are described in detail in the sections below. ### DNS configuration -GitLab Pages expect to run on their own virtual host. In your DNS you need to -add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the host that -GitLab runs. For example, an entry would look like this: +GitLab Pages expect to run on their own virtual host. In your DNS server/provider +you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the +host that GitLab runs. For example, an entry would look like this: ``` -*.gitlabpages.com. 60 IN A 1.2.3.4 +*.gitlab.io. 60 IN A 1.2.3.4 ``` -where `gitlabpages.com` is the domain under which GitLab Pages will be served +where `gitlab.io` is the domain under which GitLab Pages will be served and `1.2.3.4` is the IP address of your GitLab instance. It is strongly advised to **not** use the GitLab domain to serve user pages. -See [security](#security). +For more information see the [security section](#security). ### Omnibus package installations @@ -58,7 +58,7 @@ See the relevant documentation at .*)\.YOUR_GITLAB_PAGES\.DOMAIN$; @@ -80,16 +80,11 @@ See the relevant documentation at .*)\.gitlabpages\.com$; + server_name *.gitlab.io; ``` - You must be extra careful to not remove the backslashes. If you are using - a subdomain, make sure to escape all dots (`.`) with a backslash (\). - For example `pages.gitlab.io` would be: - - ``` - server_name ~^(?.*)\.pages\.gitlab\.io$; - ``` + You must be add `*` in front of your domain, this is required to catch all + subdomains of `gitlab.io`. 1. Restart Nginx and GitLab: @@ -115,7 +110,7 @@ required. # The domain under which the pages are served: # http://group.example.com/project # or project path can be a group page: group.example.com - host: example.com + host: gitlab.io port: 443 # Set to 443 if you serve the pages with HTTPS https: true # Set to true if you serve the pages with HTTPS ``` @@ -128,13 +123,13 @@ required. ``` Make sure to edit the config to add your domain as well as correctly point - to the right location where the SSL certificates reside. After all changes - restart Nginx. + to the right location of the SSL certificate files. Restart Nginx for the + changes to take effect. ## Set maximum pages size -The maximum size of the unpacked archive can be configured in the Admin area -under the Application settings in the **Maximum size of pages (MB)**. +The maximum size of the unpacked archive per project can be configured in the +Admin area under the Application settings in the **Maximum size of pages (MB)**. The default is 100MB. ## Change storage path From 9ff381c492695bf9b76b27047bd0b38a70a4daac Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 Dec 2015 13:41:33 +0200 Subject: [PATCH 029/183] Add pages to excluded directories in backup task [ci skip] --- doc/raketasks/backup_restore.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index f6b4db71b44..0fb69d63dbe 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -84,6 +84,29 @@ Deleting tmp directories...[DONE] Deleting old backups... [SKIPPING] ``` +## Exclude specific directories from the backup + +You can choose what should be backed up by adding the environment variable `SKIP`. +The available options are: + +* `db` +* `uploads` (attachments) +* `repositories` +* `builds` (CI build output logs) +* `artifacts` (CI build artifacts) +* `lfs` (LFS objects) +* `pages` (pages content) + +Use a comma to specify several options at the same time: + +``` +# use this command if you've installed GitLab with the Omnibus package +sudo gitlab-rake gitlab:backup:create SKIP=db,uploads + +# if you've installed GitLab from source +sudo -u git -H bundle exec rake gitlab:backup:create SKIP=db,uploads RAILS_ENV=production +``` + ## Upload backups to remote (cloud) storage Starting with GitLab 7.4 you can let the backup script upload the '.tar' file it creates. From e9e8a2f60811c460d0bb850da2bb35ea43e35698 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 18 Dec 2015 13:07:53 +0100 Subject: [PATCH 030/183] Asynchronously remove pages --- app/models/project.rb | 6 +++++- app/services/update_pages_service.rb | 2 +- app/workers/pages_worker.rb | 11 ++++++++++- spec/workers/pages_worker_spec.rb | 21 ++++++++++++++------- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index e9c7108e805..7a5bf77c5a9 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1186,7 +1186,11 @@ class Project < ActiveRecord::Base end def remove_pages - FileUtils.rm_r(pages_path, force: true) + temp_path = "#{path}.#{SecureRandom.hex}" + + if Gitlab::PagesTransfer.new.rename_project(path, temp_path, namespace.path) + PagesWorker.perform_in(5.minutes, :remove, namespace.path, temp_path) + end end def wiki diff --git a/app/services/update_pages_service.rb b/app/services/update_pages_service.rb index 818bb94a293..39f08b2a03d 100644 --- a/app/services/update_pages_service.rb +++ b/app/services/update_pages_service.rb @@ -10,6 +10,6 @@ class UpdatePagesService return unless data[:build_name] == 'pages' return unless data[:build_status] == 'success' - PagesWorker.perform_async(data[:build_id]) + PagesWorker.perform_async(:deploy, data[:build_id]) end end diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index 836e8d8ad9d..ff765a6c13c 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -7,7 +7,11 @@ class PagesWorker sidekiq_options queue: :pages, retry: false - def perform(build_id) + def perform(action, *arg) + send(action, *arg) + end + + def deploy(build_id) @build_id = build_id return unless valid? @@ -36,6 +40,11 @@ class PagesWorker return false end + def remove(namespace_path, project_path) + full_path = File.join(Settings.pages.path, namespace_path, project_path) + FileUtils.rm_r(full_path, force: true) + end + private def create_status diff --git a/spec/workers/pages_worker_spec.rb b/spec/workers/pages_worker_spec.rb index 158a4b3ba8d..85592154598 100644 --- a/spec/workers/pages_worker_spec.rb +++ b/spec/workers/pages_worker_spec.rb @@ -18,41 +18,48 @@ describe PagesWorker do it 'succeeds' do expect(project.pages_url).to be_nil - expect(worker.perform(build.id)).to be_truthy + expect(worker.deploy(build.id)).to be_truthy expect(project.pages_url).to_not be_nil end it 'limits pages size' do stub_application_setting(max_pages_size: 1) - expect(worker.perform(build.id)).to_not be_truthy + expect(worker.deploy(build.id)).to_not be_truthy end it 'removes pages after destroy' do + expect(PagesWorker).to receive(:perform_in) expect(project.pages_url).to be_nil - expect(worker.perform(build.id)).to be_truthy + expect(worker.deploy(build.id)).to be_truthy expect(project.pages_url).to_not be_nil project.destroy expect(Dir.exist?(project.public_pages_path)).to be_falsey end end + it 'fails to remove project pages when no pages is deployed' do + expect(PagesWorker).to_not receive(:perform_in) + expect(project.pages_url).to be_nil + project.destroy + end + it 'fails if no artifacts' do - expect(worker.perform(build.id)).to_not be_truthy + expect(worker.deploy(build.id)).to_not be_truthy end it 'fails for empty file fails' do build.update_attributes(artifacts_file: empty_file) - expect(worker.perform(build.id)).to_not be_truthy + expect(worker.deploy(build.id)).to_not be_truthy end it 'fails for invalid archive' do build.update_attributes(artifacts_file: invalid_file) - expect(worker.perform(build.id)).to_not be_truthy + expect(worker.deploy(build.id)).to_not be_truthy end it 'fails if sha on branch is not latest' do commit.update_attributes(sha: 'old_sha') build.update_attributes(artifacts_file: file) - expect(worker.perform(build.id)).to_not be_truthy + expect(worker.deploy(build.id)).to_not be_truthy end end From 2c6a852529cc42507e45d1266489df491ef1b893 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 18 Dec 2015 13:09:14 +0100 Subject: [PATCH 031/183] Refer to server_name with regex --- doc/pages/administration.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index b1ef2cebb14..9c24fee4f1a 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -80,11 +80,16 @@ See the relevant documentation at .*)\.gitlabpages\.com$; ``` - You must be add `*` in front of your domain, this is required to catch all - subdomains of `gitlab.io`. + You must be extra careful to not remove the backslashes. If you are using + a subdomain, make sure to escape all dots (`.`) with a backslash (\). + For example `pages.gitlab.io` would be: + + ``` + server_name ~^(?.*)\.pages\.gitlab\.io$; + ``` 1. Restart Nginx and GitLab: From 3fbe9b3b49d871071e16dad215e9af0e4dd68e42 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 18 Dec 2015 13:15:12 +0100 Subject: [PATCH 032/183] Add comment about the sequence when removing the pages --- app/models/project.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/project.rb b/app/models/project.rb index 7a5bf77c5a9..8a8aca44945 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1186,6 +1186,9 @@ class Project < ActiveRecord::Base end def remove_pages + # 1. We rename pages to temporary directory + # 2. We wait 5 minutes, due to NFS caching + # 3. We asynchronously remove pages with force temp_path = "#{path}.#{SecureRandom.hex}" if Gitlab::PagesTransfer.new.rename_project(path, temp_path, namespace.path) From 6c9ba469d9538c58434db492c0a955c20aba2ba1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 18 Dec 2015 16:18:57 +0100 Subject: [PATCH 033/183] Bring back GitLab Pages SSL config --- lib/support/nginx/gitlab-pages | 3 ++ lib/support/nginx/gitlab-pages-ssl | 81 ++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 lib/support/nginx/gitlab-pages-ssl diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages index 0eeb0cd1917..33f573d0b5b 100644 --- a/lib/support/nginx/gitlab-pages +++ b/lib/support/nginx/gitlab-pages @@ -1,3 +1,6 @@ +## GitLab +## + ## Pages serving host server { listen 0.0.0.0:80; diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl new file mode 100644 index 00000000000..006610262f9 --- /dev/null +++ b/lib/support/nginx/gitlab-pages-ssl @@ -0,0 +1,81 @@ +## GitLab +## + +## Redirects all HTTP traffic to the HTTPS host +server { + ## Either remove "default_server" from the listen line below, + ## or delete the /etc/nginx/sites-enabled/default file. This will cause gitlab + ## to be served if you visit any address that your server responds to, eg. + ## the ip address of the server (http://x.x.x.x/) + listen 0.0.0.0:80; + listen [::]:80 ipv6only=on; + + ## Replace this with something like pages.gitlab.com + server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + server_tokens off; ## Don't show the nginx version number, a security best practice + + return 301 https://$http_host$request_uri; + + access_log /var/log/nginx/gitlab_pages_access.log; + error_log /var/log/nginx/gitlab_pages_access.log; +} + +## Pages serving host +server { + listen 0.0.0.0:443 ssl; + listen [::]:443 ipv6only=on ssl; + + ## Replace this with something like pages.gitlab.com + server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + server_tokens off; ## Don't show the nginx version number, a security best practice + root /home/git/gitlab/shared/pages/${group}; + + ## Strong SSL Security + ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/ + ssl on; + ssl_certificate /etc/nginx/ssl/gitlab-pages.crt; + ssl_certificate_key /etc/nginx/ssl/gitlab-pages.key; + + # GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs + ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 5m; + + ## See app/controllers/application_controller.rb for headers set + + ## [Optional] If your certficate has OCSP, enable OCSP stapling to reduce the overhead and latency of running SSL. + ## Replace with your ssl_trusted_certificate. For more info see: + ## - https://medium.com/devops-programming/4445f4862461 + ## - https://www.ruby-forum.com/topic/4419319 + ## - https://www.digitalocean.com/community/tutorials/how-to-configure-ocsp-stapling-on-apache-and-nginx + # ssl_stapling on; + # ssl_stapling_verify on; + # ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt; + # resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired + # resolver_timeout 5s; + + ## [Optional] Generate a stronger DHE parameter: + ## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096 + ## + # ssl_dhparam /etc/ssl/certs/dhparam.pem; + + ## Individual nginx logs for GitLab pages + access_log /var/log/nginx/gitlab_pages_access.log; + error_log /var/log/nginx/gitlab_pages_error.log; + + # 1. Try to get /project/ to => shared/pages/${group}/public/ or index.html + # 2. Try to get / to => shared/pages/${group}/${host}/public/ or index.html + location ~ ^/([^/]*)(/.*)?$ { + try_files "/$1/public$2" + "/$1/public$2/index.html" + "/${host}/public/${uri}" + "/${host}/public/${uri}/index.html" + =404; + } + + # Define custom error pages + error_page 403 /403.html; + error_page 404 /404.html; +} From 324fe12a125b1a766978556d7d8f2b8bb6c22e43 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 18 Dec 2015 16:40:59 +0100 Subject: [PATCH 034/183] Fix pages path settings option. --- config/initializers/1_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index e52171f0d64..860cafad325 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -267,7 +267,7 @@ Settings.registry['path'] = File.expand_path(Settings.registry['path' # Settings['pages'] ||= Settingslogic.new({}) Settings.pages['enabled'] = false if Settings.pages['enabled'].nil? -Settings.pages['path'] ||= File.expand_path('shared/pages/', Rails.root) +Settings.pages['path'] = File.expand_path(Settings.pages['path'] || File.join(Settings.shared['path'], "pages"), Rails.root) Settings.pages['host'] ||= "example.com" Settings.pages['https'] = false if Settings.pages['https'].nil? Settings.pages['port'] ||= Settings.pages.https ? 443 : 80 From 9c78a206ce2039cdba095c2631538f9e50c28f95 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 Dec 2015 18:08:41 +0200 Subject: [PATCH 035/183] Typo fixes, remove unnecessary information about pages path [ci skip] --- doc/pages/README.md | 15 +++------------ doc/pages/administration.md | 4 ++-- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 552b3436d77..c83fdb63e53 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -4,7 +4,7 @@ _**Note:** This feature was [introduced][ee-80] in GitLab EE 8.3_ With GitLab Pages you can host for free your static websites on GitLab. Combined with the power of GitLab CI and the help of GitLab Runner you can -deploy static pages for your individual projects your user or your group. +deploy static pages for your individual projects, your user or your group. ## Enable the pages feature in your GitLab EE instance @@ -101,19 +101,10 @@ in the artifacts. ## Frequently Asked Questions -**Q:** Where are my generated pages stored? - -**A:** All content is located by default under `shared/pages/` in the root -directory of the GitLab installation. To be exact, all specific projects under -a namespace are stored in `shared/pages/${namespace}/${project}/public/` and -all user/group pages in `shared/pages/${namespace}/${namespace}/public/`. - ---- - **Q:** Can I download my generated pages? -**A:** Sure. All you need is to download the artifacts archive from the build - page. +**A:** Sure. All you need to do is download the artifacts archive from the + build page. --- diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 9c24fee4f1a..6c5436842fe 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -30,8 +30,8 @@ host that GitLab runs. For example, an entry would look like this: where `gitlab.io` is the domain under which GitLab Pages will be served and `1.2.3.4` is the IP address of your GitLab instance. -It is strongly advised to **not** use the GitLab domain to serve user pages. -For more information see the [security section](#security). +You should not use the GitLab domain to serve user pages. For more information +see the [security section](#security). ### Omnibus package installations From c66b15803a5674a5b97968ae9479b2bd293ca34f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 18 Dec 2015 17:08:02 +0100 Subject: [PATCH 036/183] Fix confusing implementation detail in nginx config about how gitlab-pages work [ci skip] --- lib/support/nginx/gitlab-pages | 4 ++-- lib/support/nginx/gitlab-pages-ssl | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages index 33f573d0b5b..ed4f7e4316a 100644 --- a/lib/support/nginx/gitlab-pages +++ b/lib/support/nginx/gitlab-pages @@ -14,8 +14,8 @@ server { access_log /var/log/nginx/gitlab_pages_access.log; error_log /var/log/nginx/gitlab_pages_error.log; - # 1. Try to get /project/ to => shared/pages/${group}/public/ or index.html - # 2. Try to get / to => shared/pages/${group}/${host}/public/ or index.html + # 1. Try to get /path/ from shared/pages/${group}/${path}/public/ + # 2. Try to get / from shared/pages/${group}/${host}/public/ location ~ ^/([^/]*)(/.*)?$ { try_files "/$1/public$2" "/$1/public$2/index.html" diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl index 006610262f9..dcbbee4042a 100644 --- a/lib/support/nginx/gitlab-pages-ssl +++ b/lib/support/nginx/gitlab-pages-ssl @@ -53,8 +53,6 @@ server { # ssl_stapling on; # ssl_stapling_verify on; # ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt; - # resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired - # resolver_timeout 5s; ## [Optional] Generate a stronger DHE parameter: ## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096 @@ -65,8 +63,8 @@ server { access_log /var/log/nginx/gitlab_pages_access.log; error_log /var/log/nginx/gitlab_pages_error.log; - # 1. Try to get /project/ to => shared/pages/${group}/public/ or index.html - # 2. Try to get / to => shared/pages/${group}/${host}/public/ or index.html + # 1. Try to get /path/ from shared/pages/${group}/${path}/public/ + # 2. Try to get / from shared/pages/${group}/${host}/public/ location ~ ^/([^/]*)(/.*)?$ { try_files "/$1/public$2" "/$1/public$2/index.html" From 0bb480dcc738889e56397a7c05950ce8d73caedf Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 22 Dec 2015 14:24:54 +0200 Subject: [PATCH 037/183] Add note about shared runners [ci skip] --- doc/pages/administration.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 6c5436842fe..529a1450fd3 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -14,6 +14,9 @@ GitLab EE instance. will be served 1. Pages use a separate Nginx configuration file which needs to be explicitly added in the server under which GitLab EE runs +1. Optionally but recommended, you can add some + [shared runners](../ci/runners/README.md) so that your users don't have to + bring their own. Both of these settings are described in detail in the sections below. From 8aba28e14dd7c66b17e0bc4a4230d0f5586155d0 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 22 Dec 2015 14:26:06 +0200 Subject: [PATCH 038/183] Clarify some things in Pages [ci skip] * Pages are enabled by default on each project * Add note about using the `only` parameter --- doc/pages/README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index c83fdb63e53..ea975ec8149 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -17,13 +17,14 @@ The steps that are performed from the initialization of a project to the creation of the static content, can be summed up to: 1. Create project (its name could be specific according to the case) -1. Enable the GitLab Pages feature under the project's settings 1. Provide a specific job in `.gitlab-ci.yml` 1. GitLab Runner builds the project 1. GitLab CI uploads the artifacts 1. Nginx serves the content As a user, you should normally be concerned only with the first three items. +If [shared runners](../ci/runners/README.md) are enabled by your GitLab +administrator, you should be able to use them instead of bringing your own. In general there are four kinds of pages one might create. This is better explained with an example so let's make some assumptions. @@ -68,6 +69,13 @@ To make use of GitLab Pages, your `.gitlab-ci.yml` must follow the rules below: 1. Any static content must be placed under a `public/` directory 1. `artifacts` with a path to the `public/` directory must be defined +Be aware that Pages are by default branch/tag agnostic and their deployment +relies solely on what you specify in `gitlab-ci.yml`. If you don't limit the +`pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), +whenever a new commit is pushed to whatever branch or tag, the Pages will be +overwritten. In the examples below, we limit the Pages to be deployed whenever +a commit is pushed only on the `master` branch, which is advisable to do so. + The pages are created after the build completes successfully and the artifacts for the `pages` job are uploaded to GitLab. @@ -84,6 +92,8 @@ pages: artifacts: paths: - public + only: + - master ``` ## Example projects @@ -101,10 +111,9 @@ in the artifacts. ## Frequently Asked Questions -**Q:** Can I download my generated pages? +**Q: Can I download my generated pages?** -**A:** Sure. All you need to do is download the artifacts archive from the - build page. +Sure. All you need to do is download the artifacts archive from the build page. --- From a7fa7f269a3a6b7562880d2a780013cbab0a5110 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 22 Dec 2015 16:57:22 +0200 Subject: [PATCH 039/183] Add missing dot in .gitlab-ci.yml [ci skip] --- doc/pages/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index ea975ec8149..f6eb8ccb7a7 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -70,7 +70,7 @@ To make use of GitLab Pages, your `.gitlab-ci.yml` must follow the rules below: 1. `artifacts` with a path to the `public/` directory must be defined Be aware that Pages are by default branch/tag agnostic and their deployment -relies solely on what you specify in `gitlab-ci.yml`. If you don't limit the +relies solely on what you specify in `.gitlab-ci.yml`. If you don't limit the `pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), whenever a new commit is pushed to whatever branch or tag, the Pages will be overwritten. In the examples below, we limit the Pages to be deployed whenever From 1d159ffbf807e3853f45faa1e7075b9a6546953f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 22 Dec 2015 18:07:14 +0000 Subject: [PATCH 040/183] Fix URL to GitLab pages documentation --- app/views/projects/edit.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index f9c6809b903..f4c1db1b93d 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -147,7 +147,7 @@ - else %p Learn how to upload your static site and have it served by - GitLab by following the #{link_to "documentation on GitLab Pages", "http://doc.gitlab.com/pages/README.html", target: :blank}. + GitLab by following the #{link_to "documentation on GitLab Pages", "http://doc.gitlab.com/ee/pages/README.html", target: :blank}. %p In the example below we define a special job named %code pages From 6e70870a2e80ea092f8528f727753184eb3265fb Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 15 Jan 2016 12:21:52 +0100 Subject: [PATCH 041/183] Move most of PagesWorker logic UpdatePagesService --- app/models/ci/build.rb | 2 +- ...date_pages_service.rb => pages_service.rb} | 2 +- app/services/projects/update_pages_service.rb | 132 +++++++++++++++++ app/workers/pages_worker.rb | 133 +----------------- ..._service_spec.rb => pages_service_spec.rb} | 4 +- .../projects/update_pages_worker_spec.rb} | 23 +-- 6 files changed, 152 insertions(+), 144 deletions(-) rename app/services/{update_pages_service.rb => pages_service.rb} (92%) create mode 100644 app/services/projects/update_pages_service.rb rename spec/services/{update_pages_service_spec.rb => pages_service_spec.rb} (91%) rename spec/{workers/pages_worker_spec.rb => services/projects/update_pages_worker_spec.rb} (80%) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 095a346f337..da8e66e5f6e 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -457,7 +457,7 @@ module Ci build_data = Gitlab::DataBuilder::Build.build(self) project.execute_hooks(build_data.dup, :build_hooks) project.execute_services(build_data.dup, :build_hooks) - UpdatePagesService.new(build_data).execute + PagesService.new(build_data).execute project.running_or_pending_build_count(force: true) end diff --git a/app/services/update_pages_service.rb b/app/services/pages_service.rb similarity index 92% rename from app/services/update_pages_service.rb rename to app/services/pages_service.rb index 39f08b2a03d..446eeb34d3b 100644 --- a/app/services/update_pages_service.rb +++ b/app/services/pages_service.rb @@ -1,4 +1,4 @@ -class UpdatePagesService +class PagesService attr_reader :data def initialize(data) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb new file mode 100644 index 00000000000..e1bb4c92e40 --- /dev/null +++ b/app/services/projects/update_pages_service.rb @@ -0,0 +1,132 @@ +module Projects + class UpdatePagesService < BaseService + BLOCK_SIZE = 32.kilobytes + MAX_SIZE = 1.terabyte + + attr_reader :build + + def initialize(project, build) + @project, @build = project, build + end + + def execute + # Create status notifying the deployment of pages + @status = create_status + @status.run! + + raise 'missing pages artifacts' unless build.artifacts_file? + raise 'pages are outdated' unless latest? + + # Create temporary directory in which we will extract the artifacts + FileUtils.mkdir_p(tmp_path) + Dir.mktmpdir(nil, tmp_path) do |archive_path| + extract_archive!(archive_path) + + # Check if we did extract public directory + archive_public_path = File.join(archive_path, 'public') + raise 'pages miss the public folder' unless Dir.exists?(archive_public_path) + raise 'pages are outdated' unless latest? + + deploy_page!(archive_public_path) + success + end + rescue => e + error(e.message) + end + + private + + def success + @status.success + super + end + + def error(message, http_status = nil) + @status.allow_failure = !latest? + @status.description = message + @status.drop + super + end + + def create_status + GenericCommitStatus.new( + project: project, + commit: build.commit, + user: build.user, + ref: build.ref, + stage: 'deploy', + name: 'pages:deploy' + ) + end + + def extract_archive!(temp_path) + results = Open3.pipeline(%W(gunzip -c #{artifacts}), + %W(dd bs=#{BLOCK_SIZE} count=#{blocks}), + %W(tar -x -C #{temp_path} public/), + err: '/dev/null') + raise 'pages failed to extract' unless results.compact.all?(&:success?) + end + + def deploy_page!(archive_public_path) + # Do atomic move of pages + # Move and removal may not be atomic, but they are significantly faster then extracting and removal + # 1. We move deployed public to previous public path (file removal is slow) + # 2. We move temporary public to be deployed public + # 3. We remove previous public path + FileUtils.mkdir_p(pages_path) + begin + FileUtils.move(public_path, previous_public_path) + rescue + end + FileUtils.move(archive_public_path, public_path) + ensure + FileUtils.rm_r(previous_public_path, force: true) + end + + def latest? + # check if sha for the ref is still the most recent one + # this helps in case when multiple deployments happens + sha == latest_sha + end + + def blocks + # Calculate dd parameters: we limit the size of pages + max_size = current_application_settings.max_pages_size.megabytes + max_size ||= MAX_SIZE + blocks = 1 + max_size / BLOCK_SIZE + blocks + end + + def tmp_path + @tmp_path ||= File.join(Settings.pages.path, 'tmp') + end + + def pages_path + @pages_path ||= project.pages_path + end + + def public_path + @public_path ||= File.join(pages_path, 'public') + end + + def previous_public_path + @previous_public_path ||= File.join(pages_path, "public.#{SecureRandom.hex}") + end + + def ref + build.ref + end + + def artifacts + build.artifacts_file.path + end + + def latest_sha + project.commit(build.ref).try(:sha).to_s + end + + def sha + build.sha + end + end +end diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index ff765a6c13c..8c99e8dbe76 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -1,9 +1,5 @@ class PagesWorker include Sidekiq::Worker - include Gitlab::CurrentSettings - - BLOCK_SIZE = 32.kilobytes - MAX_SIZE = 1.terabyte sidekiq_options queue: :pages, retry: false @@ -12,137 +8,12 @@ class PagesWorker end def deploy(build_id) - @build_id = build_id - return unless valid? - - # Create status notifying the deployment of pages - @status = create_status - @status.run! - - raise 'pages are outdated' unless latest? - - # Create temporary directory in which we will extract the artifacts - FileUtils.mkdir_p(tmp_path) - Dir.mktmpdir(nil, tmp_path) do |archive_path| - extract_archive!(archive_path) - - # Check if we did extract public directory - archive_public_path = File.join(archive_path, 'public') - raise 'pages miss the public folder' unless Dir.exists?(archive_public_path) - raise 'pages are outdated' unless latest? - - deploy_page!(archive_public_path) - - @status.success - end - rescue => e - fail(e.message, !latest?) - return false + build = Ci::Build.find_by(id: build_id) + Projects::UpdatePagesService.new(build.project, build).execute end def remove(namespace_path, project_path) full_path = File.join(Settings.pages.path, namespace_path, project_path) FileUtils.rm_r(full_path, force: true) end - - private - - def create_status - GenericCommitStatus.new( - project: project, - commit: build.commit, - user: build.user, - ref: build.ref, - stage: 'deploy', - name: 'pages:deploy' - ) - end - - def extract_archive!(temp_path) - results = Open3.pipeline(%W(gunzip -c #{artifacts}), - %W(dd bs=#{BLOCK_SIZE} count=#{blocks}), - %W(tar -x -C #{temp_path} public/), - err: '/dev/null') - raise 'pages failed to extract' unless results.compact.all?(&:success?) - end - - def deploy_page!(archive_public_path) - # Do atomic move of pages - # Move and removal may not be atomic, but they are significantly faster then extracting and removal - # 1. We move deployed public to previous public path (file removal is slow) - # 2. We move temporary public to be deployed public - # 3. We remove previous public path - FileUtils.mkdir_p(pages_path) - begin - FileUtils.move(public_path, previous_public_path) - rescue - end - FileUtils.move(archive_public_path, public_path) - ensure - FileUtils.rm_r(previous_public_path, force: true) - end - - def fail(message, allow_failure = true) - @status.allow_failure = allow_failure - @status.description = message - @status.drop - end - - def valid? - build && build.artifacts_file? - end - - def latest? - # check if sha for the ref is still the most recent one - # this helps in case when multiple deployments happens - sha == latest_sha - end - - def blocks - # Calculate dd parameters: we limit the size of pages - max_size = current_application_settings.max_pages_size.megabytes - max_size ||= MAX_SIZE - blocks = 1 + max_size / BLOCK_SIZE - blocks - end - - def build - @build ||= Ci::Build.find_by(id: @build_id) - end - - def project - @project ||= build.project - end - - def tmp_path - @tmp_path ||= File.join(Settings.pages.path, 'tmp') - end - - def pages_path - @pages_path ||= project.pages_path - end - - def public_path - @public_path ||= File.join(pages_path, 'public') - end - - def previous_public_path - @previous_public_path ||= File.join(pages_path, "public.#{SecureRandom.hex}") - end - - def ref - build.ref - end - - def artifacts - build.artifacts_file.path - end - - def latest_sha - project.commit(build.ref).try(:sha).to_s - end - - def sha - build.sha - end end diff --git a/spec/services/update_pages_service_spec.rb b/spec/services/pages_service_spec.rb similarity index 91% rename from spec/services/update_pages_service_spec.rb rename to spec/services/pages_service_spec.rb index cf1ca15da44..e6ad93358a0 100644 --- a/spec/services/update_pages_service_spec.rb +++ b/spec/services/pages_service_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' -describe UpdatePagesService, services: true do +describe PagesService, services: true do let(:build) { create(:ci_build) } let(:data) { Gitlab::BuildDataBuilder.build(build) } - let(:service) { UpdatePagesService.new(data) } + let(:service) { PagesService.new(data) } before do allow(Gitlab.config.pages).to receive(:enabled).and_return(true) diff --git a/spec/workers/pages_worker_spec.rb b/spec/services/projects/update_pages_worker_spec.rb similarity index 80% rename from spec/workers/pages_worker_spec.rb rename to spec/services/projects/update_pages_worker_spec.rb index 85592154598..0607c025b9e 100644 --- a/spec/workers/pages_worker_spec.rb +++ b/spec/services/projects/update_pages_worker_spec.rb @@ -1,13 +1,14 @@ require "spec_helper" -describe PagesWorker do +describe Projects::UpdatePagesService do let(:project) { create :project } let(:commit) { create :ci_commit, project: project, sha: project.commit('HEAD').sha } let(:build) { create :ci_build, commit: commit, ref: 'HEAD' } - let(:worker) { PagesWorker.new } let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/pages.tar.gz', 'application/octet-stream') } let(:empty_file) { fixture_file_upload(Rails.root + 'spec/fixtures/pages_empty.tar.gz', 'application/octet-stream') } let(:invalid_file) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'application/octet-stream') } + + subject { described_class.new(project, build) } before do project.remove_pages @@ -18,19 +19,19 @@ describe PagesWorker do it 'succeeds' do expect(project.pages_url).to be_nil - expect(worker.deploy(build.id)).to be_truthy + expect(execute).to eq(:success) expect(project.pages_url).to_not be_nil end it 'limits pages size' do stub_application_setting(max_pages_size: 1) - expect(worker.deploy(build.id)).to_not be_truthy + expect(execute).to_not eq(:success) end it 'removes pages after destroy' do expect(PagesWorker).to receive(:perform_in) expect(project.pages_url).to be_nil - expect(worker.deploy(build.id)).to be_truthy + expect(execute).to eq(:success) expect(project.pages_url).to_not be_nil project.destroy expect(Dir.exist?(project.public_pages_path)).to be_falsey @@ -44,22 +45,26 @@ describe PagesWorker do end it 'fails if no artifacts' do - expect(worker.deploy(build.id)).to_not be_truthy + expect(execute).to_not eq(:success) end it 'fails for empty file fails' do build.update_attributes(artifacts_file: empty_file) - expect(worker.deploy(build.id)).to_not be_truthy + expect(execute).to_not eq(:success) end it 'fails for invalid archive' do build.update_attributes(artifacts_file: invalid_file) - expect(worker.deploy(build.id)).to_not be_truthy + expect(execute).to_not eq(:success) end it 'fails if sha on branch is not latest' do commit.update_attributes(sha: 'old_sha') build.update_attributes(artifacts_file: file) - expect(worker.deploy(build.id)).to_not be_truthy + expect(execute).to_not eq(:success) + end + + def execute + subject.execute[:status] end end From c4c8ca04052aaf7d37c2335066381b536df68427 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 20 Jan 2016 21:49:26 +0100 Subject: [PATCH 042/183] Added support for zip archives in pages The ZIP archive size is calculated from artifacts metadata that should get uploaded for new artifacts --- app/services/projects/update_pages_service.rb | 41 ++++++++-- spec/fixtures/pages.zip | Bin 0 -> 1851 bytes spec/fixtures/pages.zip.meta | Bin 0 -> 225 bytes spec/fixtures/pages_empty.zip | Bin 0 -> 160 bytes spec/fixtures/pages_empty.zip.meta | Bin 0 -> 116 bytes .../projects/update_pages_worker_spec.rb | 74 ++++++++++-------- 6 files changed, 78 insertions(+), 37 deletions(-) create mode 100644 spec/fixtures/pages.zip create mode 100644 spec/fixtures/pages.zip.meta create mode 100644 spec/fixtures/pages_empty.zip create mode 100644 spec/fixtures/pages_empty.zip.meta diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index e1bb4c92e40..ceabd29fd52 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -2,6 +2,7 @@ module Projects class UpdatePagesService < BaseService BLOCK_SIZE = 32.kilobytes MAX_SIZE = 1.terabyte + SITE_PATH = 'public/' attr_reader :build @@ -60,13 +61,42 @@ module Projects end def extract_archive!(temp_path) + if artifacts.ends_with?('.tar.gz') || artifacts.ends_with?('.tgz') + extract_tar_archive!(temp_path) + elsif artifacts.ends_with?('.zip') + extract_zip_archive!(temp_path) + else + raise 'unsupported artifacts format' + end + end + + def extract_tar_archive!(temp_path) results = Open3.pipeline(%W(gunzip -c #{artifacts}), %W(dd bs=#{BLOCK_SIZE} count=#{blocks}), - %W(tar -x -C #{temp_path} public/), + %W(tar -x -C #{temp_path} #{SITE_PATH}), err: '/dev/null') raise 'pages failed to extract' unless results.compact.all?(&:success?) end + def extract_zip_archive!(temp_path) + raise 'missing artifacts metadata' unless build.artifacts_metadata? + + # Calculate page size after extract + public_entry = build.artifacts_metadata_entry(SITE_PATH, recursive: true) + + if public_entry.total_size > max_size + raise "artifacts for pages are too large: #{total_size}" + end + + # Requires UnZip at least 6.00 Info-ZIP. + # -n never overwrite existing files + # We add * to end of SITE_PATH, because we want to extract SITE_PATH and all subdirectories + site_path = File.join(SITE_PATH, '*') + unless system(*%W(unzip -n #{artifacts} #{site_path} -d #{temp_path})) + raise 'pages failed to extract' + end + end + def deploy_page!(archive_public_path) # Do atomic move of pages # Move and removal may not be atomic, but they are significantly faster then extracting and removal @@ -91,10 +121,11 @@ module Projects def blocks # Calculate dd parameters: we limit the size of pages - max_size = current_application_settings.max_pages_size.megabytes - max_size ||= MAX_SIZE - blocks = 1 + max_size / BLOCK_SIZE - blocks + 1 + max_size / BLOCK_SIZE + end + + def max_size + current_application_settings.max_pages_size.megabytes || MAX_SIZE end def tmp_path diff --git a/spec/fixtures/pages.zip b/spec/fixtures/pages.zip new file mode 100644 index 0000000000000000000000000000000000000000..9558fcd4b9428385e15ae386f473434d56d3da3f GIT binary patch literal 1851 zcmWIWW@h1H0D;mJeGf1LO0Wazg3_d%%w+ulpdt|C!Z z5sEVNQc^4QGD>oDe1p7K$r$q7c`jmg;DY!GtvxB?P6`wD9=mpi<4^UZtIz#+RvW!{ ze9X2}Cv@tobn}%<4lfRRw^Vwk-y^NhPAko)o20f0rpvzayghfxi#sp;e@vKmH8w-U zFUcme?67;0_!ke&RmIABzvN=fe~Ic&yrwu!wBc0Zt0PmUYJ0A@K5wPVtcTLI?|p^d zT17U@5k4_*>w3MV4E1wXO79As=$9+1w?y)VlH8n`4l+wteha$x{>7$?vo~JWYh9wb z#C(<4DNV7oEbm#19UsJhTzOr?VA&V#mbI^g@1Oi5VJ4Sj)cf*6{pB`wzsB$P{ce^# z_Hf_0QoO3&$1Ab#&=f1XC4O7xR47gVpIm0f%9HntCAIp?G^S)m7s+{#ub3sOTO=jF z?pwIV^YPJdE;havb9Jiv4@v)>ak^OT`NB&M`+lFk8C!0s(Im$cr<5;L{&Z`}Ndq~b z$y~oqM~Nm*ox$?-kENmX-)(%w{09r3I`SVmdPL-;R?iJt!Gh<^MV84;x9Xm~p8S`m z_k0K6hc1BQU-hniT%8&+~8qF-O5@2#kinXb6xQ0v{3rycwC~ zm~q!U5Q7C6-a3L<@OlWO33p8dQVJ@amNcp&G$HCGh@BAa*h)N*b`V(7_y=eT)J{F=)8-r#2RaLlag15 zV>++L{c4c@F^qsYB1+$D}&TFOr8P@ubxjiv+n6~Npc?sZl0b0Z-CMH|} bBE>o{upmTe&y?Q)00960N7SRnF#!Mo?;mgM literal 0 HcmV?d00001 diff --git a/spec/fixtures/pages_empty.zip b/spec/fixtures/pages_empty.zip new file mode 100644 index 0000000000000000000000000000000000000000..db3f0334c12d868859a979dc3f8fa3d8fa283813 GIT binary patch literal 160 zcmWIWW@h1H00F@?eGf1LO0Y7>Fu3{#ghYmha56AYx-%mTgi9;985miKkGgE$NTEA$na literal 0 HcmV?d00001 diff --git a/spec/fixtures/pages_empty.zip.meta b/spec/fixtures/pages_empty.zip.meta new file mode 100644 index 0000000000000000000000000000000000000000..d0b93b3b9c0fd3a1279532e9aa03b6f6915c2963 GIT binary patch literal 116 zcmV-)0E_=0iwFP!32ul0|6*WZP;<{L@kvZla4OBrNl|btD#=VsOfD%_@J%gAOi3(B zR4~vp&@ Date: Tue, 9 Feb 2016 18:06:55 +0100 Subject: [PATCH 043/183] Initial work on GitLab Pages update --- Gemfile | 3 + Gemfile.lock | 4 + app/controllers/projects/pages_controller.rb | 94 +++++++++++++++++++ app/controllers/projects_controller.rb | 10 -- app/helpers/projects_helper.rb | 8 ++ app/models/project.rb | 51 ++++++++-- app/policies/project_policy.rb | 1 + .../update_pages_configuration_service.rb | 53 +++++++++++ app/validators/certificate_key_validator.rb | 24 +++++ app/validators/certificate_validator.rb | 30 ++++++ .../layouts/nav/_project_settings.html.haml | 4 + app/views/projects/pages/_access.html.haml | 34 +++++++ app/views/projects/pages/_destroy.haml | 10 ++ app/views/projects/pages/_disabled.html.haml | 4 + app/views/projects/pages/_form.html.haml | 35 +++++++ .../pages/_remove_certificate.html.haml | 16 ++++ .../pages/_upload_certificate.html.haml | 32 +++++++ app/views/projects/pages/_use.html.haml | 18 ++++ app/views/projects/pages/show.html.haml | 18 ++++ app/workers/pages_worker.rb | 6 +- config/initializers/1_settings.rb | 1 + config/routes/project.rb | 5 +- ...808_add_pages_custom_domain_to_projects.rb | 10 ++ 23 files changed, 451 insertions(+), 20 deletions(-) create mode 100644 app/controllers/projects/pages_controller.rb create mode 100644 app/services/projects/update_pages_configuration_service.rb create mode 100644 app/validators/certificate_key_validator.rb create mode 100644 app/validators/certificate_validator.rb create mode 100644 app/views/projects/pages/_access.html.haml create mode 100644 app/views/projects/pages/_destroy.haml create mode 100644 app/views/projects/pages/_disabled.html.haml create mode 100644 app/views/projects/pages/_form.html.haml create mode 100644 app/views/projects/pages/_remove_certificate.html.haml create mode 100644 app/views/projects/pages/_upload_certificate.html.haml create mode 100644 app/views/projects/pages/_use.html.haml create mode 100644 app/views/projects/pages/show.html.haml create mode 100644 db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb diff --git a/Gemfile b/Gemfile index dd7c93c5a75..bc1b13c7331 100644 --- a/Gemfile +++ b/Gemfile @@ -48,6 +48,9 @@ gem 'rqrcode-rails3', '~> 0.1.7' gem 'attr_encrypted', '~> 3.0.0' gem 'u2f', '~> 0.2.1' +# GitLab Pages +gem 'validates_hostname', '~> 1.0.0' + # Browser detection gem 'browser', '~> 2.2' diff --git a/Gemfile.lock b/Gemfile.lock index 3b207d19d1f..6263b02b041 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -799,6 +799,9 @@ GEM get_process_mem (~> 0) unicorn (>= 4, < 6) uniform_notifier (1.10.0) + validates_hostname (1.0.5) + activerecord (>= 3.0) + activesupport (>= 3.0) version_sorter (2.1.0) virtus (1.0.5) axiom-types (~> 0.1) @@ -1014,6 +1017,7 @@ DEPENDENCIES unf (~> 0.1.4) unicorn (~> 5.1.0) unicorn-worker-killer (~> 0.4.4) + validates_hostname (~> 1.0.0) version_sorter (~> 2.1.0) virtus (~> 1.0.1) vmstat (~> 2.3.0) diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb new file mode 100644 index 00000000000..ef0ed505142 --- /dev/null +++ b/app/controllers/projects/pages_controller.rb @@ -0,0 +1,94 @@ +class Projects::PagesController < Projects::ApplicationController + layout 'project_settings' + + before_action :authorize_update_pages!, except: [:show] + before_action :authorize_remove_pages!, only: :destroy + + helper_method :valid_certificate?, :valid_certificate_key? + helper_method :valid_key_for_certificiate?, :valid_certificate_intermediates? + helper_method :certificate, :certificate_key + + def show + end + + def update + if @project.update_attributes(pages_params) + redirect_to namespace_project_pages_path(@project.namespace, @project) + else + render 'show' + end + end + + def certificate + @project.remove_pages_certificate + end + + def destroy + @project.remove_pages + + respond_to do |format| + format.html { redirect_to project_path(@project) } + end + end + + private + + def pages_params + params.require(:project).permit( + :pages_custom_certificate, + :pages_custom_certificate_key, + :pages_custom_domain, + :pages_redirect_http, + ) + end + + def valid_certificate? + certificate.present? + end + + def valid_certificate_key? + certificate_key.present? + end + + def valid_key_for_certificiate? + return false unless certificate + return false unless certificate_key + + certificate.verify(certificate_key) + rescue OpenSSL::X509::CertificateError + false + end + + def valid_certificate_intermediates? + return false unless certificate + + store = OpenSSL::X509::Store.new + store.set_default_paths + + # This forces to load all intermediate certificates stored in `pages_custom_certificate` + Tempfile.open('project_certificate') do |f| + f.write(@project.pages_custom_certificate) + f.flush + store.add_file(f.path) + end + + store.verify(certificate) + rescue OpenSSL::X509::StoreError + false + end + + def certificate + return unless @project.pages_custom_certificate + + @certificate ||= OpenSSL::X509::Certificate.new(@project.pages_custom_certificate) + rescue OpenSSL::X509::CertificateError + nil + end + + def certificate_key + return unless @project.pages_custom_certificate_key + @certificate_key ||= OpenSSL::PKey::RSA.new(@project.pages_custom_certificate_key) + rescue OpenSSL::PKey::PKeyError + nil + end +end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 123dc179e73..444ff837bb3 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -151,16 +151,6 @@ class ProjectsController < Projects::ApplicationController end end - def remove_pages - return access_denied! unless can?(current_user, :remove_pages, @project) - - @project.remove_pages - - respond_to do |format| - format.html { redirect_to project_path(@project) } - end - end - def housekeeping ::Projects::HousekeepingService.new(@project).execute diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index eb98204285d..63aa182502d 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -81,6 +81,14 @@ module ProjectsHelper "You are going to remove the fork relationship to source project #{@project.forked_from_project.name_with_namespace}. Are you ABSOLUTELY sure?" end + def remove_pages_message(project) + "You are going to remove the pages for #{project.name_with_namespace}.\n Are you ABSOLUTELY sure?" + end + + def remove_pages_certificate_message(project) + "You are going to remove a certificates for #{project.name_with_namespace}.\n Are you ABSOLUTELY sure?" + end + def project_nav_tabs @nav_tabs ||= get_project_nav_tabs(@project, current_user) end diff --git a/app/models/project.rb b/app/models/project.rb index 8a8aca44945..34618817fb6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -76,6 +76,8 @@ class Project < ActiveRecord::Base attr_accessor :new_default_branch attr_accessor :old_path_with_namespace + attr_encrypted :pages_custom_certificate_key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base + alias_attribute :title, :name # Relations @@ -205,6 +207,11 @@ class Project < ActiveRecord::Base presence: true, inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } } + validates :pages_custom_domain, hostname: true, allow_blank: true, allow_nil: true + validates_uniqueness_of :pages_custom_domain, allow_nil: true, allow_blank: true + validates :pages_custom_certificate, certificate: { intermediate: true } + validates :pages_custom_certificate_key, certificate_key: true + add_authentication_token_field :runners_token before_save :ensure_runners_token @@ -1164,16 +1171,27 @@ class Project < ActiveRecord::Base end def pages_url - if Dir.exist?(public_pages_path) - host = "#{namespace.path}.#{Settings.pages.host}" - url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix| - "#{prefix}#{namespace.path}." - end + return unless Dir.exist?(public_pages_path) - # If the project path is the same as host, leave the short version - return url if host == path + host = "#{namespace.path}.#{Settings.pages.host}" + url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix| + "#{prefix}#{namespace.path}." + end - "#{url}/#{path}" + # If the project path is the same as host, leave the short version + return url if host == path + + "#{url}/#{path}" + end + + def pages_custom_url + return unless pages_custom_domain + return unless Dir.exist?(public_pages_path) + + if Gitlab.config.pages.https + return "https://#{pages_custom_domain}" + else + return "http://#{pages_custom_domain}" end end @@ -1185,6 +1203,15 @@ class Project < ActiveRecord::Base File.join(pages_path, 'public') end + def remove_pages_certificate + update( + pages_custom_certificate: nil, + pages_custom_certificate_key: nil + ) + + UpdatePagesConfigurationService.new(self).execute + end + def remove_pages # 1. We rename pages to temporary directory # 2. We wait 5 minutes, due to NFS caching @@ -1194,6 +1221,14 @@ class Project < ActiveRecord::Base if Gitlab::PagesTransfer.new.rename_project(path, temp_path, namespace.path) PagesWorker.perform_in(5.minutes, :remove, namespace.path, temp_path) end + + update( + pages_custom_certificate: nil, + pages_custom_certificate_key: nil, + pages_custom_domain: nil + ) + + UpdatePagesConfigurationService.new(self).execute end def wiki diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 63bc639688d..ca5b39a001f 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -110,6 +110,7 @@ class ProjectPolicy < BasePolicy can! :admin_pipeline can! :admin_environment can! :admin_deployment + can! :update_pages end def public_access! diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb new file mode 100644 index 00000000000..be4c2fbef8c --- /dev/null +++ b/app/services/projects/update_pages_configuration_service.rb @@ -0,0 +1,53 @@ +module Projects + class UpdatePagesConfigurationService < BaseService + attr_reader :project + + def initialize(project) + @project = project + end + + def execute + update_file(pages_cname_file, project.pages_custom_domain) + update_file(pages_certificate_file, project.pages_custom_certificate) + update_file(pages_certificate_file_key, project.pages_custom_certificate_key) + reload_daemon + success + rescue => e + error(e.message) + end + + private + + def reload_daemon + # GitLab Pages daemon constantly watches for modification time of `pages.path` + # It reloads configuration when `pages.path` is modified + File.touch(Settings.pages.path) + end + + def pages_path + @pages_path ||= project.pages_path + end + + def pages_cname_file + File.join(pages_path, 'CNAME') + end + + def pages_certificate_file + File.join(pages_path, 'domain.crt') + end + + def pages_certificate_key_file + File.join(pages_path, 'domain.key') + end + + def update_file(file, data) + if data + File.open(file, 'w') do |file| + file.write(data) + end + else + File.rm_r(file) + end + end + end +end diff --git a/app/validators/certificate_key_validator.rb b/app/validators/certificate_key_validator.rb new file mode 100644 index 00000000000..3b5bd30db1a --- /dev/null +++ b/app/validators/certificate_key_validator.rb @@ -0,0 +1,24 @@ +# UrlValidator +# +# Custom validator for private keys. +# +# class Project < ActiveRecord::Base +# validates :certificate_key, certificate_key: true +# end +# +class CertificateKeyValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + unless valid_private_key_pem?(value) + record.errors.add(attribute, "must be a valid PEM private key") + end + end + + private + + def valid_private_key_pem?(value) + pkey = OpenSSL::PKey::RSA.new(value) + pkey.private? + rescue OpenSSL::PKey::PKeyError + false + end +end diff --git a/app/validators/certificate_validator.rb b/app/validators/certificate_validator.rb new file mode 100644 index 00000000000..2cba5a435b7 --- /dev/null +++ b/app/validators/certificate_validator.rb @@ -0,0 +1,30 @@ +# UrlValidator +# +# Custom validator for private keys. +# +# class Project < ActiveRecord::Base +# validates :certificate_key, certificate_key: true +# end +# +class CertificateValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + certificate = parse_certificate(value) + unless certificate + record.errors.add(attribute, "must be a valid PEM certificate") + end + + if options[:intermediates] + unless certificate + record.errors.add(attribute, "certificate verification failed: missing intermediate certificates") + end + end + end + + private + + def parse_certificate(value) + OpenSSL::X509::Certificate.new(value) + rescue OpenSSL::X509::CertificateError + nil + end +end diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index c6df66d2c3c..d6c158b6de3 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -34,3 +34,7 @@ = link_to namespace_project_pipelines_settings_path(@project.namespace, @project), title: 'CI/CD Pipelines' do %span CI/CD Pipelines + = nav_link(controller: :pages) do + = link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages', data: {placement: 'right'} do + %span + Pages diff --git a/app/views/projects/pages/_access.html.haml b/app/views/projects/pages/_access.html.haml new file mode 100644 index 00000000000..d64f99fd22b --- /dev/null +++ b/app/views/projects/pages/_access.html.haml @@ -0,0 +1,34 @@ +- if @project.pages_url + .panel.panel-default + .panel-heading + Access pages + .panel-body + %p + %strong + Congratulations! Your pages are served at: + %p= link_to @project.pages_url, @project.pages_url + + - if Settings.pages.custom_domain && @project.pages_custom_url + %p= link_to @project.pages_custom_url, @project.pages_custom_url + + - if @project.pages_custom_certificate + - unless valid_certificate? + #error_explanation + .alert.alert-warning + Your certificate is invalid. + + - unless valid_certificate_key? + #error_explanation + .alert.alert-warning + Your private key is invalid. + + - unless valid_key_for_certificiate? + #error_explanation + .alert.alert-warning + Your private key can't be used with your certificate. + + - unless valid_certificate_intermediates? + #error_explanation + .alert.alert-warning + Your certificate doesn't have intermediates. + Your page may not work properly. diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml new file mode 100644 index 00000000000..61b995a5934 --- /dev/null +++ b/app/views/projects/pages/_destroy.haml @@ -0,0 +1,10 @@ +- if can?(current_user, :remove_pages, @project) && @project.pages_url + .panel.panel-default.panel.panel-danger + .panel-heading Remove pages + .errors-holder + .panel-body + = form_tag(namespace_project_pages_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do + %p + Removing the pages will prevent from exposing them to outside world. + .form-actions + = button_to 'Remove pages', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_pages_message(@project) } diff --git a/app/views/projects/pages/_disabled.html.haml b/app/views/projects/pages/_disabled.html.haml new file mode 100644 index 00000000000..cf9ef5b4d6f --- /dev/null +++ b/app/views/projects/pages/_disabled.html.haml @@ -0,0 +1,4 @@ +.panel.panel-default + .nothing-here-block + GitLab Pages is disabled. + Ask your system's administrator to enable it. diff --git a/app/views/projects/pages/_form.html.haml b/app/views/projects/pages/_form.html.haml new file mode 100644 index 00000000000..a7b03d552db --- /dev/null +++ b/app/views/projects/pages/_form.html.haml @@ -0,0 +1,35 @@ +- if can?(current_user, :update_pages, @project) + .panel.panel-default + .panel-heading + Settings + .panel-body + = form_for [@project], url: namespace_project_pages_path(@project.namespace, @project), html: { class: 'form-horizontal fieldset-form' } do |f| + - if @project.errors.any? + #error_explanation + .alert.alert-danger + - @project.errors.full_messages.each do |msg| + %p= msg + + .form-group + = f.label :pages_domain, class: 'control-label' do + Custom domain + .col-sm-10 + - if Settings.pages.custom_domain + = f.text_field :pages_custom_domain, required: false, autocomplete: 'off', class: 'form-control' + %span.help-inline Allows you to serve the pages under your domain + - else + .nothing-here-block + Support for custom domains and certificates is disabled. + Ask your system's administrator to enable it. + + - if Settings.pages.https + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :pages_redirect_http do + = f.check_box :pages_redirect_http + %span.descr Force HTTPS + .help-block Redirect the HTTP to HTTPS forcing to always use the secure connection + + .form-actions + = f.submit 'Save changes', class: "btn btn-save" diff --git a/app/views/projects/pages/_remove_certificate.html.haml b/app/views/projects/pages/_remove_certificate.html.haml new file mode 100644 index 00000000000..e8c0d03adfa --- /dev/null +++ b/app/views/projects/pages/_remove_certificate.html.haml @@ -0,0 +1,16 @@ +- if can?(current_user, :update_pages, @project) && @project.pages_custom_certificate + .panel.panel-default.panel.panel-danger + .panel-heading + Remove certificate + .errors-holder + .panel-body + = form_tag(certificates_namespace_project_pages_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do + %p + Removing the certificate will stop serving the page under HTTPS. + - if certificate + %p + %pre + = certificate.to_text + + .form-actions + = button_to 'Remove certificate', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_pages_certificate_message(@project) } diff --git a/app/views/projects/pages/_upload_certificate.html.haml b/app/views/projects/pages/_upload_certificate.html.haml new file mode 100644 index 00000000000..30873fcf395 --- /dev/null +++ b/app/views/projects/pages/_upload_certificate.html.haml @@ -0,0 +1,32 @@ +- if can?(current_user, :update_pages, @project) && Settings.pages.https && Settings.pages.custom_domain + .panel.panel-default + .panel-heading + Certificate + .panel-body + %p + Allows you to upload your certificate which will be used to serve pages under your domain. + %br + + = form_for [@project], url: namespace_project_pages_path(@project.namespace, @project), html: { class: 'form-horizontal fieldset-form' } do |f| + - if @project.errors.any? + #error_explanation + .alert.alert-danger + - @project.errors.full_messages.each do |msg| + %p= msg + + .form-group + = f.label :pages_custom_certificate, class: 'control-label' do + Certificate (PEM) + .col-sm-10 + = f.text_area :pages_custom_certificate, required: true, rows: 5, class: 'form-control', value: '' + %span.help-inline Upload a certificate for your domain with all intermediates + + .form-group + = f.label :pages_custom_certificate_key, class: 'control-label' do + Key (PEM) + .col-sm-10 + = f.text_area :pages_custom_certificate_key, required: true, rows: 5, class: 'form-control', value: '' + %span.help-inline Upload a certificate for your domain with all intermediates + + .form-actions + = f.submit 'Update certificate', class: "btn btn-save" diff --git a/app/views/projects/pages/_use.html.haml b/app/views/projects/pages/_use.html.haml new file mode 100644 index 00000000000..5542bbe670b --- /dev/null +++ b/app/views/projects/pages/_use.html.haml @@ -0,0 +1,18 @@ +- unless @project.pages_url + .panel.panel-info + .panel-heading + Configure pages + .panel-body + %p + Learn how to upload your static site and have it served by + GitLab by following the #{link_to "documentation on GitLab Pages", "http://doc.gitlab.com/ee/pages/README.html", target: :blank}. + %p + In the example below we define a special job named + %code pages + which is using Jekyll to build a static site. The generated + HTML will be stored in the + %code public/ + directory which will then be archived and uploaded to GitLab. + The name of the directory should not be different than + %code public/ + in order for the pages to work. diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml new file mode 100644 index 00000000000..5f689800da8 --- /dev/null +++ b/app/views/projects/pages/show.html.haml @@ -0,0 +1,18 @@ +- page_title "Pages" +%h3.page_title Pages +%p.light + With GitLab Pages you can host for free your static websites on GitLab. + Combined with the power of GitLab CI and the help of GitLab Runner + you can deploy static pages for your individual projects, your user or your group. +%hr + +- if Settings.pages.enabled + = render 'access' + = render 'use' + - if @project.pages_url + = render 'form' + = render 'upload_certificate' + = render 'remove_certificate' + = render 'destroy' +- else + = render 'disabled' diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index 8c99e8dbe76..4eeb9666bb0 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -9,7 +9,11 @@ class PagesWorker def deploy(build_id) build = Ci::Build.find_by(id: build_id) - Projects::UpdatePagesService.new(build.project, build).execute + result = Projects::UpdatePagesService.new(build.project, build).execute + if result[:status] == :success + result = Projects::UpdatePagesConfigurationService.new(build.project).execute + end + result end def remove(namespace_path, project_path) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 860cafad325..239aa662d9f 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -273,6 +273,7 @@ Settings.pages['https'] = false if Settings.pages['https'].nil? Settings.pages['port'] ||= Settings.pages.https ? 443 : 80 Settings.pages['protocol'] ||= Settings.pages.https ? "https" : "http" Settings.pages['url'] ||= Settings.send(:build_pages_url) +Settings.pages['custom_domain'] ||= false if Settings.pages['custom_domain'].nil? # # Git LFS diff --git a/config/routes/project.rb b/config/routes/project.rb index cd56f6281f5..956a2d3186f 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -39,6 +39,10 @@ constraints(ProjectUrlConstrainer.new) do end end + resource :pages, only: [:show, :update, :destroy] do + delete :certificates + end + resources :compare, only: [:index, :create] do collection do get :diff_for_path @@ -329,7 +333,6 @@ constraints(ProjectUrlConstrainer.new) do post :archive post :unarchive post :housekeeping - post :remove_pages post :toggle_star post :preview_markdown post :export diff --git a/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb b/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb new file mode 100644 index 00000000000..6472199fc4a --- /dev/null +++ b/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb @@ -0,0 +1,10 @@ +class AddPagesCustomDomainToProjects < ActiveRecord::Migration + def change + add_column :projects, :pages_custom_certificate, :text + add_column :projects, :pages_custom_certificate_key, :text + add_column :projects, :pages_custom_certificate_key_iv, :string + add_column :projects, :pages_custom_certificate_key_salt, :string + add_column :projects, :pages_custom_domain, :string, unique: true + add_column :projects, :pages_redirect_http, :boolean, default: false, null: false + end +end From 930a7030b5a0080128b2fe3e2b9506717c54a6a5 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 9 Feb 2016 19:04:39 +0100 Subject: [PATCH 044/183] Implement proper verification of certificate's public_key against the private_key --- app/controllers/projects/pages_controller.rb | 5 ++- app/models/project.rb | 8 ++-- app/validators/certificate_key_validator.rb | 1 + app/validators/certificate_validator.rb | 14 ++----- app/views/projects/edit.html.haml | 41 ------------------- app/views/projects/pages/_use.html.haml | 10 ----- ...808_add_pages_custom_domain_to_projects.rb | 6 +-- 7 files changed, 15 insertions(+), 70 deletions(-) diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index ef0ed505142..359544472e9 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -54,8 +54,9 @@ class Projects::PagesController < Projects::ApplicationController return false unless certificate return false unless certificate_key - certificate.verify(certificate_key) - rescue OpenSSL::X509::CertificateError + # We compare the public key stored in certificate with public key from certificate key + certificate.public_key.to_pem == certificate_key.public_key.to_pem + rescue OpenSSL::X509::CertificateError, OpenSSL::PKey::PKeyError false end diff --git a/app/models/project.rb b/app/models/project.rb index 34618817fb6..f447c2bf293 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -76,8 +76,6 @@ class Project < ActiveRecord::Base attr_accessor :new_default_branch attr_accessor :old_path_with_namespace - attr_encrypted :pages_custom_certificate_key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base - alias_attribute :title, :name # Relations @@ -209,14 +207,16 @@ class Project < ActiveRecord::Base validates :pages_custom_domain, hostname: true, allow_blank: true, allow_nil: true validates_uniqueness_of :pages_custom_domain, allow_nil: true, allow_blank: true - validates :pages_custom_certificate, certificate: { intermediate: true } - validates :pages_custom_certificate_key, certificate_key: true + validates :pages_custom_certificate, certificate: true, allow_nil: true, allow_blank: true + validates :pages_custom_certificate_key, certificate_key: true, allow_nil: true, allow_blank: true add_authentication_token_field :runners_token before_save :ensure_runners_token mount_uploader :avatar, AvatarUploader + attr_encrypted :pages_custom_certificate_key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base + # Scopes default_scope { where(pending_delete: false) } diff --git a/app/validators/certificate_key_validator.rb b/app/validators/certificate_key_validator.rb index 3b5bd30db1a..7039bd5a621 100644 --- a/app/validators/certificate_key_validator.rb +++ b/app/validators/certificate_key_validator.rb @@ -16,6 +16,7 @@ class CertificateKeyValidator < ActiveModel::EachValidator private def valid_private_key_pem?(value) + return unless value pkey = OpenSSL::PKey::RSA.new(value) pkey.private? rescue OpenSSL::PKey::PKeyError diff --git a/app/validators/certificate_validator.rb b/app/validators/certificate_validator.rb index 2cba5a435b7..2a04c76d4b9 100644 --- a/app/validators/certificate_validator.rb +++ b/app/validators/certificate_validator.rb @@ -3,26 +3,20 @@ # Custom validator for private keys. # # class Project < ActiveRecord::Base -# validates :certificate_key, certificate_key: true +# validates :certificate_key, certificate: true # end # class CertificateValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) - certificate = parse_certificate(value) - unless certificate + unless valid_certificate_pem?(value) record.errors.add(attribute, "must be a valid PEM certificate") end - - if options[:intermediates] - unless certificate - record.errors.add(attribute, "certificate verification failed: missing intermediate certificates") - end - end end private - def parse_certificate(value) + def valid_certificate_pem?(value) + return unless value OpenSSL::X509::Certificate.new(value) rescue OpenSSL::X509::CertificateError nil diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index f4c1db1b93d..dab40d37ead 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -134,47 +134,6 @@ = link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" = f.submit 'Save changes', class: "btn btn-save" - - if Settings.pages.enabled - .pages-settings - .panel.panel-default - .panel-heading Pages - .errors-holder - .panel-body - - if @project.pages_url - %strong - Congratulations! Your pages are served at: - %p= link_to @project.pages_url, @project.pages_url - - else - %p - Learn how to upload your static site and have it served by - GitLab by following the #{link_to "documentation on GitLab Pages", "http://doc.gitlab.com/ee/pages/README.html", target: :blank}. - %p - In the example below we define a special job named - %code pages - which is using Jekyll to build a static site. The generated - HTML will be stored in the - %code public/ - directory which will then be archived and uploaded to GitLab. - The name of the directory should not be different than - %code public/ - in order for the pages to work. - %ul - %li - %pre - :plain - pages: - image: jekyll/jekyll - script: jekyll build -d public/ - artifacts: - paths: - - public/ - - - if @project.pages_url && can?(current_user, :remove_pages, @project) - .form-actions - = link_to 'Remove pages', remove_pages_namespace_project_path(@project.namespace, @project), - data: { confirm: "Are you sure that you want to remove pages for this project?" }, - method: :post, class: "btn btn-warning" - .row.prepend-top-default %hr .row.prepend-top-default diff --git a/app/views/projects/pages/_use.html.haml b/app/views/projects/pages/_use.html.haml index 5542bbe670b..ee38f45d44d 100644 --- a/app/views/projects/pages/_use.html.haml +++ b/app/views/projects/pages/_use.html.haml @@ -6,13 +6,3 @@ %p Learn how to upload your static site and have it served by GitLab by following the #{link_to "documentation on GitLab Pages", "http://doc.gitlab.com/ee/pages/README.html", target: :blank}. - %p - In the example below we define a special job named - %code pages - which is using Jekyll to build a static site. The generated - HTML will be stored in the - %code public/ - directory which will then be archived and uploaded to GitLab. - The name of the directory should not be different than - %code public/ - in order for the pages to work. diff --git a/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb b/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb index 6472199fc4a..13b42d18a7a 100644 --- a/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb +++ b/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb @@ -1,9 +1,9 @@ class AddPagesCustomDomainToProjects < ActiveRecord::Migration def change add_column :projects, :pages_custom_certificate, :text - add_column :projects, :pages_custom_certificate_key, :text - add_column :projects, :pages_custom_certificate_key_iv, :string - add_column :projects, :pages_custom_certificate_key_salt, :string + add_column :projects, :encrypted_pages_custom_certificate_key, :text + add_column :projects, :encrypted_pages_custom_certificate_key_iv, :string + add_column :projects, :encrypted_pages_custom_certificate_key_salt, :string add_column :projects, :pages_custom_domain, :string, unique: true add_column :projects, :pages_redirect_http, :boolean, default: false, null: false end From f034f6b3ec5dc8b72f43c954ddb34bae037be254 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 10 Feb 2016 11:37:27 +0100 Subject: [PATCH 045/183] WIP --- app/controllers/projects/pages_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index 359544472e9..055f182ae00 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -89,7 +89,7 @@ class Projects::PagesController < Projects::ApplicationController def certificate_key return unless @project.pages_custom_certificate_key @certificate_key ||= OpenSSL::PKey::RSA.new(@project.pages_custom_certificate_key) - rescue OpenSSL::PKey::PKeyError + rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError nil end end From 6e99226cca41f36d92c4ccb2cd398d2256091adc Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 10 Feb 2016 12:07:46 +0100 Subject: [PATCH 046/183] Added PagesDomain --- app/models/pages_domain.rb | 29 ++++++++++++++ app/models/project.rb | 38 +------------------ ...808_add_pages_custom_domain_to_projects.rb | 10 ----- .../20160210105555_create_pages_domain.rb | 14 +++++++ 4 files changed, 45 insertions(+), 46 deletions(-) create mode 100644 app/models/pages_domain.rb delete mode 100644 db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb create mode 100644 db/migrate/20160210105555_create_pages_domain.rb diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb new file mode 100644 index 00000000000..eebdf7501de --- /dev/null +++ b/app/models/pages_domain.rb @@ -0,0 +1,29 @@ +class PagesDomain < ActiveRecord::Base + belongs_to :project + + validates :domain, hostname: true + validates_uniqueness_of :domain, allow_nil: true, allow_blank: true + validates :certificate, certificate: true, allow_nil: true, allow_blank: true + validates :key, certificate_key: true, allow_nil: true, allow_blank: true + + attr_encrypted :pages_custom_certificate_key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base + + after_create :update + after_save :update + after_destroy :update + + def url + return unless domain + return unless Dir.exist?(project.public_pages_path) + + if certificate + return "https://#{domain}" + else + return "http://#{domain}" + end + end + + def update + UpdatePagesConfigurationService.new(project).execute + end +end diff --git a/app/models/project.rb b/app/models/project.rb index f447c2bf293..dac52a0fc5e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -150,6 +150,7 @@ class Project < ActiveRecord::Base has_many :lfs_objects, through: :lfs_objects_projects has_many :project_group_links, dependent: :destroy has_many :invited_groups, through: :project_group_links, source: :group + has_many :pages_domains, dependent: :destroy has_many :todos, dependent: :destroy has_many :notification_settings, dependent: :destroy, as: :source @@ -205,18 +206,11 @@ class Project < ActiveRecord::Base presence: true, inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } } - validates :pages_custom_domain, hostname: true, allow_blank: true, allow_nil: true - validates_uniqueness_of :pages_custom_domain, allow_nil: true, allow_blank: true - validates :pages_custom_certificate, certificate: true, allow_nil: true, allow_blank: true - validates :pages_custom_certificate_key, certificate_key: true, allow_nil: true, allow_blank: true - add_authentication_token_field :runners_token before_save :ensure_runners_token mount_uploader :avatar, AvatarUploader - attr_encrypted :pages_custom_certificate_key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base - # Scopes default_scope { where(pending_delete: false) } @@ -1184,17 +1178,6 @@ class Project < ActiveRecord::Base "#{url}/#{path}" end - def pages_custom_url - return unless pages_custom_domain - return unless Dir.exist?(public_pages_path) - - if Gitlab.config.pages.https - return "https://#{pages_custom_domain}" - else - return "http://#{pages_custom_domain}" - end - end - def pages_path File.join(Settings.pages.path, path_with_namespace) end @@ -1203,32 +1186,15 @@ class Project < ActiveRecord::Base File.join(pages_path, 'public') end - def remove_pages_certificate - update( - pages_custom_certificate: nil, - pages_custom_certificate_key: nil - ) - - UpdatePagesConfigurationService.new(self).execute - end - def remove_pages # 1. We rename pages to temporary directory # 2. We wait 5 minutes, due to NFS caching # 3. We asynchronously remove pages with force - temp_path = "#{path}.#{SecureRandom.hex}" + temp_path = "#{path}.#{SecureRandom.hex}.deleted" if Gitlab::PagesTransfer.new.rename_project(path, temp_path, namespace.path) PagesWorker.perform_in(5.minutes, :remove, namespace.path, temp_path) end - - update( - pages_custom_certificate: nil, - pages_custom_certificate_key: nil, - pages_custom_domain: nil - ) - - UpdatePagesConfigurationService.new(self).execute end def wiki diff --git a/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb b/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb deleted file mode 100644 index 13b42d18a7a..00000000000 --- a/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb +++ /dev/null @@ -1,10 +0,0 @@ -class AddPagesCustomDomainToProjects < ActiveRecord::Migration - def change - add_column :projects, :pages_custom_certificate, :text - add_column :projects, :encrypted_pages_custom_certificate_key, :text - add_column :projects, :encrypted_pages_custom_certificate_key_iv, :string - add_column :projects, :encrypted_pages_custom_certificate_key_salt, :string - add_column :projects, :pages_custom_domain, :string, unique: true - add_column :projects, :pages_redirect_http, :boolean, default: false, null: false - end -end diff --git a/db/migrate/20160210105555_create_pages_domain.rb b/db/migrate/20160210105555_create_pages_domain.rb new file mode 100644 index 00000000000..9af206143bd --- /dev/null +++ b/db/migrate/20160210105555_create_pages_domain.rb @@ -0,0 +1,14 @@ +class CreatePagesDomain < ActiveRecord::Migration + def change + create_table :pages_domains do |t| + t.integer :project_id + t.text :certificate + t.text :encrypted_key + t.string :encrypted_key_iv + t.string :encrypted_key_salt + t.string :domain + end + + add_index :pages_domains, :domain, unique: true + end +end From 13b6bad17ec46eb78878f6972da1e7e34be86bb5 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 10 Feb 2016 15:06:31 +0100 Subject: [PATCH 047/183] Implement extra domains and save pages configuration --- app/controllers/projects/pages_controller.rb | 94 +++++++------------ app/helpers/projects_helper.rb | 4 - app/models/pages_domain.rb | 84 ++++++++++++++++- .../update_pages_configuration_service.rb | 32 ++++--- app/views/projects/pages/_access.html.haml | 29 +----- app/views/projects/pages/_destroy.haml | 2 +- app/views/projects/pages/_form.html.haml | 64 ++++++------- app/views/projects/pages/_list.html.haml | 16 ++++ .../projects/pages/_no_domains.html.haml | 6 ++ .../pages/_remove_certificate.html.haml | 16 ---- .../pages/_upload_certificate.html.haml | 32 ------- app/views/projects/pages/index.html.haml | 25 +++++ app/views/projects/pages/new.html.haml | 6 ++ app/views/projects/pages/show.html.haml | 38 ++++---- config/gitlab.yml.example | 2 + config/initializers/1_settings.rb | 3 +- config/routes/project.rb | 4 +- 17 files changed, 249 insertions(+), 208 deletions(-) create mode 100644 app/views/projects/pages/_list.html.haml create mode 100644 app/views/projects/pages/_no_domains.html.haml delete mode 100644 app/views/projects/pages/_remove_certificate.html.haml delete mode 100644 app/views/projects/pages/_upload_certificate.html.haml create mode 100644 app/views/projects/pages/index.html.haml create mode 100644 app/views/projects/pages/new.html.haml diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index 055f182ae00..82814afe196 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -2,25 +2,45 @@ class Projects::PagesController < Projects::ApplicationController layout 'project_settings' before_action :authorize_update_pages!, except: [:show] - before_action :authorize_remove_pages!, only: :destroy + before_action :authorize_remove_pages!, only: [:remove_pages] + before_action :label, only: [:destroy] + before_action :domain, only: [:show] helper_method :valid_certificate?, :valid_certificate_key? helper_method :valid_key_for_certificiate?, :valid_certificate_intermediates? helper_method :certificate, :certificate_key + def index + @domains = @project.pages_domains.order(:domain) + end + def show end - def update - if @project.update_attributes(pages_params) + def new + @domain = @project.pages_domains.new + end + + def create + @domain = @project.pages_domains.create(pages_domain_params) + + if @domain.valid? redirect_to namespace_project_pages_path(@project.namespace, @project) else - render 'show' + render 'new' end end - def certificate - @project.remove_pages_certificate + def destroy + @domain.destroy + + respond_to do |format| + format.html do + redirect_to(namespace_project_pages_path(@project.namespace, @project), + notice: 'Domain was removed') + end + format.js + end end def destroy @@ -33,63 +53,15 @@ class Projects::PagesController < Projects::ApplicationController private - def pages_params - params.require(:project).permit( - :pages_custom_certificate, - :pages_custom_certificate_key, - :pages_custom_domain, - :pages_redirect_http, + def pages_domain_params + params.require(:pages_domain).permit( + :certificate, + :key, + :domain ) end - def valid_certificate? - certificate.present? - end - - def valid_certificate_key? - certificate_key.present? - end - - def valid_key_for_certificiate? - return false unless certificate - return false unless certificate_key - - # We compare the public key stored in certificate with public key from certificate key - certificate.public_key.to_pem == certificate_key.public_key.to_pem - rescue OpenSSL::X509::CertificateError, OpenSSL::PKey::PKeyError - false - end - - def valid_certificate_intermediates? - return false unless certificate - - store = OpenSSL::X509::Store.new - store.set_default_paths - - # This forces to load all intermediate certificates stored in `pages_custom_certificate` - Tempfile.open('project_certificate') do |f| - f.write(@project.pages_custom_certificate) - f.flush - store.add_file(f.path) - end - - store.verify(certificate) - rescue OpenSSL::X509::StoreError - false - end - - def certificate - return unless @project.pages_custom_certificate - - @certificate ||= OpenSSL::X509::Certificate.new(@project.pages_custom_certificate) - rescue OpenSSL::X509::CertificateError - nil - end - - def certificate_key - return unless @project.pages_custom_certificate_key - @certificate_key ||= OpenSSL::PKey::RSA.new(@project.pages_custom_certificate_key) - rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError - nil + def domain + @domain ||= @project.pages_domains.find_by(domain: params[:id].to_s) end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 63aa182502d..054cc849839 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -85,10 +85,6 @@ module ProjectsHelper "You are going to remove the pages for #{project.name_with_namespace}.\n Are you ABSOLUTELY sure?" end - def remove_pages_certificate_message(project) - "You are going to remove a certificates for #{project.name_with_namespace}.\n Are you ABSOLUTELY sure?" - end - def project_nav_tabs @nav_tabs ||= get_project_nav_tabs(@project, current_user) end diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index eebdf7501de..810af4e832a 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -2,19 +2,25 @@ class PagesDomain < ActiveRecord::Base belongs_to :project validates :domain, hostname: true - validates_uniqueness_of :domain, allow_nil: true, allow_blank: true + validates_uniqueness_of :domain, case_sensitive: false validates :certificate, certificate: true, allow_nil: true, allow_blank: true validates :key, certificate_key: true, allow_nil: true, allow_blank: true - attr_encrypted :pages_custom_certificate_key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base + validate :validate_matching_key, if: ->(domain) { domain.certificate.present? && domain.key.present? } + validate :validate_intermediates, if: ->(domain) { domain.certificate.present? } + + attr_encrypted :key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base after_create :update after_save :update after_destroy :update + def to_param + domain + end + def url return unless domain - return unless Dir.exist?(project.public_pages_path) if certificate return "https://#{domain}" @@ -23,7 +29,77 @@ class PagesDomain < ActiveRecord::Base end end + def has_matching_key? + return unless x509 + return unless pkey + + # We compare the public key stored in certificate with public key from certificate key + x509.check_private_key(pkey) + end + + def has_intermediates? + return false unless x509 + + store = OpenSSL::X509::Store.new + store.set_default_paths + + # This forces to load all intermediate certificates stored in `certificate` + Tempfile.open('certificate_chain') do |f| + f.write(certificate) + f.flush + store.add_file(f.path) + end + + store.verify(x509) + rescue OpenSSL::X509::StoreError + false + end + + def expired? + return false unless x509 + current = Time.new + return current < x509.not_before || x509.not_after < current + end + + def subject + return unless x509 + return x509.subject.to_s + end + + def fingerprint + return unless x509 + @fingeprint ||= OpenSSL::Digest::SHA256.new(x509.to_der).to_s + end + + private + + def x509 + return unless certificate + @x509 ||= OpenSSL::X509::Certificate.new(certificate) + rescue OpenSSL::X509::CertificateError + nil + end + + def pkey + return unless key + @pkey ||= OpenSSL::PKey::RSA.new(key) + rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError + nil + end + def update - UpdatePagesConfigurationService.new(project).execute + ::Projects::UpdatePagesConfigurationService.new(project).execute + end + + def validate_matching_key + unless has_matching_key? + self.errors.add(:key, "doesn't match the certificate") + end + end + + def validate_intermediates + unless has_intermediates? + self.errors.add(:certificate, 'misses intermediates') + end end end diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index be4c2fbef8c..5afb0582ca6 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -7,9 +7,7 @@ module Projects end def execute - update_file(pages_cname_file, project.pages_custom_domain) - update_file(pages_certificate_file, project.pages_custom_certificate) - update_file(pages_certificate_file_key, project.pages_custom_certificate_key) + update_file(pages_config_file, pages_config) reload_daemon success rescue => e @@ -18,6 +16,22 @@ module Projects private + def pages_config + { + domains: pages_domains_config + } + end + + def pages_domains_config + project.pages_domains.map do |domain| + { + domain: domain.domain, + certificate: domain.certificate, + key: domain.key, + } + end + end + def reload_daemon # GitLab Pages daemon constantly watches for modification time of `pages.path` # It reloads configuration when `pages.path` is modified @@ -28,16 +42,8 @@ module Projects @pages_path ||= project.pages_path end - def pages_cname_file - File.join(pages_path, 'CNAME') - end - - def pages_certificate_file - File.join(pages_path, 'domain.crt') - end - - def pages_certificate_key_file - File.join(pages_path, 'domain.key') + def pages_config_file + File.join(pages_path, 'config.jso') end def update_file(file, data) diff --git a/app/views/projects/pages/_access.html.haml b/app/views/projects/pages/_access.html.haml index d64f99fd22b..9740877b214 100644 --- a/app/views/projects/pages/_access.html.haml +++ b/app/views/projects/pages/_access.html.haml @@ -5,30 +5,9 @@ .panel-body %p %strong - Congratulations! Your pages are served at: + Congratulations! Your pages are served under: + %p= link_to @project.pages_url, @project.pages_url - - if Settings.pages.custom_domain && @project.pages_custom_url - %p= link_to @project.pages_custom_url, @project.pages_custom_url - - - if @project.pages_custom_certificate - - unless valid_certificate? - #error_explanation - .alert.alert-warning - Your certificate is invalid. - - - unless valid_certificate_key? - #error_explanation - .alert.alert-warning - Your private key is invalid. - - - unless valid_key_for_certificiate? - #error_explanation - .alert.alert-warning - Your private key can't be used with your certificate. - - - unless valid_certificate_intermediates? - #error_explanation - .alert.alert-warning - Your certificate doesn't have intermediates. - Your page may not work properly. + - @project.pages_domains.each do |domain| + %p= link_to domain.url, domain.url diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index 61b995a5934..dd493a6d312 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -3,7 +3,7 @@ .panel-heading Remove pages .errors-holder .panel-body - = form_tag(namespace_project_pages_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do + = form_tag(remove_pages_namespace_project_pages_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do %p Removing the pages will prevent from exposing them to outside world. .form-actions diff --git a/app/views/projects/pages/_form.html.haml b/app/views/projects/pages/_form.html.haml index a7b03d552db..c69b76c6697 100644 --- a/app/views/projects/pages/_form.html.haml +++ b/app/views/projects/pages/_form.html.haml @@ -1,35 +1,35 @@ -- if can?(current_user, :update_pages, @project) - .panel.panel-default - .panel-heading - Settings - .panel-body - = form_for [@project], url: namespace_project_pages_path(@project.namespace, @project), html: { class: 'form-horizontal fieldset-form' } do |f| - - if @project.errors.any? - #error_explanation - .alert.alert-danger - - @project.errors.full_messages.each do |msg| - %p= msg += form_for [@domain], url: namespace_project_pages_path(@project.namespace, @project), html: { class: 'form-horizontal fieldset-form' } do |f| + - if @domain.errors.any? + #error_explanation + .alert.alert-danger + - @domain.errors.full_messages.each do |msg| + %p= msg - .form-group - = f.label :pages_domain, class: 'control-label' do - Custom domain - .col-sm-10 - - if Settings.pages.custom_domain - = f.text_field :pages_custom_domain, required: false, autocomplete: 'off', class: 'form-control' - %span.help-inline Allows you to serve the pages under your domain - - else - .nothing-here-block - Support for custom domains and certificates is disabled. - Ask your system's administrator to enable it. + .form-group + = f.label :domain, class: 'control-label' do + Domain + .col-sm-10 + = f.text_field :domain, required: true, autocomplete: 'off', class: 'form-control' + %span.help-inline * required - - if Settings.pages.https - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox - = f.label :pages_redirect_http do - = f.check_box :pages_redirect_http - %span.descr Force HTTPS - .help-block Redirect the HTTP to HTTPS forcing to always use the secure connection + - if Settings.pages.external_https + .form-group + = f.label :certificate, class: 'control-label' do + Certificate (PEM) + .col-sm-10 + = f.text_area :certificate, rows: 5, class: 'form-control', value: '' + %span.help-inline Upload a certificate for your domain with all intermediates - .form-actions - = f.submit 'Save changes', class: "btn btn-save" + .form-group + = f.label :key, class: 'control-label' do + Key (PEM) + .col-sm-10 + = f.text_area :key, rows: 5, class: 'form-control', value: '' + %span.help-inline Upload a certificate for your domain with all intermediates + - else + .nothing-here-block + Support for custom certificates is disabled. + Ask your system's administrator to enable it. + + .form-actions + = f.submit 'Create New Domain', class: "btn btn-save" diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml new file mode 100644 index 00000000000..7dfeb0e6e12 --- /dev/null +++ b/app/views/projects/pages/_list.html.haml @@ -0,0 +1,16 @@ +.panel.panel-default + .panel-heading + Domains (#{@domains.count}) + %ul.well-list + - @domains.each do |domain| + %li + .pull-right + = link_to 'Details', namespace_project_page_path(@project.namespace, @project, domain), class: "btn btn-sm btn-grouped" + = link_to 'Remove', namespace_project_page_path(@project.namespace, @project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" + .clearfix + %span= link_to domain.domain, domain.url + %p + - if domain.subject + %span.label.label-gray Certificate: #{domain.subject} + - if domain.expired? + %span.label.label-danger Expired diff --git a/app/views/projects/pages/_no_domains.html.haml b/app/views/projects/pages/_no_domains.html.haml new file mode 100644 index 00000000000..5a18740346a --- /dev/null +++ b/app/views/projects/pages/_no_domains.html.haml @@ -0,0 +1,6 @@ +.panel.panel-default + .panel-heading + Domains + .nothing-here-block + Support for domains and certificates is disabled. + Ask your system's administrator to enable it. diff --git a/app/views/projects/pages/_remove_certificate.html.haml b/app/views/projects/pages/_remove_certificate.html.haml deleted file mode 100644 index e8c0d03adfa..00000000000 --- a/app/views/projects/pages/_remove_certificate.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -- if can?(current_user, :update_pages, @project) && @project.pages_custom_certificate - .panel.panel-default.panel.panel-danger - .panel-heading - Remove certificate - .errors-holder - .panel-body - = form_tag(certificates_namespace_project_pages_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do - %p - Removing the certificate will stop serving the page under HTTPS. - - if certificate - %p - %pre - = certificate.to_text - - .form-actions - = button_to 'Remove certificate', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_pages_certificate_message(@project) } diff --git a/app/views/projects/pages/_upload_certificate.html.haml b/app/views/projects/pages/_upload_certificate.html.haml deleted file mode 100644 index 30873fcf395..00000000000 --- a/app/views/projects/pages/_upload_certificate.html.haml +++ /dev/null @@ -1,32 +0,0 @@ -- if can?(current_user, :update_pages, @project) && Settings.pages.https && Settings.pages.custom_domain - .panel.panel-default - .panel-heading - Certificate - .panel-body - %p - Allows you to upload your certificate which will be used to serve pages under your domain. - %br - - = form_for [@project], url: namespace_project_pages_path(@project.namespace, @project), html: { class: 'form-horizontal fieldset-form' } do |f| - - if @project.errors.any? - #error_explanation - .alert.alert-danger - - @project.errors.full_messages.each do |msg| - %p= msg - - .form-group - = f.label :pages_custom_certificate, class: 'control-label' do - Certificate (PEM) - .col-sm-10 - = f.text_area :pages_custom_certificate, required: true, rows: 5, class: 'form-control', value: '' - %span.help-inline Upload a certificate for your domain with all intermediates - - .form-group - = f.label :pages_custom_certificate_key, class: 'control-label' do - Key (PEM) - .col-sm-10 - = f.text_area :pages_custom_certificate_key, required: true, rows: 5, class: 'form-control', value: '' - %span.help-inline Upload a certificate for your domain with all intermediates - - .form-actions - = f.submit 'Update certificate', class: "btn btn-save" diff --git a/app/views/projects/pages/index.html.haml b/app/views/projects/pages/index.html.haml new file mode 100644 index 00000000000..fea34c113ba --- /dev/null +++ b/app/views/projects/pages/index.html.haml @@ -0,0 +1,25 @@ +- page_title "Pages" +%h3.page_title + Pages + + = link_to new_namespace_project_page_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Domain" do + %i.fa.fa-plus + New Domain + +%p.light + With GitLab Pages you can host for free your static websites on GitLab. + Combined with the power of GitLab CI and the help of GitLab Runner + you can deploy static pages for your individual projects, your user or your group. + +%hr.clearfix + +- if Settings.pages.enabled + = render 'access' + = render 'use' + - if Settings.pages.external_http || Settings.pages.external_https + = render 'list' + - else + = render 'no_domains' + = render 'destroy' +- else + = render 'disabled' diff --git a/app/views/projects/pages/new.html.haml b/app/views/projects/pages/new.html.haml new file mode 100644 index 00000000000..2609df62aac --- /dev/null +++ b/app/views/projects/pages/new.html.haml @@ -0,0 +1,6 @@ +- page_title 'Pages' +%h3.page_title + New Pages Domain +%hr.clearfix +%div + = render 'form' diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index 5f689800da8..98c4e890968 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -1,18 +1,22 @@ -- page_title "Pages" -%h3.page_title Pages -%p.light - With GitLab Pages you can host for free your static websites on GitLab. - Combined with the power of GitLab CI and the help of GitLab Runner - you can deploy static pages for your individual projects, your user or your group. -%hr +- page_title "#{@domain.domain}", "Pages Domain" -- if Settings.pages.enabled - = render 'access' - = render 'use' - - if @project.pages_url - = render 'form' - = render 'upload_certificate' - = render 'remove_certificate' - = render 'destroy' -- else - = render 'disabled' +%h3.page-title + #{@domain.domain} + +.table-holder + %table.table + %tr + %td + Domain + %td + = link_to @domain.domain, @domain.url + %tr + %td + Certificate + %td + - if @domain.certificate + %pre + = @domain.certificate.to_text + - else + .light + missing diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index c6f06d43d07..f2bde602795 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -165,6 +165,8 @@ production: &base host: example.com port: 80 # Set to 443 if you serve the pages with HTTPS https: false # Set to true if you serve the pages with HTTPS + # external_http: "1.1.1.1:80" # if defined notifies the GitLab pages do support Custom Domains + # external_https: "1.1.1.1:443" # if defined notifies the GitLab pages do support Custom Domains with Certificates ## Mattermost ## For enabling Add to Mattermost button diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 239aa662d9f..0015ddf902d 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -273,7 +273,8 @@ Settings.pages['https'] = false if Settings.pages['https'].nil? Settings.pages['port'] ||= Settings.pages.https ? 443 : 80 Settings.pages['protocol'] ||= Settings.pages.https ? "https" : "http" Settings.pages['url'] ||= Settings.send(:build_pages_url) -Settings.pages['custom_domain'] ||= false if Settings.pages['custom_domain'].nil? +Settings.pages['external_http'] ||= false if Settings.pages['external_http'].nil? +Settings.pages['external_https'] ||= false if Settings.pages['external_https'].nil? # # Git LFS diff --git a/config/routes/project.rb b/config/routes/project.rb index 956a2d3186f..ac1e3fce16a 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -39,8 +39,8 @@ constraints(ProjectUrlConstrainer.new) do end end - resource :pages, only: [:show, :update, :destroy] do - delete :certificates + resources :pages, except: [:edit, :update] do + delete :remove_pages end resources :compare, only: [:index, :create] do From e5e2e7b70315cbcee12db66ebc73dbd0ef4a14ae Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 10 Feb 2016 16:21:39 +0100 Subject: [PATCH 048/183] Fix the remove_pages --- app/controllers/projects/pages_controller.rb | 5 +++++ .../projects/update_pages_configuration_service.rb | 4 ++-- app/views/projects/pages/index.html.haml | 7 ++++--- config/routes/project.rb | 4 +++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index 82814afe196..0c7f2bd5784 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -51,6 +51,11 @@ class Projects::PagesController < Projects::ApplicationController end end + def remove_pages + project.remove_pages + project.pages_domains.destroy_all + end + private def pages_domain_params diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index 5afb0582ca6..53e9d9e2757 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -43,7 +43,7 @@ module Projects end def pages_config_file - File.join(pages_path, 'config.jso') + File.join(pages_path, 'config.json') end def update_file(file, data) @@ -52,7 +52,7 @@ module Projects file.write(data) end else - File.rm_r(file) + File.rm(file, force: true) end end end diff --git a/app/views/projects/pages/index.html.haml b/app/views/projects/pages/index.html.haml index fea34c113ba..284f362d535 100644 --- a/app/views/projects/pages/index.html.haml +++ b/app/views/projects/pages/index.html.haml @@ -2,9 +2,10 @@ %h3.page_title Pages - = link_to new_namespace_project_page_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Domain" do - %i.fa.fa-plus - New Domain + - if Settings.pages.external_http || Settings.pages.external_https + = link_to new_namespace_project_page_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Domain" do + %i.fa.fa-plus + New Domain %p.light With GitLab Pages you can host for free your static websites on GitLab. diff --git a/config/routes/project.rb b/config/routes/project.rb index ac1e3fce16a..7a41cb81bfa 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -40,7 +40,9 @@ constraints(ProjectUrlConstrainer.new) do end resources :pages, except: [:edit, :update] do - delete :remove_pages + collection do + delete :remove_pages + end end resources :compare, only: [:index, :create] do From 0552c0b6f185433ad0a7caac321f0a6d445a0b63 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 10 Feb 2016 16:45:59 +0100 Subject: [PATCH 049/183] Fix views --- app/models/pages_domain.rb | 4 +-- app/views/projects/pages/_list.html.haml | 33 ++++++++++++------------ app/views/projects/pages/show.html.haml | 6 ++--- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index 810af4e832a..985329bb856 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -71,8 +71,6 @@ class PagesDomain < ActiveRecord::Base @fingeprint ||= OpenSSL::Digest::SHA256.new(x509.to_der).to_s end - private - def x509 return unless certificate @x509 ||= OpenSSL::X509::Certificate.new(certificate) @@ -87,6 +85,8 @@ class PagesDomain < ActiveRecord::Base nil end + private + def update ::Projects::UpdatePagesConfigurationService.new(project).execute end diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml index 7dfeb0e6e12..e88a001d636 100644 --- a/app/views/projects/pages/_list.html.haml +++ b/app/views/projects/pages/_list.html.haml @@ -1,16 +1,17 @@ -.panel.panel-default - .panel-heading - Domains (#{@domains.count}) - %ul.well-list - - @domains.each do |domain| - %li - .pull-right - = link_to 'Details', namespace_project_page_path(@project.namespace, @project, domain), class: "btn btn-sm btn-grouped" - = link_to 'Remove', namespace_project_page_path(@project.namespace, @project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" - .clearfix - %span= link_to domain.domain, domain.url - %p - - if domain.subject - %span.label.label-gray Certificate: #{domain.subject} - - if domain.expired? - %span.label.label-danger Expired +- if @domains.any? + .panel.panel-default + .panel-heading + Domains (#{@domains.count}) + %ul.well-list + - @domains.each do |domain| + %li + .pull-right + = link_to 'Details', namespace_project_page_path(@project.namespace, @project, domain), class: "btn btn-sm btn-grouped" + = link_to 'Remove', namespace_project_page_path(@project.namespace, @project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" + .clearfix + %span= link_to domain.domain, domain.url + %p + - if domain.subject + %span.label.label-gray Certificate: #{domain.subject} + - if domain.expired? + %span.label.label-danger Expired diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index 98c4e890968..52493b1959b 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -1,7 +1,7 @@ - page_title "#{@domain.domain}", "Pages Domain" %h3.page-title - #{@domain.domain} + Pages Domain .table-holder %table.table @@ -14,9 +14,9 @@ %td Certificate %td - - if @domain.certificate + - if @domain.x509 %pre - = @domain.certificate.to_text + = @domain.x509.to_text - else .light missing From d3b828487647f106a8947864e18ac1ad7bd9d6f4 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 12 Feb 2016 16:05:17 +0100 Subject: [PATCH 050/183] Pages domain model specs --- app/models/pages_domain.rb | 50 +++--- .../update_pages_configuration_service.rb | 22 ++- app/services/projects/update_pages_service.rb | 3 +- app/views/projects/pages/show.html.haml | 4 +- db/schema.rb | 11 ++ spec/factories/pages_domains.rb | 79 +++++++++ spec/models/pages_domain_spec.rb | 152 ++++++++++++++++++ spec/models/project_spec.rb | 1 + ...r_spec.rb => update_pages_service_spec.rb} | 0 9 files changed, 293 insertions(+), 29 deletions(-) create mode 100644 spec/factories/pages_domains.rb create mode 100644 spec/models/pages_domain_spec.rb rename spec/services/projects/{update_pages_worker_spec.rb => update_pages_service_spec.rb} (100%) diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index 985329bb856..b594957493a 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -6,7 +6,8 @@ class PagesDomain < ActiveRecord::Base validates :certificate, certificate: true, allow_nil: true, allow_blank: true validates :key, certificate_key: true, allow_nil: true, allow_blank: true - validate :validate_matching_key, if: ->(domain) { domain.certificate.present? && domain.key.present? } + validate :validate_pages_domain + validate :validate_matching_key, if: ->(domain) { domain.certificate.present? || domain.key.present? } validate :validate_intermediates, if: ->(domain) { domain.certificate.present? } attr_encrypted :key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base @@ -30,8 +31,8 @@ class PagesDomain < ActiveRecord::Base end def has_matching_key? - return unless x509 - return unless pkey + return false unless x509 + return false unless pkey # We compare the public key stored in certificate with public key from certificate key x509.check_private_key(pkey) @@ -40,6 +41,9 @@ class PagesDomain < ActiveRecord::Base def has_intermediates? return false unless x509 + # self-signed certificates doesn't have the certificate chain + return true if x509.verify(x509.public_key) + store = OpenSSL::X509::Store.new store.set_default_paths @@ -66,23 +70,8 @@ class PagesDomain < ActiveRecord::Base return x509.subject.to_s end - def fingerprint - return unless x509 - @fingeprint ||= OpenSSL::Digest::SHA256.new(x509.to_der).to_s - end - - def x509 - return unless certificate - @x509 ||= OpenSSL::X509::Certificate.new(certificate) - rescue OpenSSL::X509::CertificateError - nil - end - - def pkey - return unless key - @pkey ||= OpenSSL::PKey::RSA.new(key) - rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError - nil + def certificate_text + @certificate_text ||= x509.try(:to_text) end private @@ -102,4 +91,25 @@ class PagesDomain < ActiveRecord::Base self.errors.add(:certificate, 'misses intermediates') end end + + def validate_pages_domain + return unless domain + if domain.downcase.ends_with?(".#{Settings.pages.host}".downcase) + self.errors.add(:domain, "*.#{Settings.pages.host} is restricted") + end + end + + def x509 + return unless certificate + @x509 ||= OpenSSL::X509::Certificate.new(certificate) + rescue OpenSSL::X509::CertificateError + nil + end + + def pkey + return unless key + @pkey ||= OpenSSL::PKey::RSA.new(key) + rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError + nil + end end diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index 53e9d9e2757..b5324587d0e 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -35,7 +35,7 @@ module Projects def reload_daemon # GitLab Pages daemon constantly watches for modification time of `pages.path` # It reloads configuration when `pages.path` is modified - File.touch(Settings.pages.path) + update_file(pages_update_file, SecureRandom.hex(64)) end def pages_path @@ -46,14 +46,24 @@ module Projects File.join(pages_path, 'config.json') end + def pages_update_file + File.join(Settings.pages.path, '.update') + end + def update_file(file, data) - if data - File.open(file, 'w') do |file| - file.write(data) - end - else + unless data File.rm(file, force: true) + return end + + temp_file = "#{file}.#{SecureRandom.hex(16)}" + File.open(temp_file, 'w') do |file| + file.write(data) + end + File.mv(temp_file, file, force: true) + ensure + # In case if the updating fails + File.rm(temp_file, force: true) end end end diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index ceabd29fd52..a9979bf1e96 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -1,5 +1,6 @@ module Projects - class UpdatePagesService < BaseService + class + UpdatePagesService < BaseService BLOCK_SIZE = 32.kilobytes MAX_SIZE = 1.terabyte SITE_PATH = 'public/' diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index 52493b1959b..8b7010b75b2 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -14,9 +14,9 @@ %td Certificate %td - - if @domain.x509 + - if @domain.certificate_text %pre - = @domain.x509.to_text + = @domain.certificate_text - else .light missing diff --git a/db/schema.rb b/db/schema.rb index 15f378b28ff..dc3d8c22e8d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -855,6 +855,17 @@ ActiveRecord::Schema.define(version: 20170130204620) do add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree + create_table "pages_domains", force: :cascade do |t| + t.integer "project_id" + t.text "certificate" + t.text "encrypted_key" + t.string "encrypted_key_iv" + t.string "encrypted_key_salt" + t.string "domain" + end + + add_index "pages_domains", ["domain"], name: "index_pages_domains_on_domain", unique: true, using: :btree + create_table "personal_access_tokens", force: :cascade do |t| t.integer "user_id", null: false t.string "token", null: false diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb new file mode 100644 index 00000000000..4608867087c --- /dev/null +++ b/spec/factories/pages_domains.rb @@ -0,0 +1,79 @@ +FactoryGirl.define do + factory :pages_domain, class: 'PagesDomain' do + domain 'my.domain.com' + + trait :with_certificate do + certificate '-----BEGIN CERTIFICATE----- +MIICGzCCAYSgAwIBAgIBATANBgkqhkiG9w0BAQUFADAbMRkwFwYDVQQDExB0ZXN0 +LWNlcnRpZmljYXRlMB4XDTE2MDIxMjE0MzIwMFoXDTIwMDQxMjE0MzIwMFowGzEZ +MBcGA1UEAxMQdGVzdC1jZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEApL4J9L0ZxFJ1hI1LPIflAlAGvm6ZEvoT4qKU5Xf2JgU7/2geNR1qlNFa +SvCc08Knupp5yTgmvyK/Xi09U0N82vvp4Zvr/diSc4A/RA6Mta6egLySNT438kdT +nY2tR5feoTLwQpX0t4IMlwGQGT5h6Of2fKmDxzuwuyffcIHqLdsCAwEAAaNvMG0w +DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUxl9WSxBprB0z0ibJs3rXEk0+95AwCwYD +VR0PBAQDAgXgMBEGCWCGSAGG+EIBAQQEAwIGQDAeBglghkgBhvhCAQ0EERYPeGNh +IGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4GBAGC4T8SlFHK0yPSa+idGLQFQ +joZp2JHYvNlTPkRJ/J4TcXxBTJmArcQgTIuNoBtC+0A/SwdK4MfTCUY4vNWNdese +5A4K65Nb7Oh1AdQieTBHNXXCdyFsva9/ScfQGEl7p55a52jOPs0StPd7g64uvjlg +YHi2yesCrOvVXt+lgPTd +-----END CERTIFICATE-----' + end + + trait :with_key do + key '-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKS+CfS9GcRSdYSN +SzyH5QJQBr5umRL6E+KilOV39iYFO/9oHjUdapTRWkrwnNPCp7qaeck4Jr8iv14t +PVNDfNr76eGb6/3YknOAP0QOjLWunoC8kjU+N/JHU52NrUeX3qEy8EKV9LeCDJcB +kBk+Yejn9nypg8c7sLsn33CB6i3bAgMBAAECgYA2D26w80T7WZvazYr86BNMePpd +j2mIAqx32KZHzt/lhh40J/SRtX9+Kl0Y7nBoRR5Ja9u/HkAIxNxLiUjwg9r6cpg/ +uITEF5nMt7lAk391BuI+7VOZZGbJDsq2ulPd6lO+C8Kq/PI/e4kXcIjeH6KwQsuR +5vrXfBZ3sQfflaiN4QJBANBt8JY2LIGQF8o89qwUpRL5vbnKQ4IzZ5+TOl4RLR7O +AQpJ81tGuINghO7aunctb6rrcKJrxmEH1whzComybrMCQQDKV49nOBudRBAIgG4K +EnLzsRKISUHMZSJiYTYnablof8cKw1JaQduw7zgrUlLwnroSaAGX88+Jw1f5n2Lh +Vlg5AkBDdUGnrDLtYBCDEQYZHblrkc7ZAeCllDOWjxUV+uMqlCv8A4Ey6omvY57C +m6I8DkWVAQx8VPtozhvHjUw80rZHAkB55HWHAM3h13axKG0htCt7klhPsZHpx6MH +EPjGlXIT+aW2XiPmK3ZlCDcWIenE+lmtbOpI159Wpk8BGXs/s/xBAkEAlAY3ymgx +63BDJEwvOb2IaP8lDDxNsXx9XJNVvQbv5n15vNsLHbjslHfAhAbxnLQ1fLhUPqSi +nNp/xedE1YxutQ== +-----END PRIVATE KEY-----' + end + + trait :with_certificate_chain do + # This certificate is signed with different key + certificate '-----BEGIN CERTIFICATE----- +MIIDGTCCAgGgAwIBAgIBAjANBgkqhkiG9w0BAQUFADASMRAwDgYDVQQDEwdUZXN0 +IENBMB4XDTE2MDIxMjE0MjMwMFoXDTE3MDIxMTE0MjMwMFowHTEbMBkGA1UEAxMS +dGVzdC1jZXJ0aWZpY2F0ZS0yMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAw8RWetIUT0YymSuKvBpClzDv/jQdX0Ch+2iF7f4Lm3lcmoUuXgyhl/WRe5K9 +ONuMHPQlZbeavEbvWb0BsU7geInhsjd/zAu3EP17jfSIXToUdSD20wcSG/yclLdZ +qhb6NCtHTJKFUI8BktoS7kafkdvmeem/UJFzlvcA6VMyGDkS8ZN39a45R1jGmPEl +Yk0g1jW7lSKcBLjU1O/Csv59LyWXqBP6jR1vB8ijlUf1IyK8gOk7NHF13GHl7Z3A +/8zwuEt/pB3yK92o71P+FnSEcJ23zcAalz6H9ajVTzRr/AXttineBNVYnEuPXW+V +Rsboe+bBO/e4pVKXnQ1F3aMT7QIDAQABo28wbTAMBgNVHRMBAf8EAjAAMB0GA1Ud +DgQWBBSFwo3rhc26lD8ZVaBVcUY1NyCOLDALBgNVHQ8EBAMCBeAwEQYJYIZIAYb4 +QgEBBAQDAgZAMB4GCWCGSAGG+EIBDQQRFg94Y2EgY2VydGlmaWNhdGUwDQYJKoZI +hvcNAQEFBQADggEBABppUhunuT7qArM9gZ2gLgcOK8qyZWU8AJulvloaCZDvqGVs +Qom0iEMBrrt5+8bBevNiB49Tz7ok8NFgLzrlEnOw6y6QGjiI/g8sRKEiXl+ZNX8h +s8VN6arqT348OU8h2BixaXDmBF/IqZVApGhR8+B4fkCt0VQmdzVuHGbOQXMWJCpl +WlU8raZoPIqf6H/8JA97pM/nk/3CqCoHsouSQv+jGY4pSL22RqsO0ylIM0LDBbmF +m4AEaojTljX1tMJAF9Rbiw/omam5bDPq2JWtosrz/zB69y5FaQjc6FnCk0M4oN/+ +VM+d42lQAgoq318A84Xu5vRh1KCAJuztkhNbM+w= +-----END CERTIFICATE-----' + end + + trait :with_expired_certificate do + certificate '-----BEGIN CERTIFICATE----- +MIIBsDCCARmgAwIBAgIBATANBgkqhkiG9w0BAQUFADAeMRwwGgYDVQQDExNleHBp +cmVkLWNlcnRpZmljYXRlMB4XDTE1MDIxMjE0MzMwMFoXDTE2MDIwMTE0MzMwMFow +HjEcMBoGA1UEAxMTZXhwaXJlZC1jZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEF +AAOBjQAwgYkCgYEApL4J9L0ZxFJ1hI1LPIflAlAGvm6ZEvoT4qKU5Xf2JgU7/2ge +NR1qlNFaSvCc08Knupp5yTgmvyK/Xi09U0N82vvp4Zvr/diSc4A/RA6Mta6egLyS +NT438kdTnY2tR5feoTLwQpX0t4IMlwGQGT5h6Of2fKmDxzuwuyffcIHqLdsCAwEA +ATANBgkqhkiG9w0BAQUFAAOBgQBNj+vWvneyW1KkbVK+b/cVmnYPSfbkHrYK6m8X +Hq9LkWn6WP4EHsesHyslgTQZF8C7kVLTbLn2noLnOE+Mp3vcWlZxl3Yk6aZMhKS+ +Iy6oRpHaCF/2obZdIdgf9rlyz0fkqyHJc9GkioSoOhJZxEV2SgAkap8yS0sX2tJ9 +ZDXgrA== +-----END CERTIFICATE-----' + end + end +end diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb new file mode 100644 index 00000000000..929b2a26549 --- /dev/null +++ b/spec/models/pages_domain_spec.rb @@ -0,0 +1,152 @@ +require 'spec_helper' + +describe PagesDomain, models: true do + describe 'associations' do + it { is_expected.to belong_to(:project) } + end + + describe :validate_domain do + subject { build(:pages_domain, domain: domain) } + + context 'is unique' do + let(:domain) { 'my.domain.com' } + + it { is_expected.to validate_uniqueness_of(:domain) } + end + + context 'valid domain' do + let(:domain) { 'my.domain.com' } + + it { is_expected.to be_valid } + end + + context 'no domain' do + let(:domain) { nil } + + it { is_expected.to_not be_valid } + end + + context 'invalid domain' do + let(:domain) { '0123123' } + + it { is_expected.to_not be_valid } + end + + context 'domain from .example.com' do + let(:domain) { 'my.domain.com' } + + before { allow(Settings.pages).to receive(:host).and_return('domain.com') } + + it { is_expected.to_not be_valid } + end + end + + describe 'validate certificate' do + subject { domain } + + context 'when only certificate is specified' do + let(:domain) { build(:pages_domain, :with_certificate) } + + it { is_expected.to_not be_valid } + end + + context 'when only key is specified' do + let(:domain) { build(:pages_domain, :with_key) } + + it { is_expected.to_not be_valid } + end + + context 'with matching key' do + let(:domain) { build(:pages_domain, :with_certificate, :with_key) } + + it { is_expected.to be_valid } + end + + context 'for not matching key' do + let(:domain) { build(:pages_domain, :with_certificate_chain, :with_key) } + + it { is_expected.to_not be_valid } + end + end + + describe :url do + subject { domain.url } + + context 'without the certificate' do + let(:domain) { build(:pages_domain) } + + it { is_expected.to eq('http://my.domain.com') } + end + + context 'with a certificate' do + let(:domain) { build(:pages_domain, :with_certificate) } + + it { is_expected.to eq('https://my.domain.com') } + end + end + + describe :has_matching_key? do + subject { domain.has_matching_key? } + + context 'for matching key' do + let(:domain) { build(:pages_domain, :with_certificate, :with_key) } + + it { is_expected.to be_truthy } + end + + context 'for invalid key' do + let(:domain) { build(:pages_domain, :with_certificate_chain, :with_key) } + + it { is_expected.to be_falsey } + end + end + + describe :has_intermediates? do + subject { domain.has_intermediates? } + + context 'for self signed' do + let(:domain) { build(:pages_domain, :with_certificate) } + + it { is_expected.to be_truthy } + end + + context 'for certificate chain without the root' do + let(:domain) { build(:pages_domain, :with_certificate_chain) } + + it { is_expected.to be_falsey } + end + end + + describe :expired? do + subject { domain.expired? } + + context 'for valid' do + let(:domain) { build(:pages_domain, :with_certificate) } + + it { is_expected.to be_falsey } + end + + context 'for expired' do + let(:domain) { build(:pages_domain, :with_expired_certificate) } + + it { is_expected.to be_truthy } + end + end + + describe :subject do + let(:domain) { build(:pages_domain, :with_certificate) } + + subject { domain.subject } + + it { is_expected.to eq('/CN=test-certificate') } + end + + describe :certificate_text do + let(:domain) { build(:pages_domain, :with_certificate) } + + subject { domain.certificate_text } + + # We test only existence of output, since the output is long + it { is_expected.to_not be_empty } + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 48b085781e7..5fde9194e93 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -60,6 +60,7 @@ describe Project, models: true do it { is_expected.to have_many(:runners) } it { is_expected.to have_many(:variables) } it { is_expected.to have_many(:triggers) } + it { is_expected.to have_many(:pages_domains) } it { is_expected.to have_many(:labels).class_name('ProjectLabel').dependent(:destroy) } it { is_expected.to have_many(:users_star_projects).dependent(:destroy) } it { is_expected.to have_many(:environments).dependent(:destroy) } diff --git a/spec/services/projects/update_pages_worker_spec.rb b/spec/services/projects/update_pages_service_spec.rb similarity index 100% rename from spec/services/projects/update_pages_worker_spec.rb rename to spec/services/projects/update_pages_service_spec.rb From b7fd7daee4610674a2301c618fd60b8997f2cf8a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 14 Feb 2016 19:58:45 +0100 Subject: [PATCH 051/183] Fix rubocop complains --- app/controllers/projects/pages_controller.rb | 21 ++++++++++---------- app/models/pages_domain.rb | 4 ++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index 0c7f2bd5784..2268d2d8aa2 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -43,26 +43,25 @@ class Projects::PagesController < Projects::ApplicationController end end - def destroy - @project.remove_pages - - respond_to do |format| - format.html { redirect_to project_path(@project) } - end - end - def remove_pages project.remove_pages project.pages_domains.destroy_all + + respond_to do |format| + format.html do + redirect_to(namespace_project_pages_path(@project.namespace, @project), + notice: 'Pages were removed') + end + end end private def pages_domain_params params.require(:pages_domain).permit( - :certificate, - :key, - :domain + :certificate, + :key, + :domain ) end diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index b594957493a..83fdc1c630d 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -62,12 +62,12 @@ class PagesDomain < ActiveRecord::Base def expired? return false unless x509 current = Time.new - return current < x509.not_before || x509.not_after < current + current < x509.not_before || x509.not_after < current end def subject return unless x509 - return x509.subject.to_s + x509.subject.to_s end def certificate_text From db35f3dc573fe5d07eb03de7690d98eef98784d3 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 14 Feb 2016 20:28:02 +0100 Subject: [PATCH 052/183] Add tests for Active Tab --- app/views/projects/pages/_disabled.html.haml | 2 +- features/project/active_tab.feature | 7 +++++++ features/steps/project/active_tab.rb | 8 ++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/views/projects/pages/_disabled.html.haml b/app/views/projects/pages/_disabled.html.haml index cf9ef5b4d6f..ad51fbc6cab 100644 --- a/app/views/projects/pages/_disabled.html.haml +++ b/app/views/projects/pages/_disabled.html.haml @@ -1,4 +1,4 @@ .panel.panel-default .nothing-here-block - GitLab Pages is disabled. + GitLab Pages are disabled. Ask your system's administrator to enable it. diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature index d033e6b167b..5c14c5db665 100644 --- a/features/project/active_tab.feature +++ b/features/project/active_tab.feature @@ -53,6 +53,13 @@ Feature: Project Active Tab And no other sub navs should be active And the active main tab should be Settings + Scenario: On Project Settings/Pages + Given I visit my project's settings page + And I click the "Pages" tab + Then the active sub nav should be Pages + And no other sub navs should be active + And the active main tab should be Settings + Scenario: On Project Members Given I visit my project's members page Then the active sub nav should be Members diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb index 9f701840f1d..e842d7bec2b 100644 --- a/features/steps/project/active_tab.rb +++ b/features/steps/project/active_tab.rb @@ -35,6 +35,10 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps click_link('Deploy Keys') end + step 'I click the "Pages" tab' do + click_link('Pages') + end + step 'the active sub nav should be Members' do ensure_active_sub_nav('Members') end @@ -47,6 +51,10 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps ensure_active_sub_nav('Deploy Keys') end + step 'the active sub nav should be Pages' do + ensure_active_sub_nav('Pages') + end + # Sub Tabs: Commits step 'I click the "Compare" tab' do From 84edc9a22f5d858cb02f32d22b66c92fb939378a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 14 Feb 2016 21:06:24 +0100 Subject: [PATCH 053/183] Added spinach tests --- .../projects/pages_domains_controller.rb | 0 app/helpers/projects_helper.rb | 4 - app/views/projects/pages/_destroy.haml | 9 +- app/views/projects/pages/_form.html.haml | 2 +- app/views/projects/pages/index.html.haml | 6 +- features/project/pages.feature | 73 +++++++++ features/steps/project/pages.rb | 139 ++++++++++++++++++ 7 files changed, 220 insertions(+), 13 deletions(-) create mode 100644 app/controllers/projects/pages_domains_controller.rb create mode 100644 features/project/pages.feature create mode 100644 features/steps/project/pages.rb diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 054cc849839..eb98204285d 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -81,10 +81,6 @@ module ProjectsHelper "You are going to remove the fork relationship to source project #{@project.forked_from_project.name_with_namespace}. Are you ABSOLUTELY sure?" end - def remove_pages_message(project) - "You are going to remove the pages for #{project.name_with_namespace}.\n Are you ABSOLUTELY sure?" - end - def project_nav_tabs @nav_tabs ||= get_project_nav_tabs(@project, current_user) end diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index dd493a6d312..c560aca5725 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -3,8 +3,7 @@ .panel-heading Remove pages .errors-holder .panel-body - = form_tag(remove_pages_namespace_project_pages_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do - %p - Removing the pages will prevent from exposing them to outside world. - .form-actions - = button_to 'Remove pages', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_pages_message(@project) } + %p + Removing the pages will prevent from exposing them to outside world. + .form-actions + = link_to 'Remove', remove_pages_namespace_project_pages_path(@project.namespace, @project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" diff --git a/app/views/projects/pages/_form.html.haml b/app/views/projects/pages/_form.html.haml index c69b76c6697..fd411462330 100644 --- a/app/views/projects/pages/_form.html.haml +++ b/app/views/projects/pages/_form.html.haml @@ -12,7 +12,7 @@ = f.text_field :domain, required: true, autocomplete: 'off', class: 'form-control' %span.help-inline * required - - if Settings.pages.external_https + - if Gitlab.config.pages.external_https .form-group = f.label :certificate, class: 'control-label' do Certificate (PEM) diff --git a/app/views/projects/pages/index.html.haml b/app/views/projects/pages/index.html.haml index 284f362d535..1a5dbb79830 100644 --- a/app/views/projects/pages/index.html.haml +++ b/app/views/projects/pages/index.html.haml @@ -2,7 +2,7 @@ %h3.page_title Pages - - if Settings.pages.external_http || Settings.pages.external_https + - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https = link_to new_namespace_project_page_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Domain" do %i.fa.fa-plus New Domain @@ -14,10 +14,10 @@ %hr.clearfix -- if Settings.pages.enabled +- if Gitlab.config.pages.enabled = render 'access' = render 'use' - - if Settings.pages.external_http || Settings.pages.external_https + - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https = render 'list' - else = render 'no_domains' diff --git a/features/project/pages.feature b/features/project/pages.feature new file mode 100644 index 00000000000..392f2d29c3c --- /dev/null +++ b/features/project/pages.feature @@ -0,0 +1,73 @@ +Feature: Project Pages + Background: + Given I sign in as a user + And I own a project + + Scenario: Pages are disabled + Given pages are disabled + When I visit the Project Pages + Then I should see that GitLab Pages are disabled + + Scenario: I can see the pages usage if not deployed + Given pages are enabled + When I visit the Project Pages + Then I should see the usage of GitLab Pages + + Scenario: I can access the pages if deployed + Given pages are enabled + And pages are deployed + When I visit the Project Pages + Then I should be able to access the Pages + + Scenario: I should message that domains support is disabled + Given pages are enabled + And pages are deployed + And support for external domains is disabled + When I visit the Project Pages + Then I should see that support for domains is disabled + + Scenario: I should see a new domain button + Given pages are enabled + And pages are exposed on external HTTP address + When I visit the Project Pages + And I should be able to add a New Domain + + Scenario: I should be able to add a new domain + Given pages are enabled + And pages are exposed on external HTTP address + When I visit add a new Pages Domain + And I fill the domain + And I click on "Create New Domain" + Then I should see a new domain added + + Scenario: I should be denied to add the same domain twice + Given pages are enabled + And pages are exposed on external HTTP address + And pages domain is added + When I visit add a new Pages Domain + And I fill the domain + And I click on "Create New Domain" + Then I should see error message that domain already exists + + Scenario: I should message that certificates support is disabled when trying to add a new domain + Given pages are enabled + And pages are exposed on external HTTP address + And pages domain is added + When I visit add a new Pages Domain + Then I should see that support for certificates is disabled + + Scenario: I should be able to add a new domain with certificate + Given pages are enabled + And pages are exposed on external HTTPS address + When I visit add a new Pages Domain + And I fill the domain + And I fill the certificate and key + And I click on "Create New Domain" + Then I should see a new domain added + + Scenario: I can remove the pages if deployed + Given pages are enabled + And pages are deployed + When I visit the Project Pages + And I click Remove Pages + Then The Pages should get removed diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb new file mode 100644 index 00000000000..d484ae90bdc --- /dev/null +++ b/features/steps/project/pages.rb @@ -0,0 +1,139 @@ +class Spinach::Features::ProjectPages < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedProject + + step 'pages are enabled' do + Gitlab.config.pages.stub(:enabled).and_return(true) + Gitlab.config.pages.stub(:host).and_return('example.com') + Gitlab.config.pages.stub(:port).and_return(80) + Gitlab.config.pages.stub(:https).and_return(false) + end + + step 'pages are disabled' do + Gitlab.config.pages.stub(:enabled).and_return(false) + end + + step 'I visit the Project Pages' do + visit namespace_project_pages_path(@project.namespace, @project) + end + + step 'I should see that GitLab Pages are disabled' do + expect(page).to have_content('GitLab Pages are disabled') + end + + step 'I should see the usage of GitLab Pages' do + expect(page).to have_content('Configure pages') + end + + step 'pages are deployed' do + commit = @project.ensure_ci_commit(@project.commit('HEAD').sha) + build = build(:ci_build, + project: @project, + commit: commit, + ref: 'HEAD', + artifacts_file: fixture_file_upload(Rails.root + 'spec/fixtures/pages.zip'), + artifacts_metadata: fixture_file_upload(Rails.root + 'spec/fixtures/pages.zip.meta') + ) + result = ::Projects::UpdatePagesService.new(@project, build).execute + expect(result[:status]).to eq(:success) + end + + step 'I should be able to access the Pages' do + expect(page).to have_content('Access pages') + end + + step 'I should see that support for domains is disabled' do + expect(page).to have_content('Support for domains and certificates is disabled') + end + + step 'support for external domains is disabled' do + Gitlab.config.pages.stub(:external_http).and_return(nil) + Gitlab.config.pages.stub(:external_https).and_return(nil) + end + + step 'pages are exposed on external HTTP address' do + Gitlab.config.pages.stub(:external_http).and_return('1.1.1.1:80') + Gitlab.config.pages.stub(:external_https).and_return(nil) + end + + step 'pages are exposed on external HTTPS address' do + Gitlab.config.pages.stub(:external_http).and_return('1.1.1.1:80') + Gitlab.config.pages.stub(:external_https).and_return('1.1.1.1:443') + end + + step 'I should be able to add a New Domain' do + expect(page).to have_content('New Domain') + end + + step 'I visit add a new Pages Domain' do + visit new_namespace_project_page_path(@project.namespace, @project) + end + + step 'I fill the domain' do + fill_in 'Domain', with: 'my.test.domain.com' + end + + step 'I click on "Create New Domain"' do + click_button 'Create New Domain' + end + + step 'I should see a new domain added' do + expect(page).to have_content('Domains (1)') + expect(page).to have_content('my.test.domain.com') + end + + step 'pages domain is added' do + @project.pages_domains.create!(domain: 'my.test.domain.com') + end + + step 'I should see error message that domain already exists' do + expect(page).to have_content('Domain has already been taken') + end + + step 'I should see that support for certificates is disabled' do + expect(page).to have_content('Support for custom certificates is disabled') + end + + step 'I fill the certificate and key' do + fill_in 'Certificate (PEM)', with: '-----BEGIN CERTIFICATE----- +MIICGzCCAYSgAwIBAgIBATANBgkqhkiG9w0BAQUFADAbMRkwFwYDVQQDExB0ZXN0 +LWNlcnRpZmljYXRlMB4XDTE2MDIxMjE0MzIwMFoXDTIwMDQxMjE0MzIwMFowGzEZ +MBcGA1UEAxMQdGVzdC1jZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEApL4J9L0ZxFJ1hI1LPIflAlAGvm6ZEvoT4qKU5Xf2JgU7/2geNR1qlNFa +SvCc08Knupp5yTgmvyK/Xi09U0N82vvp4Zvr/diSc4A/RA6Mta6egLySNT438kdT +nY2tR5feoTLwQpX0t4IMlwGQGT5h6Of2fKmDxzuwuyffcIHqLdsCAwEAAaNvMG0w +DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUxl9WSxBprB0z0ibJs3rXEk0+95AwCwYD +VR0PBAQDAgXgMBEGCWCGSAGG+EIBAQQEAwIGQDAeBglghkgBhvhCAQ0EERYPeGNh +IGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4GBAGC4T8SlFHK0yPSa+idGLQFQ +joZp2JHYvNlTPkRJ/J4TcXxBTJmArcQgTIuNoBtC+0A/SwdK4MfTCUY4vNWNdese +5A4K65Nb7Oh1AdQieTBHNXXCdyFsva9/ScfQGEl7p55a52jOPs0StPd7g64uvjlg +YHi2yesCrOvVXt+lgPTd +-----END CERTIFICATE-----' + + fill_in 'Key (PEM)', with: '-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKS+CfS9GcRSdYSN +SzyH5QJQBr5umRL6E+KilOV39iYFO/9oHjUdapTRWkrwnNPCp7qaeck4Jr8iv14t +PVNDfNr76eGb6/3YknOAP0QOjLWunoC8kjU+N/JHU52NrUeX3qEy8EKV9LeCDJcB +kBk+Yejn9nypg8c7sLsn33CB6i3bAgMBAAECgYA2D26w80T7WZvazYr86BNMePpd +j2mIAqx32KZHzt/lhh40J/SRtX9+Kl0Y7nBoRR5Ja9u/HkAIxNxLiUjwg9r6cpg/ +uITEF5nMt7lAk391BuI+7VOZZGbJDsq2ulPd6lO+C8Kq/PI/e4kXcIjeH6KwQsuR +5vrXfBZ3sQfflaiN4QJBANBt8JY2LIGQF8o89qwUpRL5vbnKQ4IzZ5+TOl4RLR7O +AQpJ81tGuINghO7aunctb6rrcKJrxmEH1whzComybrMCQQDKV49nOBudRBAIgG4K +EnLzsRKISUHMZSJiYTYnablof8cKw1JaQduw7zgrUlLwnroSaAGX88+Jw1f5n2Lh +Vlg5AkBDdUGnrDLtYBCDEQYZHblrkc7ZAeCllDOWjxUV+uMqlCv8A4Ey6omvY57C +m6I8DkWVAQx8VPtozhvHjUw80rZHAkB55HWHAM3h13axKG0htCt7klhPsZHpx6MH +EPjGlXIT+aW2XiPmK3ZlCDcWIenE+lmtbOpI159Wpk8BGXs/s/xBAkEAlAY3ymgx +63BDJEwvOb2IaP8lDDxNsXx9XJNVvQbv5n15vNsLHbjslHfAhAbxnLQ1fLhUPqSi +nNp/xedE1YxutQ== +-----END PRIVATE KEY-----' + end + + step 'I click Remove Pages' do + click_link 'Remove pages' + end + + step 'The Pages should get removed' do + expect(@project.pages_url).to be_nil + end +end From 7f12cb0eed06ad3f83126a3a8038e7fa658f4eac Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 14 Feb 2016 21:22:44 +0100 Subject: [PATCH 054/183] Split PagesController into PagesController and PagesDomainsController 1. PagesController is used to show all domains and general overview of Pages 2. PagesDomainsController is used to manage pages domains --- app/controllers/projects/pages_controller.rb | 54 +------------------ .../projects/pages_domains_controller.rb | 49 +++++++++++++++++ app/views/projects/pages/_destroy.haml | 2 +- app/views/projects/pages/_list.html.haml | 4 +- app/views/projects/pages/index.html.haml | 26 --------- app/views/projects/pages/show.html.haml | 44 ++++++++------- .../{pages => pages_domains}/_form.html.haml | 2 +- .../{pages => pages_domains}/new.html.haml | 0 .../projects/pages_domains/show.html.haml | 22 ++++++++ config/routes/project.rb | 6 +-- features/steps/project/pages.rb | 2 +- 11 files changed, 104 insertions(+), 107 deletions(-) delete mode 100644 app/views/projects/pages/index.html.haml rename app/views/projects/{pages => pages_domains}/_form.html.haml (88%) rename app/views/projects/{pages => pages_domains}/new.html.haml (100%) create mode 100644 app/views/projects/pages_domains/show.html.haml diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index 2268d2d8aa2..b73f998392d 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -1,49 +1,13 @@ class Projects::PagesController < Projects::ApplicationController layout 'project_settings' - before_action :authorize_update_pages!, except: [:show] - before_action :authorize_remove_pages!, only: [:remove_pages] - before_action :label, only: [:destroy] - before_action :domain, only: [:show] + before_action :authorize_update_pages! - helper_method :valid_certificate?, :valid_certificate_key? - helper_method :valid_key_for_certificiate?, :valid_certificate_intermediates? - helper_method :certificate, :certificate_key - - def index + def show @domains = @project.pages_domains.order(:domain) end - def show - end - - def new - @domain = @project.pages_domains.new - end - - def create - @domain = @project.pages_domains.create(pages_domain_params) - - if @domain.valid? - redirect_to namespace_project_pages_path(@project.namespace, @project) - else - render 'new' - end - end - def destroy - @domain.destroy - - respond_to do |format| - format.html do - redirect_to(namespace_project_pages_path(@project.namespace, @project), - notice: 'Domain was removed') - end - format.js - end - end - - def remove_pages project.remove_pages project.pages_domains.destroy_all @@ -54,18 +18,4 @@ class Projects::PagesController < Projects::ApplicationController end end end - - private - - def pages_domain_params - params.require(:pages_domain).permit( - :certificate, - :key, - :domain - ) - end - - def domain - @domain ||= @project.pages_domains.find_by(domain: params[:id].to_s) - end end diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb index e69de29bb2d..b8c253f6ae3 100644 --- a/app/controllers/projects/pages_domains_controller.rb +++ b/app/controllers/projects/pages_domains_controller.rb @@ -0,0 +1,49 @@ +class Projects::PagesDomainsController < Projects::ApplicationController + layout 'project_settings' + + before_action :authorize_update_pages!, except: [:show] + before_action :domain, only: [:show, :destroy] + + def show + end + + def new + @domain = @project.pages_domains.new + end + + def create + @domain = @project.pages_domains.create(pages_domain_params) + + if @domain.valid? + redirect_to namespace_project_pages_path(@project.namespace, @project) + else + render 'new' + end + end + + def destroy + @domain.destroy + + respond_to do |format| + format.html do + redirect_to(namespace_project_pages_path(@project.namespace, @project), + notice: 'Domain was removed') + end + format.js + end + end + + private + + def pages_domain_params + params.require(:pages_domain).permit( + :certificate, + :key, + :domain + ) + end + + def domain + @domain ||= @project.pages_domains.find_by(domain: params[:id].to_s) + end +end diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index c560aca5725..0cd25f82cd4 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -6,4 +6,4 @@ %p Removing the pages will prevent from exposing them to outside world. .form-actions - = link_to 'Remove', remove_pages_namespace_project_pages_path(@project.namespace, @project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" + = link_to 'Remove pages', namespace_project_pages_path(@project.namespace, @project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml index e88a001d636..c1a6948a574 100644 --- a/app/views/projects/pages/_list.html.haml +++ b/app/views/projects/pages/_list.html.haml @@ -6,8 +6,8 @@ - @domains.each do |domain| %li .pull-right - = link_to 'Details', namespace_project_page_path(@project.namespace, @project, domain), class: "btn btn-sm btn-grouped" - = link_to 'Remove', namespace_project_page_path(@project.namespace, @project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" + = link_to 'Details', namespace_project_pages_domain_path(@project.namespace, @project, domain), class: "btn btn-sm btn-grouped" + = link_to 'Remove', namespace_project_pages_domain_path(@project.namespace, @project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" .clearfix %span= link_to domain.domain, domain.url %p diff --git a/app/views/projects/pages/index.html.haml b/app/views/projects/pages/index.html.haml deleted file mode 100644 index 1a5dbb79830..00000000000 --- a/app/views/projects/pages/index.html.haml +++ /dev/null @@ -1,26 +0,0 @@ -- page_title "Pages" -%h3.page_title - Pages - - - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https - = link_to new_namespace_project_page_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Domain" do - %i.fa.fa-plus - New Domain - -%p.light - With GitLab Pages you can host for free your static websites on GitLab. - Combined with the power of GitLab CI and the help of GitLab Runner - you can deploy static pages for your individual projects, your user or your group. - -%hr.clearfix - -- if Gitlab.config.pages.enabled - = render 'access' - = render 'use' - - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https - = render 'list' - - else - = render 'no_domains' - = render 'destroy' -- else - = render 'disabled' diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index 8b7010b75b2..9be6f8678cf 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -1,22 +1,26 @@ -- page_title "#{@domain.domain}", "Pages Domain" +- page_title "Pages" +%h3.page_title + Pages -%h3.page-title - Pages Domain + - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https + = link_to new_namespace_project_pages_domain_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Domain" do + %i.fa.fa-plus + New Domain -.table-holder - %table.table - %tr - %td - Domain - %td - = link_to @domain.domain, @domain.url - %tr - %td - Certificate - %td - - if @domain.certificate_text - %pre - = @domain.certificate_text - - else - .light - missing +%p.light + With GitLab Pages you can host for free your static websites on GitLab. + Combined with the power of GitLab CI and the help of GitLab Runner + you can deploy static pages for your individual projects, your user or your group. + +%hr.clearfix + +- if Gitlab.config.pages.enabled + = render 'access' + = render 'use' + - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https + = render 'list' + - else + = render 'no_domains' + = render 'destroy' +- else + = render 'disabled' diff --git a/app/views/projects/pages/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml similarity index 88% rename from app/views/projects/pages/_form.html.haml rename to app/views/projects/pages_domains/_form.html.haml index fd411462330..5458f9e7734 100644 --- a/app/views/projects/pages/_form.html.haml +++ b/app/views/projects/pages_domains/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@domain], url: namespace_project_pages_path(@project.namespace, @project), html: { class: 'form-horizontal fieldset-form' } do |f| += form_for [@project.namespace, @project, @domain], html: { class: 'form-horizontal fieldset-form' } do |f| - if @domain.errors.any? #error_explanation .alert.alert-danger diff --git a/app/views/projects/pages/new.html.haml b/app/views/projects/pages_domains/new.html.haml similarity index 100% rename from app/views/projects/pages/new.html.haml rename to app/views/projects/pages_domains/new.html.haml diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml new file mode 100644 index 00000000000..8b7010b75b2 --- /dev/null +++ b/app/views/projects/pages_domains/show.html.haml @@ -0,0 +1,22 @@ +- page_title "#{@domain.domain}", "Pages Domain" + +%h3.page-title + Pages Domain + +.table-holder + %table.table + %tr + %td + Domain + %td + = link_to @domain.domain, @domain.url + %tr + %td + Certificate + %td + - if @domain.certificate_text + %pre + = @domain.certificate_text + - else + .light + missing diff --git a/config/routes/project.rb b/config/routes/project.rb index 7a41cb81bfa..ea3bfdd45e6 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -39,10 +39,8 @@ constraints(ProjectUrlConstrainer.new) do end end - resources :pages, except: [:edit, :update] do - collection do - delete :remove_pages - end + resource :pages, only: [:show, :destroy] do + resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains' end resources :compare, only: [:index, :create] do diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb index d484ae90bdc..a5cb81b0ef3 100644 --- a/features/steps/project/pages.rb +++ b/features/steps/project/pages.rb @@ -67,7 +67,7 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps end step 'I visit add a new Pages Domain' do - visit new_namespace_project_page_path(@project.namespace, @project) + visit new_namespace_project_pages_domain_path(@project.namespace, @project) end step 'I fill the domain' do From c6723ed3e018d6249ca9409ebd08c04bd76dea97 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 15 Feb 2016 14:44:54 +0100 Subject: [PATCH 055/183] Updated configuration saving --- .../projects/update_pages_configuration_service.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index b5324587d0e..188847b5ad6 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -7,7 +7,7 @@ module Projects end def execute - update_file(pages_config_file, pages_config) + update_file(pages_config_file, pages_config.to_json) reload_daemon success rescue => e @@ -52,18 +52,18 @@ module Projects def update_file(file, data) unless data - File.rm(file, force: true) + FileUtils.remove(file, force: true) return end temp_file = "#{file}.#{SecureRandom.hex(16)}" - File.open(temp_file, 'w') do |file| - file.write(data) + File.open(temp_file, 'w') do |f| + f.write(data) end - File.mv(temp_file, file, force: true) + FileUtils.move(temp_file, file, force: true) ensure # In case if the updating fails - File.rm(temp_file, force: true) + FileUtils.remove(temp_file, force: true) end end end From fd7756ec3741e503197d451927849a09526423f6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 15 Feb 2016 14:45:11 +0100 Subject: [PATCH 056/183] Added information about the CNAME record --- app/views/projects/pages_domains/show.html.haml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml index 8b7010b75b2..e4c5922d863 100644 --- a/app/views/projects/pages_domains/show.html.haml +++ b/app/views/projects/pages_domains/show.html.haml @@ -10,6 +10,14 @@ Domain %td = link_to @domain.domain, @domain.url + %tr + %td + DNS + %td + %p + To access the domain create a new DNS record: + %pre + #{@domain.domain} CNAME #{@domain.project.namespace.path}.#{Settings.pages.host}. %tr %td Certificate From 361047a7911dbf5da3c33aefa5c77a43621e5514 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 15 Feb 2016 15:01:42 +0100 Subject: [PATCH 057/183] Updated according to comments --- app/models/pages_domain.rb | 4 ++-- app/services/projects/update_pages_service.rb | 3 +-- app/views/projects/pages/show.html.haml | 6 +++--- app/views/projects/pages_domains/_form.html.haml | 7 +++---- app/views/projects/pages_domains/new.html.haml | 2 +- app/views/projects/pages_domains/show.html.haml | 2 +- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index 83fdc1c630d..9155e57331d 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -24,9 +24,9 @@ class PagesDomain < ActiveRecord::Base return unless domain if certificate - return "https://#{domain}" + "https://#{domain}" else - return "http://#{domain}" + "http://#{domain}" end end diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index a9979bf1e96..ceabd29fd52 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -1,6 +1,5 @@ module Projects - class - UpdatePagesService < BaseService + class UpdatePagesService < BaseService BLOCK_SIZE = 32.kilobytes MAX_SIZE = 1.terabyte SITE_PATH = 'public/' diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index 9be6f8678cf..f4ca33f418b 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -1,14 +1,14 @@ -- page_title "Pages" +- page_title 'Pages' %h3.page_title Pages - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https - = link_to new_namespace_project_pages_domain_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Domain" do + = link_to new_namespace_project_pages_domain_path(@project.namespace, @project), class: 'btn btn-new pull-right', title: 'New Domain' do %i.fa.fa-plus New Domain %p.light - With GitLab Pages you can host for free your static websites on GitLab. + With GitLab Pages you can host your static websites on GitLab. Combined with the power of GitLab CI and the help of GitLab Runner you can deploy static pages for your individual projects, your user or your group. diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml index 5458f9e7734..e97d19653d5 100644 --- a/app/views/projects/pages_domains/_form.html.haml +++ b/app/views/projects/pages_domains/_form.html.haml @@ -10,22 +10,21 @@ Domain .col-sm-10 = f.text_field :domain, required: true, autocomplete: 'off', class: 'form-control' - %span.help-inline * required - if Gitlab.config.pages.external_https .form-group = f.label :certificate, class: 'control-label' do Certificate (PEM) .col-sm-10 - = f.text_area :certificate, rows: 5, class: 'form-control', value: '' + = f.text_area :certificate, rows: 5, class: 'form-control' %span.help-inline Upload a certificate for your domain with all intermediates .form-group = f.label :key, class: 'control-label' do Key (PEM) .col-sm-10 - = f.text_area :key, rows: 5, class: 'form-control', value: '' - %span.help-inline Upload a certificate for your domain with all intermediates + = f.text_area :key, rows: 5, class: 'form-control' + %span.help-inline Upload a private key for your certificate - else .nothing-here-block Support for custom certificates is disabled. diff --git a/app/views/projects/pages_domains/new.html.haml b/app/views/projects/pages_domains/new.html.haml index 2609df62aac..e1477c71d06 100644 --- a/app/views/projects/pages_domains/new.html.haml +++ b/app/views/projects/pages_domains/new.html.haml @@ -1,4 +1,4 @@ -- page_title 'Pages' +- page_title 'New Pages Domain' %h3.page_title New Pages Domain %hr.clearfix diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml index e4c5922d863..52dddb052a7 100644 --- a/app/views/projects/pages_domains/show.html.haml +++ b/app/views/projects/pages_domains/show.html.haml @@ -1,4 +1,4 @@ -- page_title "#{@domain.domain}", "Pages Domain" +- page_title "#{@domain.domain}", 'Pages Domains' %h3.page-title Pages Domain From 4d2337175872217eb22f035c3fcd981a38e8a374 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 16 Feb 2016 10:48:51 +0100 Subject: [PATCH 058/183] Final fixes --- config/gitlab.yml.example | 4 ++-- features/steps/project/pages.rb | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index f2bde602795..1cf24e3f3db 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -165,8 +165,8 @@ production: &base host: example.com port: 80 # Set to 443 if you serve the pages with HTTPS https: false # Set to true if you serve the pages with HTTPS - # external_http: "1.1.1.1:80" # if defined notifies the GitLab pages do support Custom Domains - # external_https: "1.1.1.1:443" # if defined notifies the GitLab pages do support Custom Domains with Certificates + # external_http: "1.1.1.1:80" # If defined, enables custom domain support in GitLab Pages + # external_https: "1.1.1.1:443" # If defined, enables custom domain and certificate support in GitLab Pages ## Mattermost ## For enabling Add to Mattermost button diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb index a5cb81b0ef3..ac44aac9e38 100644 --- a/features/steps/project/pages.rb +++ b/features/steps/project/pages.rb @@ -4,14 +4,14 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps include SharedProject step 'pages are enabled' do - Gitlab.config.pages.stub(:enabled).and_return(true) - Gitlab.config.pages.stub(:host).and_return('example.com') - Gitlab.config.pages.stub(:port).and_return(80) - Gitlab.config.pages.stub(:https).and_return(false) + allow(Gitlab.config.pages).to receive(:enabled).and_return(true) + allow(Gitlab.config.pages).to receive(:host).and_return('example.com') + allow(Gitlab.config.pages).to receive(:port).and_return(80) + allow(Gitlab.config.pages).to receive(:https).and_return(false) end step 'pages are disabled' do - Gitlab.config.pages.stub(:enabled).and_return(false) + allow(Gitlab.config.pages).to receive(:enabled).and_return(false) end step 'I visit the Project Pages' do @@ -48,18 +48,18 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps end step 'support for external domains is disabled' do - Gitlab.config.pages.stub(:external_http).and_return(nil) - Gitlab.config.pages.stub(:external_https).and_return(nil) + allow(Gitlab.config.pages).to receive(:external_http).and_return(nil) + allow(Gitlab.config.pages).to receive(:external_https).and_return(nil) end step 'pages are exposed on external HTTP address' do - Gitlab.config.pages.stub(:external_http).and_return('1.1.1.1:80') - Gitlab.config.pages.stub(:external_https).and_return(nil) + allow(Gitlab.config.pages).to receive(:external_http).and_return('1.1.1.1:80') + allow(Gitlab.config.pages).to receive(:external_https).and_return(nil) end step 'pages are exposed on external HTTPS address' do - Gitlab.config.pages.stub(:external_http).and_return('1.1.1.1:80') - Gitlab.config.pages.stub(:external_https).and_return('1.1.1.1:443') + allow(Gitlab.config.pages).to receive(:external_http).and_return('1.1.1.1:80') + allow(Gitlab.config.pages).to receive(:external_https).and_return('1.1.1.1:443') end step 'I should be able to add a New Domain' do From 3bcb65c9f2ceaa4213c6d3eb1641ecb0f07d35ad Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 16 Feb 2016 13:32:38 +0100 Subject: [PATCH 059/183] Added pages version [ci skip] --- GITLAB_PAGES_VERSION | 1 + 1 file changed, 1 insertion(+) create mode 100644 GITLAB_PAGES_VERSION diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION new file mode 100644 index 00000000000..6e8bf73aa55 --- /dev/null +++ b/GITLAB_PAGES_VERSION @@ -0,0 +1 @@ +0.1.0 From 8f09ec28379da331fb5bd4a4da950def7b83dd94 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 16 Feb 2016 14:39:58 +0100 Subject: [PATCH 060/183] Verify trusted certificate chain --- spec/factories/pages_domains.rb | 78 +++++++++++++++++++++++++++++++- spec/models/pages_domain_spec.rb | 14 ++++-- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb index 4608867087c..ff72df8dc02 100644 --- a/spec/factories/pages_domains.rb +++ b/spec/factories/pages_domains.rb @@ -38,8 +38,9 @@ nNp/xedE1YxutQ== -----END PRIVATE KEY-----' end - trait :with_certificate_chain do + trait :with_missing_chain do # This certificate is signed with different key + # And misses the CA to build trust chain certificate '-----BEGIN CERTIFICATE----- MIIDGTCCAgGgAwIBAgIBAjANBgkqhkiG9w0BAQUFADASMRAwDgYDVQQDEwdUZXN0 IENBMB4XDTE2MDIxMjE0MjMwMFoXDTE3MDIxMTE0MjMwMFowHTEbMBkGA1UEAxMS @@ -61,6 +62,81 @@ VM+d42lQAgoq318A84Xu5vRh1KCAJuztkhNbM+w= -----END CERTIFICATE-----' end + trait :with_trusted_chain do + # This is + # [Intermediate #2 (SHA-2)] 'Comodo RSA Domain Validation Secure Server CA' + # [Intermediate #1 (SHA-2)] COMODO RSA Certification Authority + # We only validate that we want to rebuild the trust chain, + # we don't need end-to-end certificate to do that + certificate '-----BEGIN CERTIFICATE----- +MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy +MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh +bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh +bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0 +Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6 +ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51 +UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n +c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY +MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz +30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV +HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG +BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv +bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB +AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E +T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v +ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p +mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/ +e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps +P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY +dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc +2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG +V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4 +HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX +j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII +0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap +lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf ++AZxAeKCINT+b72x +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv +MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk +ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF +eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow +gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO +BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD +VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw +AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6 +2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr +ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt +4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq +m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/ +vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT +8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE +IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO +KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO +GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/ +s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g +JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD +AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9 +MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy +bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6 +Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ +zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj +Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY +Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5 +B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx +PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR +pu/xO28QOG8= +-----END CERTIFICATE-----' + end + trait :with_expired_certificate do certificate '-----BEGIN CERTIFICATE----- MIIBsDCCARmgAwIBAgIBATANBgkqhkiG9w0BAQUFADAeMRwwGgYDVQQDExNleHBp diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index 929b2a26549..3e083ba9001 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -63,7 +63,7 @@ describe PagesDomain, models: true do end context 'for not matching key' do - let(:domain) { build(:pages_domain, :with_certificate_chain, :with_key) } + let(:domain) { build(:pages_domain, :with_missing_chain, :with_key) } it { is_expected.to_not be_valid } end @@ -95,7 +95,7 @@ describe PagesDomain, models: true do end context 'for invalid key' do - let(:domain) { build(:pages_domain, :with_certificate_chain, :with_key) } + let(:domain) { build(:pages_domain, :with_missing_chain, :with_key) } it { is_expected.to be_falsey } end @@ -110,11 +110,17 @@ describe PagesDomain, models: true do it { is_expected.to be_truthy } end - context 'for certificate chain without the root' do - let(:domain) { build(:pages_domain, :with_certificate_chain) } + context 'for missing certificate chain' do + let(:domain) { build(:pages_domain, :with_missing_chain) } it { is_expected.to be_falsey } end + + context 'for trusted certificate chain' do + let(:domain) { build(:pages_domain, :with_trusted_chain) } + + it { is_expected.to be_truthy } + end end describe :expired? do From 63eb415610b151495ac54e98804ce37ba5500be4 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 16 Feb 2016 14:40:54 +0100 Subject: [PATCH 061/183] Fix certificate validators --- app/validators/certificate_key_validator.rb | 2 +- app/validators/certificate_validator.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/validators/certificate_key_validator.rb b/app/validators/certificate_key_validator.rb index 7039bd5a621..098b16017d2 100644 --- a/app/validators/certificate_key_validator.rb +++ b/app/validators/certificate_key_validator.rb @@ -16,7 +16,7 @@ class CertificateKeyValidator < ActiveModel::EachValidator private def valid_private_key_pem?(value) - return unless value + return false unless value pkey = OpenSSL::PKey::RSA.new(value) pkey.private? rescue OpenSSL::PKey::PKeyError diff --git a/app/validators/certificate_validator.rb b/app/validators/certificate_validator.rb index 2a04c76d4b9..e3d18097f71 100644 --- a/app/validators/certificate_validator.rb +++ b/app/validators/certificate_validator.rb @@ -16,9 +16,9 @@ class CertificateValidator < ActiveModel::EachValidator private def valid_certificate_pem?(value) - return unless value - OpenSSL::X509::Certificate.new(value) + return false unless value + OpenSSL::X509::Certificate.new(value).present? rescue OpenSSL::X509::CertificateError - nil + false end end From 92a1efe69b8d3a491e14b5d583033ec7ebd4c623 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 16 Feb 2016 19:13:28 +0100 Subject: [PATCH 062/183] Use GitLab Pages 0.2.0 --- GITLAB_PAGES_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index 6e8bf73aa55..0ea3a944b39 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -0.1.0 +0.2.0 From c089f103342ae8f60c7fa9055ef79e3245d6a5fb Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 17 Feb 2016 10:05:26 +0100 Subject: [PATCH 063/183] Update comments --- features/steps/project/pages.rb | 2 +- spec/factories/pages_domains.rb | 6 ++---- spec/models/pages_domain_spec.rb | 4 ++++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb index ac44aac9e38..b3a6b93c5d0 100644 --- a/features/steps/project/pages.rb +++ b/features/steps/project/pages.rb @@ -34,7 +34,7 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps ref: 'HEAD', artifacts_file: fixture_file_upload(Rails.root + 'spec/fixtures/pages.zip'), artifacts_metadata: fixture_file_upload(Rails.root + 'spec/fixtures/pages.zip.meta') - ) + ) result = ::Projects::UpdatePagesService.new(@project, build).execute expect(result[:status]).to eq(:success) end diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb index ff72df8dc02..6d2e45f41ba 100644 --- a/spec/factories/pages_domains.rb +++ b/spec/factories/pages_domains.rb @@ -63,11 +63,9 @@ VM+d42lQAgoq318A84Xu5vRh1KCAJuztkhNbM+w= end trait :with_trusted_chain do - # This is + # This contains # [Intermediate #2 (SHA-2)] 'Comodo RSA Domain Validation Secure Server CA' - # [Intermediate #1 (SHA-2)] COMODO RSA Certification Authority - # We only validate that we want to rebuild the trust chain, - # we don't need end-to-end certificate to do that + # [Intermediate #1 (SHA-2)] 'COMODO RSA Certification Authority' certificate '-----BEGIN CERTIFICATE----- MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index 3e083ba9001..0b95bf594c5 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -117,6 +117,10 @@ describe PagesDomain, models: true do end context 'for trusted certificate chain' do + # We only validate that we can to rebuild the trust chain, for certificates + # We assume that 'AddTrustExternalCARoot' needed to validate the chain is in trusted store. + # It will be if ca-certificates is installed on Debian/Ubuntu/Alpine + let(:domain) { build(:pages_domain, :with_trusted_chain) } it { is_expected.to be_truthy } From 492627c987fd167c956df49843e741cbe29fd77a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 19 Feb 2016 15:11:03 +0100 Subject: [PATCH 064/183] Fix the URL of group pages --- app/models/project.rb | 10 +++++++--- doc/pages/README.md | 8 ++++++++ spec/models/project_spec.rb | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index dac52a0fc5e..73a642e1580 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1167,12 +1167,16 @@ class Project < ActiveRecord::Base def pages_url return unless Dir.exist?(public_pages_path) - host = "#{namespace.path}.#{Settings.pages.host}" + # The hostname always needs to be in downcased + # All web servers convert hostname to lowercase + host = "#{namespace.path}.#{Settings.pages.host}".downcase + + # The host in URL always needs to be downcased url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix| "#{prefix}#{namespace.path}." - end + end.downcase - # If the project path is the same as host, leave the short version + # If the project path is the same as host, we serve it as group page return url if host == path "#{url}/#{path}" diff --git a/doc/pages/README.md b/doc/pages/README.md index f6eb8ccb7a7..eb4217e0d1a 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -45,6 +45,14 @@ URL it will be accessible. | Specific project under a user's page | `walter/area51` | `https://walter.gitlab.io/area51` | | Specific project under a group's page | `therug/welovecats` | `https://therug.gitlab.io/welovecats` | +## Group pages + +You can create a group page in context of your group. +The project for group page must be written in lower. + +If you have a group `TheRug` and pages are hosted under `Example.com` in order to create a group page +create a new project named `therug.example.com`. + ## Enable the pages feature in your project The GitLab Pages feature needs to be explicitly enabled for each project diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 5fde9194e93..cf45ee54fa4 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1845,4 +1845,37 @@ describe Project, models: true do def enable_lfs allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) end + + describe :pages_url do + let(:group) { create :group, name: group_name } + let(:project) { create :empty_project, namespace: group, name: project_name } + let(:domain) { 'Example.com' } + + subject { project.pages_url } + + before do + FileUtils.mkdir_p(project.public_pages_path) + + allow(Settings.pages).to receive(:host).and_return(domain) + allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com') + end + + after do + FileUtils.rmdir(project.public_pages_path) + end + + context 'group page' do + let(:group_name) { 'Group' } + let(:project_name) { 'group.example.com' } + + it { is_expected.to eq("http://group.example.com") } + end + + context 'project page' do + let(:group_name) { 'Group' } + let(:project_name) { 'Project' } + + it { is_expected.to eq("http://group.example.com/project") } + end + end end From 3e6cbcdd00017acae132daafa5af35f16bf48e3c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 19 Feb 2016 15:11:26 +0100 Subject: [PATCH 065/183] Fix pages abilities --- app/controllers/projects/pages_controller.rb | 3 ++- app/policies/project_policy.rb | 2 ++ app/views/projects/pages/_destroy.haml | 2 ++ app/views/projects/pages/_list.html.haml | 2 +- app/views/projects/pages/_no_domains.html.haml | 13 +++++++------ app/views/projects/pages/show.html.haml | 2 +- doc/user/permissions.md | 3 +++ 7 files changed, 18 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index b73f998392d..fbd18b68141 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -1,7 +1,8 @@ class Projects::PagesController < Projects::ApplicationController layout 'project_settings' - before_action :authorize_update_pages! + before_action :authorize_read_pages!, only: [:show] + before_action :authorize_update_pages!, except: [:show] def show @domains = @project.pages_domains.order(:domain) diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index ca5b39a001f..f5fd50745aa 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -110,6 +110,8 @@ class ProjectPolicy < BasePolicy can! :admin_pipeline can! :admin_environment can! :admin_deployment + can! :admin_pages + can! :read_pages can! :update_pages end diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index 0cd25f82cd4..896a86712a1 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -7,3 +7,5 @@ Removing the pages will prevent from exposing them to outside world. .form-actions = link_to 'Remove pages', namespace_project_pages_path(@project.namespace, @project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" +- else + .nothing-here-block Only the project owner can remove pages diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml index c1a6948a574..4f2dd1a1398 100644 --- a/app/views/projects/pages/_list.html.haml +++ b/app/views/projects/pages/_list.html.haml @@ -1,4 +1,4 @@ -- if @domains.any? +- if can?(current_user, :update_pages, @project) && @domains.any? .panel.panel-default .panel-heading Domains (#{@domains.count}) diff --git a/app/views/projects/pages/_no_domains.html.haml b/app/views/projects/pages/_no_domains.html.haml index 5a18740346a..7cea5f3e70b 100644 --- a/app/views/projects/pages/_no_domains.html.haml +++ b/app/views/projects/pages/_no_domains.html.haml @@ -1,6 +1,7 @@ -.panel.panel-default - .panel-heading - Domains - .nothing-here-block - Support for domains and certificates is disabled. - Ask your system's administrator to enable it. +- if can?(current_user, :update_pages, @project) + .panel.panel-default + .panel-heading + Domains + .nothing-here-block + Support for domains and certificates is disabled. + Ask your system's administrator to enable it. diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index f4ca33f418b..b6595269b06 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -2,7 +2,7 @@ %h3.page_title Pages - - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https + - if can?(current_user, :update_pages, @project) && (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https) = link_to new_namespace_project_pages_domain_path(@project.namespace, @project), class: 'btn btn-new pull-right', title: 'New Domain' do %i.fa.fa-plus New Domain diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 678fc3ffd1f..e87cae092a5 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -62,11 +62,14 @@ The following table depicts the various user permission levels in a project. | Manage runners | | | | ✓ | ✓ | | Manage build triggers | | | | ✓ | ✓ | | Manage variables | | | | ✓ | ✓ | +| Manage pages | | | | ✓ | ✓ | +| Manage pages domains and certificates | | | | ✓ | ✓ | | Switch visibility level | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ | | Remove project | | | | | ✓ | | Force push to protected branches [^3] | | | | | | | Remove protected branches [^3] | | | | | | +| Remove pages | | | | | ✓ | ## Group From 8a861c87bf8ba71d5c1a479c8118d9ed6aaf8e88 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 19 Feb 2016 20:07:27 +0100 Subject: [PATCH 066/183] Describe #pages_url instead of :pages_url --- spec/models/project_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index cf45ee54fa4..bb4d82a4df1 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1846,7 +1846,7 @@ describe Project, models: true do allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) end - describe :pages_url do + describe '#pages_url' do let(:group) { create :group, name: group_name } let(:project) { create :empty_project, namespace: group, name: project_name } let(:domain) { 'Example.com' } From 06d96a9a624d31294bdf16a4662aaa7121274061 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 19 Feb 2016 20:12:56 +0100 Subject: [PATCH 067/183] Introduce pages_deployed? to Project model --- app/models/project.rb | 6 +++-- app/views/projects/pages/_access.html.haml | 2 +- app/views/projects/pages/_destroy.haml | 2 +- app/views/projects/pages/_use.html.haml | 2 +- spec/models/project_spec.rb | 23 ++++++++++++++----- .../projects/update_pages_service_spec.rb | 12 +++++----- 6 files changed, 30 insertions(+), 17 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 73a642e1580..a1034e80b6c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1164,9 +1164,11 @@ class Project < ActiveRecord::Base ensure_runners_token! end - def pages_url - return unless Dir.exist?(public_pages_path) + def pages_deployed? + Dir.exist?(public_pages_path) + end + def pages_url # The hostname always needs to be in downcased # All web servers convert hostname to lowercase host = "#{namespace.path}.#{Settings.pages.host}".downcase diff --git a/app/views/projects/pages/_access.html.haml b/app/views/projects/pages/_access.html.haml index 9740877b214..82e20eeebb3 100644 --- a/app/views/projects/pages/_access.html.haml +++ b/app/views/projects/pages/_access.html.haml @@ -1,4 +1,4 @@ -- if @project.pages_url +- if @project.pages_deployed? .panel.panel-default .panel-heading Access pages diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index 896a86712a1..6a7b6baf767 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -1,4 +1,4 @@ -- if can?(current_user, :remove_pages, @project) && @project.pages_url +- if can?(current_user, :remove_pages, @project) && @project.pages_deployed? .panel.panel-default.panel.panel-danger .panel-heading Remove pages .errors-holder diff --git a/app/views/projects/pages/_use.html.haml b/app/views/projects/pages/_use.html.haml index ee38f45d44d..9db46f0b1fc 100644 --- a/app/views/projects/pages/_use.html.haml +++ b/app/views/projects/pages/_use.html.haml @@ -1,4 +1,4 @@ -- unless @project.pages_url +- unless @project.pages_deployed? .panel.panel-info .panel-heading Configure pages diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index bb4d82a4df1..558674b5b39 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1068,6 +1068,23 @@ describe Project, models: true do end end + describe '#pages_deployed?' do + let(:project) { create :empty_project } + + subject { project.pages_deployed? } + + context 'if public folder does exist' do + before { FileUtils.mkdir_p(project.public_pages_path) } + after { FileUtils.rmdir(project.public_pages_path) } + + it { is_expected.to be_truthy } + end + + context "if public folder doesn't exist" do + it { is_expected.to be_falsey } + end + end + describe '.search' do let(:project) { create(:empty_project, description: 'kitten mittens') } @@ -1854,16 +1871,10 @@ describe Project, models: true do subject { project.pages_url } before do - FileUtils.mkdir_p(project.public_pages_path) - allow(Settings.pages).to receive(:host).and_return(domain) allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com') end - after do - FileUtils.rmdir(project.public_pages_path) - end - context 'group page' do let(:group_name) { 'Group' } let(:project_name) { 'group.example.com' } diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index 68e66866340..51da582c497 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -27,9 +27,9 @@ describe Projects::UpdatePagesService do end it 'succeeds' do - expect(project.pages_url).to be_nil + expect(project.pages_deployed?).to be_falsey expect(execute).to eq(:success) - expect(project.pages_url).to_not be_nil + expect(project.pages_deployed?).to be_truthy end it 'limits pages size' do @@ -39,11 +39,11 @@ describe Projects::UpdatePagesService do it 'removes pages after destroy' do expect(PagesWorker).to receive(:perform_in) - expect(project.pages_url).to be_nil + expect(project.pages_deployed?).to be_falsey expect(execute).to eq(:success) - expect(project.pages_url).to_not be_nil + expect(project.pages_deployed?).to be_truthy project.destroy - expect(Dir.exist?(project.public_pages_path)).to be_falsey + expect(project.pages_deployed?).to be_falsey end it 'fails if sha on branch is not latest' do @@ -61,7 +61,7 @@ describe Projects::UpdatePagesService do it 'fails to remove project pages when no pages is deployed' do expect(PagesWorker).to_not receive(:perform_in) - expect(project.pages_url).to be_nil + expect(project.pages_deployed?).to be_falsey project.destroy end From 861129c33ae1a1c4c3122832033c838d4af5d88d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 19 Feb 2016 20:13:58 +0100 Subject: [PATCH 068/183] Mock Dir::exist? in project_spec.rb --- spec/models/project_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 558674b5b39..591ea314142 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1074,8 +1074,7 @@ describe Project, models: true do subject { project.pages_deployed? } context 'if public folder does exist' do - before { FileUtils.mkdir_p(project.public_pages_path) } - after { FileUtils.rmdir(project.public_pages_path) } + before { allow(Dir).to receive(:exist?).with(project.public_pages_path).and_return(true) } it { is_expected.to be_truthy } end From a621b9d5dff7dfb2418a473473df6e6011dfc63a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 19 Feb 2016 20:17:10 +0100 Subject: [PATCH 069/183] Update docs --- doc/pages/README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index eb4217e0d1a..c9d0d7e2b49 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -47,11 +47,10 @@ URL it will be accessible. ## Group pages -You can create a group page in context of your group. -The project for group page must be written in lower. +To create a page for a group, add a new project to it. The project name must be lowercased. -If you have a group `TheRug` and pages are hosted under `Example.com` in order to create a group page -create a new project named `therug.example.com`. +For example, if you have a group called `TheRug` and pages are hosted under `Example.com`, +create a project named `therug.example.com`. ## Enable the pages feature in your project From c634ff42ae26ed8e33a70a4c5cb75b38f68644fc Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 19 Feb 2016 22:17:15 +0100 Subject: [PATCH 070/183] Fix broken feature tests --- features/steps/project/pages.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb index b3a6b93c5d0..34f97f1ea8b 100644 --- a/features/steps/project/pages.rb +++ b/features/steps/project/pages.rb @@ -134,6 +134,6 @@ nNp/xedE1YxutQ== end step 'The Pages should get removed' do - expect(@project.pages_url).to be_nil + expect(@project.pages_deployed?).to be_falsey end end From d5ccea0286b229ba64a50e4576a68674d83ef30b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 20 Feb 2016 23:29:40 +0200 Subject: [PATCH 071/183] Add init scripts for GitLab Pages daemon --- lib/support/init.d/gitlab | 61 ++++++++++++++++++++--- lib/support/init.d/gitlab.default.example | 24 +++++++++ 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 31b00ff128a..38a9ab194d1 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -89,6 +89,13 @@ check_pids(){ mpid=0 fi fi + if [ "$gitlab_pages_enabled" = true ]; then + if [ -f "$gitlab_pages_pid_path" ]; then + gppid=$(cat "$gitlab_pages_pid_path") + else + gppid=0 + fi + fi } ## Called when we have started the two processes and are waiting for their pid files. @@ -144,7 +151,15 @@ check_status(){ mail_room_status="-1" fi fi - if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && [ $gitlab_workhorse_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ]; }; then + if [ "$gitlab_pages_enabled" = true ]; then + if [ $gppid -ne 0 ]; then + kill -0 "$gppid" 2>/dev/null + gitlab_pages_status="$?" + else + gitlab_pages_status="-1" + fi + fi + if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && [ $gitlab_workhorse_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ]; } && { [ "$gitlab_pages_enabled" != true ] || [ $gitlab_pages_status = 0 ]; }; then gitlab_status=0 else # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html @@ -186,12 +201,19 @@ check_stale_pids(){ exit 1 fi fi + if [ "$gitlab_pages_enabled" = true ] && [ "$gppid" != "0" ] && [ "$gitlab_pages_status" != "0" ]; then + echo "Removing stale GitLab Pages job dispatcher pid. This is most likely caused by GitLab Pages crashing the last time it ran." + if ! rm "$gitlab_pages_pid_path"; then + echo "Unable to remove stale pid, exiting" + exit 1 + fi + fi } ## If no parts of the service is running, bail out. exit_if_not_running(){ check_stale_pids - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; }; then echo "GitLab is not running." exit fi @@ -213,6 +235,9 @@ start_gitlab() { if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then echo "Starting GitLab MailRoom" fi + if [ "gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" != "0" ]; then + echo "Starting GitLab Pages" + fi # Then check if the service is running. If it is: don't start again. if [ "$web_status" = "0" ]; then @@ -252,6 +277,16 @@ start_gitlab() { fi fi + if [ "$gitlab_pages_enabled" = true ]; then + if [ "$gitlab_pages_status" = "0" ]; then + echo "The GitLab Pages is already running with pid $spid, not restarting" + else + $app_root/bin/daemon_with_pidfile $gitlab_pages_pid_path \ + $gitlab_pages_dir/gitlab-pages $gitlab_pages_options \ + >> $gitlab_pages_log 2>&1 & + fi + fi + # Wait for the pids to be planted wait_for_pids # Finally check the status to tell wether or not GitLab is running @@ -278,13 +313,17 @@ stop_gitlab() { echo "Shutting down GitLab MailRoom" RAILS_ENV=$RAILS_ENV bin/mail_room stop fi + if [ "$gitlab_pages_status" = "0" ]; then + echo "Shutting down gitlab-pages" + kill -- $(cat $gitlab_pages_pid_path) + fi # If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script. - while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; }; do + while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; } || { [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" = "0" ]; }; do sleep 1 check_status printf "." - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; }; then printf "\n" break fi @@ -298,6 +337,7 @@ stop_gitlab() { if [ "$mail_room_enabled" = true ]; then rm "$mail_room_pid_path" 2>/dev/null fi + rm -f "$gitlab_pages_pid_path" print_status } @@ -305,7 +345,7 @@ stop_gitlab() { ## Prints the status of GitLab and its components. print_status() { check_status - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; }; then echo "GitLab is not running." return fi @@ -331,7 +371,14 @@ print_status() { printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n" fi fi - if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && [ "$gitlab_workhorse_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; }; then + if [ "$gitlab_pages_enabled" = true ]; then + if [ "$gitlab_pages_status" = "0" ]; then + echo "The GitLab Pages with pid $mpid is running." + else + printf "The GitLab Pages is \033[31mnot running\033[0m.\n" + fi + fi + if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && [ "$gitlab_workhorse_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" = "0" ]; }; then printf "GitLab and all its components are \033[32mup and running\033[0m.\n" fi } @@ -362,7 +409,7 @@ reload_gitlab(){ ## Restarts Sidekiq and Unicorn. restart_gitlab(){ check_status - if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; }; then + if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; } || { [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" = "0" ]; }; then stop_gitlab fi start_gitlab diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example index cc8617b72ca..6a4f6b090c9 100755 --- a/lib/support/init.d/gitlab.default.example +++ b/lib/support/init.d/gitlab.default.example @@ -47,6 +47,30 @@ gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid" gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $socket_path/gitlab.socket -documentRoot $app_root/public" gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log" +# The GitLab Pages Daemon needs to use a separate IP address on which it will +# listen. You can also use different ports than 80 or 443 that will be +# forwarded to GitLab Pages Daemon. +# +# To enable HTTP support for custom domains add the `-listen-http` directive +# in `gitlab_pages_options` below. +# The value of -listen-http must be set to `gitlab.yml > pages > external_http` +# as well. For example: +# +# -listen-http 1.1.1.1:80 +# +# To enable HTTPS support for custom domains add the `-listen-https`, +# `-root-cert` and `-root-key` directives in `gitlab_pages_options` below. +# The value of -listen-https must be set to `gitlab.yml > pages > external_https` +# as well. For example: +# +# -listen-https 1.1.1.1:443 -root-cert /path/to/example.com.crt -root-key /path/to/example.com.key +# +# The -pages-domain must be specified the same as in `gitlab.yml > pages > host`. +# Set `gitlab_pages_enabled=false` if you want to disable the Pages feature. +gitlab_pages_enabled=true +gitlab_pages_options="-pages-domain example.com -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8282" +gitlab_pages_log="$app_root/log/gitlab-pages.log" + # mail_room_enabled specifies whether mail_room, which is used to process incoming email, is enabled. # This is required for the Reply by email feature. # The default is "false" From 50bbc326a475f0cca8e63c7a8de96b3f5538cee0 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 20 Feb 2016 23:32:46 +0200 Subject: [PATCH 072/183] Change NGINX pages configs to account for the Pages daemon --- lib/support/nginx/gitlab-pages | 16 +++++++--------- lib/support/nginx/gitlab-pages-ssl | 18 ++++++++---------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages index ed4f7e4316a..2e0eb2af4b1 100644 --- a/lib/support/nginx/gitlab-pages +++ b/lib/support/nginx/gitlab-pages @@ -8,20 +8,18 @@ server { ## Replace this with something like pages.gitlab.com server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; - root /home/git/gitlab/shared/pages/${group}; ## Individual nginx logs for GitLab pages access_log /var/log/nginx/gitlab_pages_access.log; error_log /var/log/nginx/gitlab_pages_error.log; - # 1. Try to get /path/ from shared/pages/${group}/${path}/public/ - # 2. Try to get / from shared/pages/${group}/${host}/public/ - location ~ ^/([^/]*)(/.*)?$ { - try_files "/$1/public$2" - "/$1/public$2/index.html" - "/${host}/public/${uri}" - "/${host}/public/${uri}/index.html" - =404; + location / { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + # The same address as passed to GitLab Pages: `-listen-proxy` + proxy_pass http://localhost:8282/; } # Define custom error pages diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl index dcbbee4042a..1045cd42d2f 100644 --- a/lib/support/nginx/gitlab-pages-ssl +++ b/lib/support/nginx/gitlab-pages-ssl @@ -23,12 +23,11 @@ server { ## Pages serving host server { listen 0.0.0.0:443 ssl; - listen [::]:443 ipv6only=on ssl; + listen [::]:443 ipv6only=on ssl http2; ## Replace this with something like pages.gitlab.com server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; server_tokens off; ## Don't show the nginx version number, a security best practice - root /home/git/gitlab/shared/pages/${group}; ## Strong SSL Security ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/ @@ -63,14 +62,13 @@ server { access_log /var/log/nginx/gitlab_pages_access.log; error_log /var/log/nginx/gitlab_pages_error.log; - # 1. Try to get /path/ from shared/pages/${group}/${path}/public/ - # 2. Try to get / from shared/pages/${group}/${host}/public/ - location ~ ^/([^/]*)(/.*)?$ { - try_files "/$1/public$2" - "/$1/public$2/index.html" - "/${host}/public/${uri}" - "/${host}/public/${uri}/index.html" - =404; + location / { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + # The same address as passed to GitLab Pages: `-listen-proxy` + proxy_pass http://localhost:8282/; } # Define custom error pages From 4b45f284c9d060de06f4f54d9e5b1c2815b743dd Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 00:31:46 +0200 Subject: [PATCH 073/183] Change the pages daemon proxy listen port to 8090 So as to be consistent with what is set in Omnibus --- lib/support/init.d/gitlab.default.example | 2 +- lib/support/nginx/gitlab-pages | 2 +- lib/support/nginx/gitlab-pages-ssl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example index 6a4f6b090c9..f096298afbb 100755 --- a/lib/support/init.d/gitlab.default.example +++ b/lib/support/init.d/gitlab.default.example @@ -68,7 +68,7 @@ gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log" # The -pages-domain must be specified the same as in `gitlab.yml > pages > host`. # Set `gitlab_pages_enabled=false` if you want to disable the Pages feature. gitlab_pages_enabled=true -gitlab_pages_options="-pages-domain example.com -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8282" +gitlab_pages_options="-pages-domain example.com -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090" gitlab_pages_log="$app_root/log/gitlab-pages.log" # mail_room_enabled specifies whether mail_room, which is used to process incoming email, is enabled. diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages index 2e0eb2af4b1..169d7d11ca7 100644 --- a/lib/support/nginx/gitlab-pages +++ b/lib/support/nginx/gitlab-pages @@ -19,7 +19,7 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # The same address as passed to GitLab Pages: `-listen-proxy` - proxy_pass http://localhost:8282/; + proxy_pass http://localhost:8090/; } # Define custom error pages diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl index 1045cd42d2f..16edd337e10 100644 --- a/lib/support/nginx/gitlab-pages-ssl +++ b/lib/support/nginx/gitlab-pages-ssl @@ -68,7 +68,7 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # The same address as passed to GitLab Pages: `-listen-proxy` - proxy_pass http://localhost:8282/; + proxy_pass http://localhost:8090/; } # Define custom error pages From deb9481efde12e6198b0330bb8eb4c802d1d4b4c Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 00:32:58 +0200 Subject: [PATCH 074/183] Add missing variables for gitlab-pages [ci skip] --- lib/support/init.d/gitlab | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 38a9ab194d1..9f2ce01d931 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -42,6 +42,11 @@ gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse 2> /dev/null && pwd) gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid" gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $rails_socket -documentRoot $app_root/public" gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log" +gitlab_pages_enabled=false +gitlab_pages_dir=$(cd $app_root/../gitlab-pages 2> /dev/null && pwd) +gitlab_pages_pid_path="$pid_path/gitlab-pages.pid" +gitlab_pages_options="-pages-domain example.com -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090" +gitlab_pages_log="$app_root/log/gitlab-pages.log" shell_path="/bin/bash" # Read configuration variable file if it is present From fd9916c8d2976f724589d581943cc6aa4b1237f7 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 00:41:08 +0200 Subject: [PATCH 075/183] Add section about changes from 8.4 to 8.5 [ci skip] --- doc/pages/administration.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 529a1450fd3..5e0e4f8efed 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -5,6 +5,15 @@ _**Note:** This feature was [introduced][ee-80] in GitLab EE 8.3_ If you are looking for ways to upload your static content in GitLab Pages, you probably want to read the [user documentation](README.md). +## Changes to GitLab Pages from GitLab 8.4 to 8.5 + +In GitLab 8.5 we introduced the [gitlab-pages daemon] which is now the +recommended way to set up GitLab Pages. + +The NGINX configs have changed to reflect this change. + +[gitlab-pages daemon]: https://gitlab.com/gitlab-org/gitlab-pages + ## Configuration There are a couple of things to consider before enabling GitLab pages in your From 055b8230cc84252e143798938d179024b083f152 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 12:17:36 +0200 Subject: [PATCH 076/183] Add Changelog of GitLab Pages --- doc/pages/administration.md | 51 ++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 5e0e4f8efed..f45b013b8f6 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -1,14 +1,17 @@ # GitLab Pages Administration -_**Note:** This feature was [introduced][ee-80] in GitLab EE 8.3_ +> **Note:** +This feature was [introduced][ee-80] in GitLab EE 8.3. + +This document describes how to set up the _latest_ GitLab Pages feature. Make +sure to read the [changelog](#changelog) if you are upgrading to a new GitLab +version as it may include new features and changes needed to be made in your +configuration. If you are looking for ways to upload your static content in GitLab Pages, you probably want to read the [user documentation](README.md). -## Changes to GitLab Pages from GitLab 8.4 to 8.5 - -In GitLab 8.5 we introduced the [gitlab-pages daemon] which is now the -recommended way to set up GitLab Pages. +[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 The NGINX configs have changed to reflect this change. @@ -177,5 +180,39 @@ Pages are part of the regular backup so there is nothing to configure. You should strongly consider running GitLab pages under a different hostname than GitLab to prevent XSS attacks. -[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 -[wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record +## Changelog + +GitLab Pages were first introduced in GitLab EE 8.3. Since then, many features +where added, like custom CNAME and TLS support, and many more are likely to +come. Below is a brief changelog. If no changes were introduced, assume that +the documentation is the same as the previous version(s). + +--- + +**GitLab 8.5 ([documentation][8-5-docs])** + +- In GitLab 8.5 we introduced the [gitlab-pages][] daemon which is now the + recommended way to set up GitLab Pages. +- The [NGINX configs][] have changed to reflect this change. So make sure to + update them. +- Custom CNAME and TLS certificates support + +[8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md +[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.0 +[NGINX configs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/8-5-stable-ee/lib/support/nginx + +--- + +**GitLab 8.4** + +No new changes. + +--- + +**GitLab 8.3 ([documentation][8-3-docs])** + +- GitLab Pages feature was introduced. + +[8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md + +--- From 96fed04452ff9b637c2298c05a89f1f14fa091f2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 12:27:50 +0200 Subject: [PATCH 077/183] Add first draft of architecture --- doc/pages/administration.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index f45b013b8f6..f52703b7e43 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -13,9 +13,13 @@ probably want to read the [user documentation](README.md). [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 -The NGINX configs have changed to reflect this change. +## Architecture -[gitlab-pages daemon]: https://gitlab.com/gitlab-org/gitlab-pages +GitLab uses a separate tool ([gitlab-pages]), a simple HTTP server written in +Go that serves GitLab Pages with CNAMEs and SNI using HTTP/HTTP2. You are +encouraged to read its [README][pages-readme] to fully understand how it works. + +[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md ## Configuration From 0a4585bee4a5f7edde61d7358fb3a30b8f8224a1 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 17:34:18 +0200 Subject: [PATCH 078/183] Add MR that custom CNAMEs were introduced --- doc/pages/administration.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index f52703b7e43..74c960afa4f 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -1,7 +1,10 @@ # GitLab Pages Administration > **Note:** -This feature was [introduced][ee-80] in GitLab EE 8.3. +> This feature was first [introduced][ee-80] in GitLab EE 8.3. +> Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. + +--- This document describes how to set up the _latest_ GitLab Pages feature. Make sure to read the [changelog](#changelog) if you are upgrading to a new GitLab From 516a95ddb71c524e10d19fb3cf4ab4893772fa03 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 17:35:01 +0200 Subject: [PATCH 079/183] Add TOC --- doc/pages/administration.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 74c960afa4f..282736cdfd8 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -15,6 +15,27 @@ If you are looking for ways to upload your static content in GitLab Pages, you probably want to read the [user documentation](README.md). [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 +[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 + +--- + + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Architecture](#architecture) +- [Configuration](#configuration) + - [DNS configuration](#dns-configuration) + - [Omnibus package installations](#omnibus-package-installations) + - [Installations from source](#installations-from-source) + - [Running GitLab Pages with HTTPS](#running-gitlab-pages-with-https) +- [Set maximum pages size](#set-maximum-pages-size) +- [Change storage path](#change-storage-path) +- [Backup](#backup) +- [Security](#security) +- [Changelog](#changelog) + + ## Architecture From f8927dad8c1f2940953d6694a8b7f507dbe0c3e4 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 17:36:34 +0200 Subject: [PATCH 080/183] More changelog clarification --- doc/pages/administration.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 282736cdfd8..fc858cd20e7 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -212,8 +212,9 @@ than GitLab to prevent XSS attacks. GitLab Pages were first introduced in GitLab EE 8.3. Since then, many features where added, like custom CNAME and TLS support, and many more are likely to -come. Below is a brief changelog. If no changes were introduced, assume that -the documentation is the same as the previous version(s). +come. Below is a brief changelog. If no changes were introduced or a version is +missing from the changelog, assume that the documentation is the same as the +latest previous version. --- From acf7ae5ed80ec39d26dbd37c09bc0f3eb78e1628 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 20:52:15 +0200 Subject: [PATCH 081/183] Add info about the pages daemon --- doc/pages/administration.md | 49 ++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index fc858cd20e7..3f3d6cac9b2 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -37,11 +37,52 @@ probably want to read the [user documentation](README.md). -## Architecture +## The GitLab Pages daemon -GitLab uses a separate tool ([gitlab-pages]), a simple HTTP server written in -Go that serves GitLab Pages with CNAMEs and SNI using HTTP/HTTP2. You are -encouraged to read its [README][pages-readme] to fully understand how it works. +Starting from GitLab EE 8.5, Pages make use of a separate tool ([gitlab-pages]), +a simple HTTP server written in Go that serves GitLab Pages with CNAMEs and SNI +using HTTP/HTTP2. You are encouraged to read its [README][pages-readme] to fully +understand how it works. + +What is supported when using the pages daemon: + +- Multiple domains per-project +- One TLS certificate per-domain + - Validation of certificate + - Validation of certificate chain + - Validation of private key against certificate + +--- + +In the case of custom domains, the Pages daemon needs to listen on ports `80` +and/or `443`. For that reason, there is some flexibility in the way which you +can set it up, so you basically have three choices: + +1. Run the pages daemon in the same server as GitLab, listening on a secondary IP +1. Run the pages daemon in the same server as GitLab, listening on the same IP + but on different ports. In that case, you will have to proxy the traffic with + a loadbalancer. +1. Run the pages daemon in a separate server. In that case, the Pages [`path`] + must also be present in the server that the pages daemon is installed, so + you will have to share it via network. + +[`path`]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/config/gitlab.yml.example#L155 + +### Install the Pages daemon + +**Install the Pages daemon on a source installation** + +``` +cd /home/git +sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git +cd gitlab-pages +sudo -u git -H git checkout 0.2.0 +sudo -u git -H make +``` + +**Install the Pages daemon on Omnibus** + +The `gitlab-pages` daemon is included in the Omnibus package. [pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md From dfc3e58a5d763d0250e76625ada08b88a7cb7e63 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 20:52:53 +0200 Subject: [PATCH 082/183] Add configuration scenarios --- doc/pages/administration.md | 42 ++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 3f3d6cac9b2..db0eb83b9f4 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -88,18 +88,40 @@ The `gitlab-pages` daemon is included in the Omnibus package. ## Configuration -There are a couple of things to consider before enabling GitLab pages in your -GitLab EE instance. +There are multiple ways to set up GitLab Pages according to what URL scheme you +are willing to support. Below you will find all possible scenarios to choose +from. -1. You need to properly configure your DNS to point to the domain that pages - will be served -1. Pages use a separate Nginx configuration file which needs to be explicitly - added in the server under which GitLab EE runs -1. Optionally but recommended, you can add some - [shared runners](../ci/runners/README.md) so that your users don't have to - bring their own. +### Configuration scenarios -Both of these settings are described in detail in the sections below. +Before proceeding you have to decide what Pages scenario you want to use. +Remember that in either scenario, you need: + +1. A separate domain +1. A separate Nginx configuration file which needs to be explicitly added in + the server under which GitLab EE runs (Omnibus does that automatically) +1. (Optional) A wildcard certificate for that domain if you decide to serve + pages under HTTPS +1. (Optional but recommended) [Shared runners](../ci/runners/README.md) so that + your users don't have to bring their own. + +The possible scenarios are depicted in the table below. + +| URL scheme | Option | Wildcard certificate | Pages daemon | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `http://page.gitlab.io` | 1 | no | no | no | no | no | +| `https://page.gitlab.io` | 1 | yes | no | no | no | no | +| `http://page.gitlab.io` and `http://page.com` | 2 | no | yes | yes | no | yes | +| `https://page.gitlab.io` and `https://page.com` | 2 | yes | yes | yes/no | yes | yes | + +As you see from the table above, each URL scheme comes with an option: + +1. Pages enabled, daemon is enabled and NGINX will proxy all requests to the + daemon. Pages daemon doesn't listen to the outside world. +1. Pages enabled, daemon is enabled AND pages has external IP support enabled. + In that case, the pages daemon is running, NGINX still proxies requests to + the daemon but the daemon is also able to receive requests from the outside + world. Custom domains and TLS are supported. ### DNS configuration From 39dff1e1066d2e33267602f11f611af145ba169a Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 20:53:20 +0200 Subject: [PATCH 083/183] Update TOC --- doc/pages/administration.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index db0eb83b9f4..4e4bea57096 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -23,12 +23,16 @@ probably want to read the [user documentation](README.md). **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* -- [Architecture](#architecture) +- [The GitLab Pages daemon](#the-gitlab-pages-daemon) + - [Install the Pages daemon](#install-the-pages-daemon) - [Configuration](#configuration) + - [Configuration scenarios](#configuration-scenarios) - [DNS configuration](#dns-configuration) - - [Omnibus package installations](#omnibus-package-installations) - - [Installations from source](#installations-from-source) +- [Custom domains without TLS](#custom-domains-without-tls) +- [Custom domains with TLS](#custom-domains-with-tls) +- [Installations from source](#installations-from-source) - [Running GitLab Pages with HTTPS](#running-gitlab-pages-with-https) +- [Omnibus package installations](#omnibus-package-installations) - [Set maximum pages size](#set-maximum-pages-size) - [Change storage path](#change-storage-path) - [Backup](#backup) From cfc54df4a8386ec5d58ba55fff98264fe746e3ba Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 23:06:44 +0200 Subject: [PATCH 084/183] Set pages daemon to false --- lib/support/init.d/gitlab.default.example | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example index f096298afbb..e5797d8fe3c 100755 --- a/lib/support/init.d/gitlab.default.example +++ b/lib/support/init.d/gitlab.default.example @@ -47,9 +47,9 @@ gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid" gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $socket_path/gitlab.socket -documentRoot $app_root/public" gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log" -# The GitLab Pages Daemon needs to use a separate IP address on which it will -# listen. You can also use different ports than 80 or 443 that will be -# forwarded to GitLab Pages Daemon. +# The GitLab Pages Daemon needs either a separate IP address on which it will +# listen or use different ports than 80 or 443 that will be forwarded to GitLab +# Pages Daemon. # # To enable HTTP support for custom domains add the `-listen-http` directive # in `gitlab_pages_options` below. @@ -66,8 +66,8 @@ gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log" # -listen-https 1.1.1.1:443 -root-cert /path/to/example.com.crt -root-key /path/to/example.com.key # # The -pages-domain must be specified the same as in `gitlab.yml > pages > host`. -# Set `gitlab_pages_enabled=false` if you want to disable the Pages feature. -gitlab_pages_enabled=true +# Set `gitlab_pages_enabled=true` if you want to enable the Pages feature. +gitlab_pages_enabled=false gitlab_pages_options="-pages-domain example.com -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090" gitlab_pages_log="$app_root/log/gitlab-pages.log" From b39947864df75fc52781489fbe59185d788cb208 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 23:07:14 +0200 Subject: [PATCH 085/183] chmod 644 gitlab.default.example No need to be executable since it is sourced in /etc/init.d/gitlab --- lib/support/init.d/gitlab.default.example | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 lib/support/init.d/gitlab.default.example diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example old mode 100755 new mode 100644 From 2a484c6a291172c9c69cdcfe068ea4e440771393 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 00:28:14 +0200 Subject: [PATCH 086/183] Introduce custom domains setup for source installations [ci skip] --- doc/pages/administration.md | 98 +++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 4 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 4e4bea57096..1d7ee65a0d6 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -30,8 +30,8 @@ probably want to read the [user documentation](README.md). - [DNS configuration](#dns-configuration) - [Custom domains without TLS](#custom-domains-without-tls) - [Custom domains with TLS](#custom-domains-with-tls) -- [Installations from source](#installations-from-source) - - [Running GitLab Pages with HTTPS](#running-gitlab-pages-with-https) +- [Wildcard HTTPS domain without custom domains](#wildcard-https-domain-without-custom-domains) +- [Wildcard HTTP domain without custom domains](#wildcard-http-domain-without-custom-domains) - [Omnibus package installations](#omnibus-package-installations) - [Set maximum pages size](#set-maximum-pages-size) - [Change storage path](#change-storage-path) @@ -145,9 +145,99 @@ see the [security section](#security). ### Omnibus package installations -See the relevant documentation at . +## Custom domains without TLS -### Installations from source +1. [Install the pages daemon](#install-the-pages-daemon) +1. Edit `gitlab.yml` to look like the example below. You need to change the + `host` to the FQDN under which GitLab Pages will be served. Set + `external_http` to the secondary IP on which the pages daemon will listen + for connections: + + ```yaml + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 80 + https: false + + external_http: 1.1.1.1:80 + ``` + +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain` and `-listen-http` must match the `host` and `external_http` + settings that you set above respectively: + + ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Make sure to edit the config to add your domain as well as correctly point + to the right location of the SSL certificate files. Restart Nginx for the + changes to take effect. + +1. [Restart GitLab](../../administration/restart_gitlab.md) + +## Custom domains with TLS + +1. [Install the pages daemon](#install-the-pages-daemon) +1. Edit `gitlab.yml` to look like the example below. You need to change the + `host` to the FQDN under which GitLab Pages will be served. Set + `external_http` and `external_https` to the secondary IP on which the pages + daemon will listen for connections: + + ```yaml + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 443 + https: true + + external_http: 1.1.1.1:80 + external_https: 1.1.1.1:443 + ``` + +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain`, `-listen-http` and `-listen-https` must match the `host`, + `external_http` and `external_https` settings that you set above respectively. + The `-root-cert` and `-root-key` settings are the wildcard TLS certificates + of the `example.io` domain: + + ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80 -listen-https 1.1.1.1:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Make sure to edit the config to add your domain as well as correctly point + to the right location of the SSL certificate files. Restart Nginx for the + changes to take effect. + +1. [Restart GitLab](../../administration/restart_gitlab.md) + +## Wildcard HTTPS domain without custom domains 1. Go to the GitLab installation directory: From 54c943a59732cb0e0ce90a3d5db7f98ae807f22b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 01:10:49 +0200 Subject: [PATCH 087/183] Reword pages daemon intro --- doc/pages/administration.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 1d7ee65a0d6..8477bfa8b21 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -44,11 +44,12 @@ probably want to read the [user documentation](README.md). ## The GitLab Pages daemon Starting from GitLab EE 8.5, Pages make use of a separate tool ([gitlab-pages]), -a simple HTTP server written in Go that serves GitLab Pages with CNAMEs and SNI -using HTTP/HTTP2. You are encouraged to read its [README][pages-readme] to fully -understand how it works. +a simple HTTP server written in Go that can listen on an external IP address +and provide support for custom domains and custom certificates. The GitLab +Pages Daemon supports dynamic certificates through SNI and exposes pages using +HTTP2 by default. -What is supported when using the pages daemon: +Here is a brief list with what it is supported when using the pages daemon: - Multiple domains per-project - One TLS certificate per-domain @@ -56,6 +57,9 @@ What is supported when using the pages daemon: - Validation of certificate chain - Validation of private key against certificate +You are encouraged to read its [README][pages-readme] to fully understand how +it works. + --- In the case of custom domains, the Pages daemon needs to listen on ports `80` From ca26884c1538322150c8d3e08a6f2f5295b1c725 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 01:11:36 +0200 Subject: [PATCH 088/183] Add info about the loadbalancer --- doc/pages/administration.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 8477bfa8b21..c009263f648 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -67,12 +67,18 @@ and/or `443`. For that reason, there is some flexibility in the way which you can set it up, so you basically have three choices: 1. Run the pages daemon in the same server as GitLab, listening on a secondary IP -1. Run the pages daemon in the same server as GitLab, listening on the same IP - but on different ports. In that case, you will have to proxy the traffic with - a loadbalancer. 1. Run the pages daemon in a separate server. In that case, the Pages [`path`] must also be present in the server that the pages daemon is installed, so you will have to share it via network. +1. Run the pages daemon in the same server as GitLab, listening on the same IP + but on different ports. In that case, you will have to proxy the traffic with + a loadbalancer. If you choose that route note that you should use TCP load + balancing for HTTPS. If you use TLS-termination (HTTPS-load balancing) the + pages will not be able to be served with user provided certificates. For + HTTP it's OK to use HTTP or TCP load balancing. + +In this document, we will proceed assuming the first option. First let's +install the pages daemon. [`path`]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/config/gitlab.yml.example#L155 From fed8b62c3d4c57dae05ed4796c19bccdaf540886 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 01:12:31 +0200 Subject: [PATCH 089/183] Remove pages daemon from table and add it as prerequisite --- doc/pages/administration.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index c009263f648..eeda4af18fe 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -108,25 +108,26 @@ from. ### Configuration scenarios -Before proceeding you have to decide what Pages scenario you want to use. -Remember that in either scenario, you need: +Before proceeding with setting up GitLab Pages, you have to decide which route +you want to take. Note that in either scenario, you need: +1. To use the [GitLab Pages daemon](#the-gitlab-pages-daemon) 1. A separate domain 1. A separate Nginx configuration file which needs to be explicitly added in the server under which GitLab EE runs (Omnibus does that automatically) 1. (Optional) A wildcard certificate for that domain if you decide to serve pages under HTTPS 1. (Optional but recommended) [Shared runners](../ci/runners/README.md) so that - your users don't have to bring their own. + your users don't have to bring their own The possible scenarios are depicted in the table below. -| URL scheme | Option | Wildcard certificate | Pages daemon | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| URL scheme | Option | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | | --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| `http://page.gitlab.io` | 1 | no | no | no | no | no | -| `https://page.gitlab.io` | 1 | yes | no | no | no | no | -| `http://page.gitlab.io` and `http://page.com` | 2 | no | yes | yes | no | yes | -| `https://page.gitlab.io` and `https://page.com` | 2 | yes | yes | yes/no | yes | yes | +| `http://page.example.io` | 1 | no | no | no | no | +| `https://page.example.io` | 1 | yes | no | no | no | +| `http://page.example.io` and `http://page.com` | 2 | no | yes | no | yes | +| `https://page.example.io` and `https://page.com` | 2 | yes | redirects to HTTPS | yes | yes | As you see from the table above, each URL scheme comes with an option: From d24763fa16874cf8f99a32635787134f4f76d699 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 01:22:28 +0200 Subject: [PATCH 090/183] Add the four scenarios and NGINX caveats --- doc/pages/administration.md | 247 +++++++++++++++++++++--------------- 1 file changed, 148 insertions(+), 99 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index eeda4af18fe..5d7c0d76620 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -28,11 +28,12 @@ probably want to read the [user documentation](README.md). - [Configuration](#configuration) - [Configuration scenarios](#configuration-scenarios) - [DNS configuration](#dns-configuration) -- [Custom domains without TLS](#custom-domains-without-tls) -- [Custom domains with TLS](#custom-domains-with-tls) -- [Wildcard HTTPS domain without custom domains](#wildcard-https-domain-without-custom-domains) -- [Wildcard HTTP domain without custom domains](#wildcard-http-domain-without-custom-domains) -- [Omnibus package installations](#omnibus-package-installations) +- [Setting up GitLab Pages](#setting-up-gitlab-pages) + - [Custom domains with HTTPS support](#custom-domains-with-https-support) + - [Custom domains without HTTPS support](#custom-domains-without-https-support) + - [Wildcard HTTP domain without custom domains](#wildcard-http-domain-without-custom-domains) + - [Wildcard HTTPS domain without custom domains](#wildcard-https-domain-without-custom-domains) +- [NGINX caveats](#nginx-caveats) - [Set maximum pages size](#set-maximum-pages-size) - [Change storage path](#change-storage-path) - [Backup](#backup) @@ -145,62 +146,25 @@ you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the host that GitLab runs. For example, an entry would look like this: ``` -*.gitlab.io. 60 IN A 1.2.3.4 +*.example.io. 1800 IN A 1.2.3.4 ``` -where `gitlab.io` is the domain under which GitLab Pages will be served +where `example.io` is the domain under which GitLab Pages will be served and `1.2.3.4` is the IP address of your GitLab instance. You should not use the GitLab domain to serve user pages. For more information see the [security section](#security). -### Omnibus package installations +[wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record -## Custom domains without TLS +## Setting up GitLab Pages -1. [Install the pages daemon](#install-the-pages-daemon) -1. Edit `gitlab.yml` to look like the example below. You need to change the - `host` to the FQDN under which GitLab Pages will be served. Set - `external_http` to the secondary IP on which the pages daemon will listen - for connections: +Below are the four scenarios that are described in +[#configuration-scenarios](#configuration-scenarios). - ```yaml - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages +### Custom domains with HTTPS support - host: example.io - port: 80 - https: false - - external_http: 1.1.1.1:80 - ``` - -1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in - order to enable the pages daemon. In `gitlab_pages_options` the - `-pages-domain` and `-listen-http` must match the `host` and `external_http` - settings that you set above respectively: - - ``` - gitlab_pages_enabled=true - gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Make sure to edit the config to add your domain as well as correctly point - to the right location of the SSL certificate files. Restart Nginx for the - changes to take effect. - -1. [Restart GitLab](../../administration/restart_gitlab.md) - -## Custom domains with TLS +**Source installations:** 1. [Install the pages daemon](#install-the-pages-daemon) 1. Edit `gitlab.yml` to look like the example below. You need to change the @@ -242,14 +206,70 @@ see the [security section](#security). sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf ``` - Make sure to edit the config to add your domain as well as correctly point - to the right location of the SSL certificate files. Restart Nginx for the - changes to take effect. + Make sure to [properly edit the config](#nginx-caveats) to add your domain + as well as correctly point to the right location of the SSL certificate + files. Restart Nginx for the changes to take effect. -1. [Restart GitLab](../../administration/restart_gitlab.md) +1. [Restart GitLab][restart] -## Wildcard HTTPS domain without custom domains +--- +**Omnibus installations:** + +### Custom domains without HTTPS support + +**Source installations:** + +1. [Install the pages daemon](#install-the-pages-daemon) +1. Edit `gitlab.yml` to look like the example below. You need to change the + `host` to the FQDN under which GitLab Pages will be served. Set + `external_http` to the secondary IP on which the pages daemon will listen + for connections: + + ```yaml + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 80 + https: false + + external_http: 1.1.1.1:80 + ``` + +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain` and `-listen-http` must match the `host` and `external_http` + settings that you set above respectively: + + ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Make sure to [properly edit the config](#nginx-caveats) to add your domain. + Restart Nginx for the changes to take effect. + +1. [Restart GitLab][restart] + +--- + +**Omnibus installations:** + +### Wildcard HTTP domain without custom domains + +**Source installations:** + +1. [Install the pages daemon](#install-the-pages-daemon) 1. Go to the GitLab installation directory: ```bash @@ -266,12 +286,9 @@ see the [security section](#security). # The location where pages are stored (default: shared/pages). # path: shared/pages - # The domain under which the pages are served: - # http://group.example.com/project - # or project path can be a group page: group.example.com - host: gitlab.io - port: 80 # Set to 443 if you serve the pages with HTTPS - https: false # Set to true if you serve the pages with HTTPS + host: example.io + port: 80 + https: false ``` 1. Make sure you have copied the new `gitlab-pages` Nginx configuration file: @@ -281,39 +298,27 @@ see the [security section](#security). sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf ``` - Don't forget to add your domain name in the Nginx config. For example if - your GitLab pages domain is `gitlab.io`, replace + Make sure to [properly edit the config](#nginx-caveats) to add your domain. + Restart Nginx for the changes to take effect. - ```bash - server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; +1. [Restart GitLab][restart] + +--- + +**Omnibus installations:** + +1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: + + ```ruby + pages_external_url 'http://example.io' ``` +1. [Reconfigure GitLab][reconfigure] - with +### Wildcard HTTPS domain without custom domains - ``` - server_name ~^(?.*)\.gitlabpages\.com$; - ``` - - You must be extra careful to not remove the backslashes. If you are using - a subdomain, make sure to escape all dots (`.`) with a backslash (\). - For example `pages.gitlab.io` would be: - - ``` - server_name ~^(?.*)\.pages\.gitlab\.io$; - ``` - -1. Restart Nginx and GitLab: - - ```bash - sudo service nginx restart - sudo service gitlab restart - ``` - -### Running GitLab Pages with HTTPS - -If you want the pages to be served under HTTPS, a wildcard SSL certificate is -required. +**Source installations:** +1. [Install the pages daemon](#install-the-pages-daemon) 1. In `gitlab.yml`, set the port to `443` and https to `true`: ```bash @@ -323,24 +328,65 @@ required. # The location where pages are stored (default: shared/pages). # path: shared/pages - # The domain under which the pages are served: - # http://group.example.com/project - # or project path can be a group page: group.example.com - host: gitlab.io - port: 443 # Set to 443 if you serve the pages with HTTPS - https: true # Set to true if you serve the pages with HTTPS + host: example.io + port: 443 + https: true ``` 1. Copy the `gitlab-pages-ssl` Nginx configuration file: ```bash sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf ``` - Make sure to edit the config to add your domain as well as correctly point - to the right location of the SSL certificate files. Restart Nginx for the - changes to take effect. + Make sure to [properly edit the config](#nginx-caveats) to add your domain + as well as correctly point to the right location of the SSL certificate + files. Restart Nginx for the changes to take effect. + +--- + +**Omnibus installations:** + +1. Place the certificate and key inside `/etc/gitlab/ssl` +1. In `/etc/gitlab/gitlab.rb` specify the following configuration: + + ```ruby + pages_external_url 'https://example.io' + + pages_nginx['redirect_http_to_https'] = true + pages_nginx['ssl_certificate'] = "/etc/gitlab/ssl/pages-nginx.crt" + pages_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/pages-nginx.key" + ``` + + where `pages-nginx.crt` and `pages-nginx.key` are the SSL cert and key, + respectively. + +1. [Reconfigure GitLab][reconfigure] + +## NGINX caveats + +Be extra careful when setting up the domain name in the NGINX config. You must +not remove the backslashes. + +If your GitLab pages domain is `example.io`, replace: + +```bash +server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; +``` + +with: + +``` +server_name ~^(?.*)\.example\.io$; +``` + +If you are using a subdomain, make sure to escape all dots (`.`) with a +backslash (\). For example `pages.example.io` would be: + +``` +server_name ~^(?.*)\.pages\.example\.io$; +``` ## Set maximum pages size @@ -413,3 +459,6 @@ No new changes. [8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md --- + +[reconfigure]: ../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure +[restart]: ../../administration/restart_gitlab.md#installations-from-source From 228455af6baefff6bea679c30216d6ede703b2de Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 01:28:28 +0200 Subject: [PATCH 091/183] Rename NGINX section --- doc/pages/administration.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 5d7c0d76620..675a41d8023 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -33,7 +33,7 @@ probably want to read the [user documentation](README.md). - [Custom domains without HTTPS support](#custom-domains-without-https-support) - [Wildcard HTTP domain without custom domains](#wildcard-http-domain-without-custom-domains) - [Wildcard HTTPS domain without custom domains](#wildcard-https-domain-without-custom-domains) -- [NGINX caveats](#nginx-caveats) +- [NGINX configuration](#nginx-configuration) - [Set maximum pages size](#set-maximum-pages-size) - [Change storage path](#change-storage-path) - [Backup](#backup) @@ -206,7 +206,7 @@ Below are the four scenarios that are described in sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf ``` - Make sure to [properly edit the config](#nginx-caveats) to add your domain + Make sure to [properly edit the config](#nginx-configuration) to add your domain as well as correctly point to the right location of the SSL certificate files. Restart Nginx for the changes to take effect. @@ -249,14 +249,14 @@ Below are the four scenarios that are described in gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" ``` -1. Copy the `gitlab-pages-ssl` Nginx configuration file: +1. Copy the `gitlab-pages` Nginx configuration file: ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf ``` - Make sure to [properly edit the config](#nginx-caveats) to add your domain. + Make sure to [properly edit the config](#nginx-configuration) to add your domain. Restart Nginx for the changes to take effect. 1. [Restart GitLab][restart] @@ -298,7 +298,7 @@ Below are the four scenarios that are described in sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf ``` - Make sure to [properly edit the config](#nginx-caveats) to add your domain. + Make sure to [properly edit the config](#nginx-configuration) to add your domain. Restart Nginx for the changes to take effect. 1. [Restart GitLab][restart] @@ -340,7 +340,7 @@ Below are the four scenarios that are described in sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf ``` - Make sure to [properly edit the config](#nginx-caveats) to add your domain + Make sure to [properly edit the config](#nginx-configuration) to add your domain as well as correctly point to the right location of the SSL certificate files. Restart Nginx for the changes to take effect. @@ -364,7 +364,7 @@ Below are the four scenarios that are described in 1. [Reconfigure GitLab][reconfigure] -## NGINX caveats +## NGINX configuration Be extra careful when setting up the domain name in the NGINX config. You must not remove the backslashes. From 3858443fdf96435d21dc3331169f9066793cf5e7 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 02:37:54 +0200 Subject: [PATCH 092/183] Add heading to the GitLab Pages setup scenarios --- doc/pages/administration.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 675a41d8023..2b861cc55cf 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -24,6 +24,7 @@ probably want to read the [user documentation](README.md). **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [The GitLab Pages daemon](#the-gitlab-pages-daemon) + - [The GitLab Pages daemon and the case of custom domains](#the-gitlab-pages-daemon-and-the-case-of-custom-domains) - [Install the Pages daemon](#install-the-pages-daemon) - [Configuration](#configuration) - [Configuration scenarios](#configuration-scenarios) @@ -61,7 +62,7 @@ Here is a brief list with what it is supported when using the pages daemon: You are encouraged to read its [README][pages-readme] to fully understand how it works. ---- +### The GitLab Pages daemon and the case of custom domains In the case of custom domains, the Pages daemon needs to listen on ports `80` and/or `443`. For that reason, there is some flexibility in the way which you From f8c8dc03d007efeb52a6f81add6b49697001cb09 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 02:39:27 +0200 Subject: [PATCH 093/183] Add remaining Omnibus configs --- doc/pages/administration.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 2b861cc55cf..2b50ed1a126 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -217,6 +217,23 @@ Below are the four scenarios that are described in **Omnibus installations:** +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + nginx['listen_addresses'] = ['1.1.1.1'] + pages_nginx['enable'] = false + gitlab_pages['external_http'] = '1.1.1.2:80' + gitlab_pages['external_https'] = '1.1.1.2:443' + ``` + + where `1.1.1.1` is the primary IP address that GitLab is listening to and + `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to. + Read more at the + [NGINX configuration for custom domains](#nginx-configuration-for-custom-domains) + section. + +1. [Reconfigure GitLab][reconfigure] + ### Custom domains without HTTPS support **Source installations:** @@ -266,6 +283,22 @@ Below are the four scenarios that are described in **Omnibus installations:** +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + nginx['listen_addresses'] = ['1.1.1.1'] + pages_nginx['enable'] = false + gitlab_pages['external_http'] = '1.1.1.2:80' + ``` + + where `1.1.1.1` is the primary IP address that GitLab is listening to and + `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to. + Read more at the + [NGINX configuration for custom domains](#nginx-configuration-for-custom-domains) + section. + +1. [Reconfigure GitLab][reconfigure] + ### Wildcard HTTP domain without custom domains **Source installations:** @@ -313,6 +346,7 @@ Below are the four scenarios that are described in ```ruby pages_external_url 'http://example.io' ``` + 1. [Reconfigure GitLab][reconfigure] ### Wildcard HTTPS domain without custom domains From d9e3bb0e7def068c5b24937bf887b20784d4bd8e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 02:40:04 +0200 Subject: [PATCH 094/183] Add a separate NGINX section --- doc/pages/administration.md | 89 ++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 2b50ed1a126..f67bb63ff07 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -35,6 +35,9 @@ probably want to read the [user documentation](README.md). - [Wildcard HTTP domain without custom domains](#wildcard-http-domain-without-custom-domains) - [Wildcard HTTPS domain without custom domains](#wildcard-https-domain-without-custom-domains) - [NGINX configuration](#nginx-configuration) + - [NGINX configuration files](#nginx-configuration-files) + - [NGINX configuration for custom domains](#nginx-configuration-for-custom-domains) + - [NGINX caveats](#nginx-caveats) - [Set maximum pages size](#set-maximum-pages-size) - [Change storage path](#change-storage-path) - [Backup](#backup) @@ -200,17 +203,7 @@ Below are the four scenarios that are described in gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80 -listen-https 1.1.1.1:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key ``` -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Make sure to [properly edit the config](#nginx-configuration) to add your domain - as well as correctly point to the right location of the SSL certificate - files. Restart Nginx for the changes to take effect. - +1. Make sure to [configure NGINX](#nginx-configuration) properly. 1. [Restart GitLab][restart] --- @@ -267,16 +260,7 @@ Below are the four scenarios that are described in gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" ``` -1. Copy the `gitlab-pages` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf - ``` - - Make sure to [properly edit the config](#nginx-configuration) to add your domain. - Restart Nginx for the changes to take effect. - +1. Make sure to [configure NGINX](#nginx-configuration) properly. 1. [Restart GitLab][restart] --- @@ -325,16 +309,7 @@ Below are the four scenarios that are described in https: false ``` -1. Make sure you have copied the new `gitlab-pages` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf - ``` - - Make sure to [properly edit the config](#nginx-configuration) to add your domain. - Restart Nginx for the changes to take effect. - +1. Make sure to [configure NGINX](#nginx-configuration) properly. 1. [Restart GitLab][restart] --- @@ -368,16 +343,7 @@ Below are the four scenarios that are described in https: true ``` -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Make sure to [properly edit the config](#nginx-configuration) to add your domain - as well as correctly point to the right location of the SSL certificate - files. Restart Nginx for the changes to take effect. +1. Make sure to [configure NGINX](#nginx-configuration) properly. --- @@ -401,6 +367,47 @@ Below are the four scenarios that are described in ## NGINX configuration +Depending on your setup, you will need to make some changes to NGINX. +Specifically you must change the domain name and the IP address where NGINX +listens to. Read the following sections for more details. + +### NGINX configuration files + +Copy the `gitlab-pages-ssl` Nginx configuration file: + +```bash +sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf +sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf +``` + +Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +### NGINX configuration for custom domains + +> If you are not using custom domains ignore this section. + +[In the case of custom domains](#the-gitlab-pages-daemon-and-the-case-of-custom-domains), +if you have the secondary IP address configured on the same server as GitLab, +you need to change **all** NGINX configs to listen on the first IP address. + +**Source installations:** + +1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace + `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + listens to. +1. Restart NGINX + +**Omnibus installations:** + +1. Edit `/etc/gitlab/gilab.rb`: + + ``` + nginx['listen_addresses'] = ['1.1.1.1'] + ``` +1. [Reconfigure GitLab][reconfigure] + +### NGINX caveats + Be extra careful when setting up the domain name in the NGINX config. You must not remove the backslashes. From 84ff07cdcc846cec7a58aca641363531cda86d92 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 02:49:02 +0200 Subject: [PATCH 095/183] Simplify NGINX server_name regex --- doc/pages/administration.md | 10 +++++----- lib/support/nginx/gitlab-pages | 2 +- lib/support/nginx/gitlab-pages-ssl | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index f67bb63ff07..30d2b46c36a 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -414,20 +414,20 @@ not remove the backslashes. If your GitLab pages domain is `example.io`, replace: ```bash -server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; +server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$; ``` with: ``` -server_name ~^(?.*)\.example\.io$; +server_name ~^.*\.example\.io$; ``` -If you are using a subdomain, make sure to escape all dots (`.`) with a -backslash (\). For example `pages.example.io` would be: +If you are using a subdomain, make sure to escape all dots (`.`) except from +the first one with a backslash (\). For example `pages.example.io` would be: ``` -server_name ~^(?.*)\.pages\.example\.io$; +server_name ~^.*\.pages\.example\.io$; ``` ## Set maximum pages size diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages index 169d7d11ca7..d9746c5c1aa 100644 --- a/lib/support/nginx/gitlab-pages +++ b/lib/support/nginx/gitlab-pages @@ -7,7 +7,7 @@ server { listen [::]:80 ipv6only=on; ## Replace this with something like pages.gitlab.com - server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$; ## Individual nginx logs for GitLab pages access_log /var/log/nginx/gitlab_pages_access.log; diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl index 16edd337e10..a1ccf266835 100644 --- a/lib/support/nginx/gitlab-pages-ssl +++ b/lib/support/nginx/gitlab-pages-ssl @@ -11,7 +11,7 @@ server { listen [::]:80 ipv6only=on; ## Replace this with something like pages.gitlab.com - server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$; server_tokens off; ## Don't show the nginx version number, a security best practice return 301 https://$http_host$request_uri; @@ -26,7 +26,7 @@ server { listen [::]:443 ipv6only=on ssl http2; ## Replace this with something like pages.gitlab.com - server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$; server_tokens off; ## Don't show the nginx version number, a security best practice ## Strong SSL Security From aadbb6065b58d3d308b1fe7a15f5dfad290e0771 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 03:03:05 +0200 Subject: [PATCH 096/183] Move Omnibus storage path --- doc/pages/administration.md | 45 ++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 30d2b46c36a..c99ff87e87d 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -72,9 +72,9 @@ and/or `443`. For that reason, there is some flexibility in the way which you can set it up, so you basically have three choices: 1. Run the pages daemon in the same server as GitLab, listening on a secondary IP -1. Run the pages daemon in a separate server. In that case, the Pages [`path`] - must also be present in the server that the pages daemon is installed, so - you will have to share it via network. +1. Run the pages daemon in a separate server. In that case, the + [Pages path](#change-storage-path) must also be present in the server that + the pages daemon is installed, so you will have to share it via network. 1. Run the pages daemon in the same server as GitLab, listening on the same IP but on different ports. In that case, you will have to proxy the traffic with a loadbalancer. If you choose that route note that you should use TCP load @@ -85,8 +85,6 @@ can set it up, so you basically have three choices: In this document, we will proceed assuming the first option. First let's install the pages daemon. -[`path`]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/config/gitlab.yml.example#L155 - ### Install the Pages daemon **Install the Pages daemon on a source installation** @@ -404,6 +402,7 @@ you need to change **all** NGINX configs to listen on the first IP address. ``` nginx['listen_addresses'] = ['1.1.1.1'] ``` + 1. [Reconfigure GitLab][reconfigure] ### NGINX caveats @@ -438,22 +437,32 @@ The default is 100MB. ## Change storage path -Pages are stored by default in `/home/git/gitlab/shared/pages`. -If you wish to store them in another location you must set it up in -`gitlab.yml` under the `pages` section: +**Source installations:** -```yaml -pages: - enabled: true - # The location where pages are stored (default: shared/pages). - path: /mnt/storage/pages -``` +1. Pages are stored by default in `/home/git/gitlab/shared/pages`. + If you wish to store them in another location you must set it up in + `gitlab.yml` under the `pages` section: -Restart GitLab for the changes to take effect: + ```yaml + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + path: /mnt/storage/pages + ``` -```bash -sudo service gitlab restart -``` +1. [Restart GitLab][restart] + +**Omnibus installations:** + +1. Pages are stored by default in `/var/opt/gitlab/gitlab-rails/shared/pages`. + If you wish to store them in another location you must set it up in + `/etc/gitlab/gitlab.rb`: + + ```ruby + gitlab_rails['pages_path'] = "/mnt/storage/pages" + ``` + +1. [Reconfigure GitLab][reconfigure] ## Backup From 37fc486c73d91d25ddeb35eb6d528e16dcb1e9a9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 03:03:42 +0200 Subject: [PATCH 097/183] Link to backup task --- doc/pages/administration.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index c99ff87e87d..62665a94f1c 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -466,7 +466,7 @@ The default is 100MB. ## Backup -Pages are part of the regular backup so there is nothing to configure. +Pages are part of the [regular backup][backup] so there is nothing to configure. ## Security @@ -509,7 +509,6 @@ No new changes. [8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md ---- - [reconfigure]: ../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure [restart]: ../../administration/restart_gitlab.md#installations-from-source +[backup]: ../../raketasks/backup_restore.md From 9ca234615e44e2b365b8d6c1cc5a91c5bdd7159d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 03:16:27 +0200 Subject: [PATCH 098/183] Add link to Omnibus 8-3 docs --- doc/pages/administration.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 62665a94f1c..4aaabdb3442 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -490,6 +490,7 @@ latest previous version. - The [NGINX configs][] have changed to reflect this change. So make sure to update them. - Custom CNAME and TLS certificates support +- Documentation was moved to one place [8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md [gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.0 @@ -503,12 +504,12 @@ No new changes. --- -**GitLab 8.3 ([documentation][8-3-docs])** +**GitLab 8.3 ([source docs][8-3-docs], [Omnibus docs][8-3-omnidocs])** - GitLab Pages feature was introduced. [8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md - +[8-3-omnidocs]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/8-3-stable-ee/doc/settings/pages.md [reconfigure]: ../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure [restart]: ../../administration/restart_gitlab.md#installations-from-source [backup]: ../../raketasks/backup_restore.md From e5c7c8ca5b56563e1c133d736eb13c87aa749fa9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 03:27:44 +0200 Subject: [PATCH 099/183] Small reword fixes --- doc/pages/administration.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 4aaabdb3442..ad9c04a64bb 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -48,7 +48,7 @@ probably want to read the [user documentation](README.md). ## The GitLab Pages daemon -Starting from GitLab EE 8.5, Pages make use of a separate tool ([gitlab-pages]), +Starting from GitLab EE 8.5, GitLab Pages make use of the [GitLab Pages daemon], a simple HTTP server written in Go that can listen on an external IP address and provide support for custom domains and custom certificates. The GitLab Pages Daemon supports dynamic certificates through SNI and exposes pages using @@ -65,6 +65,9 @@ Here is a brief list with what it is supported when using the pages daemon: You are encouraged to read its [README][pages-readme] to fully understand how it works. +[gitlab pages daemon]: https://gitlab.com/gitlab-org/gitlab-pages +[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md + ### The GitLab Pages daemon and the case of custom domains In the case of custom domains, the Pages daemon needs to listen on ports `80` @@ -87,7 +90,7 @@ install the pages daemon. ### Install the Pages daemon -**Install the Pages daemon on a source installation** +**Source installations** ``` cd /home/git @@ -97,11 +100,10 @@ sudo -u git -H git checkout 0.2.0 sudo -u git -H make ``` -**Install the Pages daemon on Omnibus** +**Omnibus installations** The `gitlab-pages` daemon is included in the Omnibus package. -[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md ## Configuration From bdc7301aa448b79766fa342459aa1749deeb7b85 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 03:33:20 +0200 Subject: [PATCH 100/183] Add configuration prerequisites section [ci skip] --- doc/pages/administration.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index ad9c04a64bb..7bdd331df65 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -27,6 +27,7 @@ probably want to read the [user documentation](README.md). - [The GitLab Pages daemon and the case of custom domains](#the-gitlab-pages-daemon-and-the-case-of-custom-domains) - [Install the Pages daemon](#install-the-pages-daemon) - [Configuration](#configuration) + - [Configuration prerequisites](#configuration-prerequisites) - [Configuration scenarios](#configuration-scenarios) - [DNS configuration](#dns-configuration) - [Setting up GitLab Pages](#setting-up-gitlab-pages) @@ -108,13 +109,13 @@ The `gitlab-pages` daemon is included in the Omnibus package. ## Configuration There are multiple ways to set up GitLab Pages according to what URL scheme you -are willing to support. Below you will find all possible scenarios to choose -from. +are willing to support. -### Configuration scenarios +### Configuration prerequisites -Before proceeding with setting up GitLab Pages, you have to decide which route -you want to take. Note that in either scenario, you need: +In the next section you will find all possible scenarios to choose from. + +In either scenario, you will need: 1. To use the [GitLab Pages daemon](#the-gitlab-pages-daemon) 1. A separate domain @@ -125,6 +126,11 @@ you want to take. Note that in either scenario, you need: 1. (Optional but recommended) [Shared runners](../ci/runners/README.md) so that your users don't have to bring their own +### Configuration scenarios + +Before proceeding with setting up GitLab Pages, you have to decide which route +you want to take. + The possible scenarios are depicted in the table below. | URL scheme | Option | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | From e40693047aeb293afa00800a5eea743559337308 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 09:57:27 +0200 Subject: [PATCH 101/183] Checkout the tag of pages daemon --- doc/pages/administration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 7bdd331df65..ddbc8a7765f 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -86,8 +86,8 @@ can set it up, so you basically have three choices: pages will not be able to be served with user provided certificates. For HTTP it's OK to use HTTP or TCP load balancing. -In this document, we will proceed assuming the first option. First let's -install the pages daemon. +In this document, we will proceed assuming the first option. Let's begin by +installing the pages daemon. ### Install the Pages daemon @@ -97,7 +97,7 @@ install the pages daemon. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages -sudo -u git -H git checkout 0.2.0 +sudo -u git -H git checkout v0.2.0 sudo -u git -H make ``` From 8094a9d1115bfbe2899fd63862f0d3f9fcce438b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 12:19:02 +0200 Subject: [PATCH 102/183] Add missing Omnibus settings --- doc/pages/administration.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index ddbc8a7765f..d0fdbeafa5b 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -219,8 +219,11 @@ Below are the four scenarios that are described in 1. Edit `/etc/gitlab/gitlab.rb`: ```ruby + pages_external_url "https://example.io" nginx['listen_addresses'] = ['1.1.1.1'] pages_nginx['enable'] = false + gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt" + gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key" gitlab_pages['external_http'] = '1.1.1.2:80' gitlab_pages['external_https'] = '1.1.1.2:443' ``` @@ -276,6 +279,7 @@ Below are the four scenarios that are described in 1. Edit `/etc/gitlab/gitlab.rb`: ```ruby + pages_external_url "https://example.io" nginx['listen_addresses'] = ['1.1.1.1'] pages_nginx['enable'] = false gitlab_pages['external_http'] = '1.1.1.2:80' From 5556db04040c8c97834728dcf0fb26d2ea2c9a16 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 12:26:22 +0200 Subject: [PATCH 103/183] Add missing gitlab-pages related vars in init.d/gitlab --- lib/support/init.d/gitlab | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 9f2ce01d931..97414ead3dd 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -107,7 +107,7 @@ check_pids(){ wait_for_pids(){ # We are sleeping a bit here mostly because sidekiq is slow at writing its pid i=0; - while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || [ ! -f $gitlab_workhorse_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ]; }; do + while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || [ ! -f $gitlab_workhorse_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ] || { [ "$gitlab_pages_enabled" = true ] && [ ! -f $gitlab_pages_pid_path ]; }; do sleep 0.1; i=$((i+1)) if [ $((i%10)) = 0 ]; then @@ -240,7 +240,7 @@ start_gitlab() { if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then echo "Starting GitLab MailRoom" fi - if [ "gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" != "0" ]; then + if [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" != "0" ]; then echo "Starting GitLab Pages" fi From 639cf728f8c14560e85e0f54d5f4f27329d98c3c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 23 Feb 2016 12:03:03 +0100 Subject: [PATCH 104/183] Fix adding pages domain to projects in groups --- app/views/projects/pages_domains/_form.html.haml | 2 +- features/project/pages.feature | 9 +++++++++ features/steps/shared/project.rb | 6 ++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml index e97d19653d5..ca1b41b140a 100644 --- a/app/views/projects/pages_domains/_form.html.haml +++ b/app/views/projects/pages_domains/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace, @project, @domain], html: { class: 'form-horizontal fieldset-form' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'form-horizontal fieldset-form' } do |f| - if @domain.errors.any? #error_explanation .alert.alert-danger diff --git a/features/project/pages.feature b/features/project/pages.feature index 392f2d29c3c..87d88348d09 100644 --- a/features/project/pages.feature +++ b/features/project/pages.feature @@ -40,6 +40,15 @@ Feature: Project Pages And I click on "Create New Domain" Then I should see a new domain added + Scenario: I should be able to add a new domain for project in group namespace + Given I own a project in some group namespace + And pages are enabled + And pages are exposed on external HTTP address + When I visit add a new Pages Domain + And I fill the domain + And I click on "Create New Domain" + Then I should see a new domain added + Scenario: I should be denied to add the same domain twice Given pages are enabled And pages are exposed on external HTTP address diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 7a6707a7dfb..dae248b8b7e 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -7,6 +7,12 @@ module SharedProject @project.team << [@user, :master] end + step "I own a project in some group namespace" do + @group = create(:group, name: 'some group') + @project = create(:project, namespace: @group) + @project.team << [@user, :master] + end + step "project exists in some group namespace" do @group = create(:group, name: 'some group') @project = create(:project, :repository, namespace: @group, public_builds: false) From 55214fe1ebed923b23df43afc6da34aede2a00d2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 23 Feb 2016 15:45:03 +0200 Subject: [PATCH 105/183] First iteration on simplifying the pages user docs [ci skip] --- doc/pages/README.md | 156 +++++++++++++++++++++++------ doc/pages/img/create_user_page.png | Bin 0 -> 66593 bytes 2 files changed, 126 insertions(+), 30 deletions(-) create mode 100644 doc/pages/img/create_user_page.png diff --git a/doc/pages/README.md b/doc/pages/README.md index c9d0d7e2b49..73e18d1b9a7 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -1,11 +1,38 @@ # GitLab Pages -_**Note:** This feature was [introduced][ee-80] in GitLab EE 8.3_ +> **Note:** +> This feature was [introduced][ee-80] in GitLab EE 8.3. +> Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. With GitLab Pages you can host for free your static websites on GitLab. Combined with the power of GitLab CI and the help of GitLab Runner you can deploy static pages for your individual projects, your user or your group. +--- + + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Enable the pages feature in your GitLab EE instance](#enable-the-pages-feature-in-your-gitlab-ee-instance) +- [Understanding how GitLab Pages work](#understanding-how-gitlab-pages-work) +- [Two kinds of GitLab Pages](#two-kinds-of-gitlab-pages) + - [GitLab pages per user or group](#gitlab-pages-per-user-or-group) + - [GitLab pages per project](#gitlab-pages-per-project) +- [Enable the pages feature in your project](#enable-the-pages-feature-in-your-project) +- [Remove the contents of your pages](#remove-the-contents-of-your-pages) +- [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) +- [Example projects](#example-projects) +- [Custom error codes pages](#custom-error-codes-pages) + - [Adding a custom domain to your Pages](#adding-a-custom-domain-to-your-pages) + - [Securing your Pages with TLS](#securing-your-pages-with-tls) +- [Enable the pages feature in your project](#enable-the-pages-feature-in-your-project) +- [Remove the contents of your pages](#remove-the-contents-of-your-pages) +- [Limitations](#limitations) +- [Frequently Asked Questions](#frequently-asked-questions) + + + ## Enable the pages feature in your GitLab EE instance The administrator guide is located at [administration](administration.md). @@ -16,41 +43,60 @@ GitLab Pages rely heavily on GitLab CI and its ability to upload artifacts. The steps that are performed from the initialization of a project to the creation of the static content, can be summed up to: -1. Create project (its name could be specific according to the case) -1. Provide a specific job in `.gitlab-ci.yml` +1. Find out the general domain name that is used for GitLab Pages + (ask your administrator). This is very important, so you should first make + sure you get that right. +1. Create a project (its name should be specific according to the case) +1. Provide a specific job named `pages` in `.gitlab-ci.yml` 1. GitLab Runner builds the project 1. GitLab CI uploads the artifacts -1. Nginx serves the content +1. The [GitLab Pages daemon][pages-daemon] serves the content -As a user, you should normally be concerned only with the first three items. -If [shared runners](../ci/runners/README.md) are enabled by your GitLab +As a user, you should normally be concerned only with the first three or four +items. If [shared runners](../ci/runners/README.md) are enabled by your GitLab administrator, you should be able to use them instead of bringing your own. -In general there are four kinds of pages one might create. This is better -explained with an example so let's make some assumptions. +> **Note:** +> In the rest of this document we will assume that the general domain name that +> is used for GitLab Pages is `example.io`. -The domain under which the pages are hosted is named `gitlab.io`. There is a -user with the username `walter` and they are the owner of an organization named -`therug`. The personal project of `walter` is named `area51` and don't forget -that the organization has also a project under its namespace, called -`welovecats`. +## Two kinds of GitLab Pages -The following table depicts what the project's name should be and under which -URL it will be accessible. +In general there are two kinds of pages one might create: -| Pages type | Repository name | URL schema | -| ---------- | --------------- | ---------- | -| User page | `walter/walter.gitlab.io` | `https://walter.gitlab.io` | -| Group page | `therug/therug.gitlab.io` | `https://therug.gitlab.io` | -| Specific project under a user's page | `walter/area51` | `https://walter.gitlab.io/area51` | -| Specific project under a group's page | `therug/welovecats` | `https://therug.gitlab.io/welovecats` | +- Pages per user/group +- Pages per project -## Group pages +In GitLab, usernames and groupnames are unique and often people refer to them +as namespaces. There can be only one namespace in a GitLab instance. -To create a page for a group, add a new project to it. The project name must be lowercased. +> **Warning:** +> There are some known [limitations](#limitations) regarding namespaces served +> under the general domain name and HTTPS. Make sure to read that section. + +### GitLab pages per user or group + +Head over your GitLab instance that supports GitLab Pages and create a +repository named `username.example.io`, where `username` is your username on +GitLab. If the first part of the project name doesn’t match exactly your +username, it won’t work, so make sure to get it right. + +![Create a user-based pages repository](img/create_user_page.png) + +--- + +To create a group page the steps are exactly the same. Just make sure that +you are creating the project within the group's namespace. + +After you upload some static content to your repository, you will be able to +access it under `http(s)://username.example.io`. Keep reading to find out how. + +### GitLab pages per project + +> **Note:** +> You do _not_ have to create a project named `username.example.io` in order to +> serve a project's page. -For example, if you have a group called `TheRug` and pages are hosted under `Example.com`, -create a project named `therug.example.com`. ## Enable the pages feature in your project @@ -60,7 +106,7 @@ under its **Settings**. ## Remove the contents of your pages Pages can be explicitly removed from a project by clicking **Remove Pages** -in a project's **Settings**. +Go to your project's **Settings > Pages**. ## Explore the contents of .gitlab-ci.yml @@ -86,8 +132,8 @@ a commit is pushed only on the `master` branch, which is advisable to do so. The pages are created after the build completes successfully and the artifacts for the `pages` job are uploaded to GitLab. -The example below is using [Jekyll][] and assumes that the created HTML files -are generated under the `public/` directory. +The example below uses [Jekyll][] and generates the created HTML files +under the `public/` directory. ```yaml image: ruby:2.1 @@ -103,11 +149,31 @@ pages: - master ``` +The example below doesn't use any static site generator, but simply moves all +files from the root of the project to the `public/` directory. The `.public` +workaround is so `cp` doesn’t also copy `public/` to itself in an infinite +loop. + +```yaml +pages: + stage: deploy + script: + - mkdir .public + - cp -r * .public + - mv .public public + artifacts: + paths: + - public + only: + - master +``` + ## Example projects -Below is a list of example projects that make use of static generators. -Contributions are very welcome. +Below is a list of example projects for GitLab Pages with a plain HTML website +or various static site generators. Contributions are very welcome. +* [Plain HTML](https://gitlab.com/gitlab-examples/pages-plain-html) * [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) ## Custom error codes pages @@ -116,6 +182,34 @@ You can provide your own 403 and 404 error pages by creating the `403.html` and `404.html` files respectively in the `public/` directory that will be included in the artifacts. +### Adding a custom domain to your Pages + + + +### Securing your Pages with TLS + + +## Enable the pages feature in your project + +The GitLab Pages feature needs to be explicitly enabled for each project +under **Settings > Pages**. + +## Remove the contents of your pages + +Pages can be explicitly removed from a project by clicking **Remove Pages** +in a project's **Settings**. + +## Limitations + +When using Pages under the general domain of a GitLab instance (`*.example.io`), +you _cannot_ use HTTPS with sub-subdomains. That means that if your +username/groupname contains a dot, for example `foo.bar`, the domain +`https://foo.bar.example.io` will _not_ work. This is a limitation of the +[HTTP Over TLS protocol][rfc]. HTTP pages will continue to work provided you +don't redirect HTTP to HTTPS. + +[rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC" + ## Frequently Asked Questions **Q: Can I download my generated pages?** @@ -126,3 +220,5 @@ Sure. All you need to do is download the artifacts archive from the build page. [jekyll]: http://jekyllrb.com/ [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 +[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 +[pages-daemon]: https://gitlab.com/gitlab-org/gitlab-pages diff --git a/doc/pages/img/create_user_page.png b/doc/pages/img/create_user_page.png new file mode 100644 index 0000000000000000000000000000000000000000..8c2b67e233a3c0cc88488aeedcb3f0772d900bb8 GIT binary patch literal 66593 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

?My^3D9>T-WO?Uw*pk}j~oa(P{F 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 literal 0 HcmV?d00001 From 47bff50ffd68fccd6db71bc800194d49fad6eaa1 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 23 Feb 2016 19:29:24 +0200 Subject: [PATCH 106/183] Reorganize sections [ci skip] --- doc/pages/README.md | 72 +++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 73e18d1b9a7..50f45a4cae1 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -15,19 +15,17 @@ deploy static pages for your individual projects, your user or your group. **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [Enable the pages feature in your GitLab EE instance](#enable-the-pages-feature-in-your-gitlab-ee-instance) -- [Understanding how GitLab Pages work](#understanding-how-gitlab-pages-work) -- [Two kinds of GitLab Pages](#two-kinds-of-gitlab-pages) +- [Getting started with GitLab Pages](#getting-started-with-gitlab-pages) - [GitLab pages per user or group](#gitlab-pages-per-user-or-group) - [GitLab pages per project](#gitlab-pages-per-project) -- [Enable the pages feature in your project](#enable-the-pages-feature-in-your-project) -- [Remove the contents of your pages](#remove-the-contents-of-your-pages) -- [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) -- [Example projects](#example-projects) -- [Custom error codes pages](#custom-error-codes-pages) - - [Adding a custom domain to your Pages](#adding-a-custom-domain-to-your-pages) - - [Securing your Pages with TLS](#securing-your-pages-with-tls) -- [Enable the pages feature in your project](#enable-the-pages-feature-in-your-project) -- [Remove the contents of your pages](#remove-the-contents-of-your-pages) + - [Enable the pages feature in your project](#enable-the-pages-feature-in-your-project) + - [Remove the contents of your pages](#remove-the-contents-of-your-pages) + - [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) +- [Next steps](#next-steps) + - [Adding a custom domain to your Pages website](#adding-a-custom-domain-to-your-pages-website) + - [Securing your custom domain website with TLS](#securing-your-custom-domain-website-with-tls) + - [Example projects](#example-projects) + - [Custom error codes pages](#custom-error-codes-pages) - [Limitations](#limitations) - [Frequently Asked Questions](#frequently-asked-questions) @@ -37,7 +35,7 @@ deploy static pages for your individual projects, your user or your group. The administrator guide is located at [administration](administration.md). -## Understanding how GitLab Pages work +## Getting started with GitLab Pages GitLab Pages rely heavily on GitLab CI and its ability to upload artifacts. The steps that are performed from the initialization of a project to the @@ -60,8 +58,6 @@ administrator, you should be able to use them instead of bringing your own. > In the rest of this document we will assume that the general domain name that > is used for GitLab Pages is `example.io`. -## Two kinds of GitLab Pages - In general there are two kinds of pages one might create: - Pages per user/group @@ -78,7 +74,7 @@ as namespaces. There can be only one namespace in a GitLab instance. Head over your GitLab instance that supports GitLab Pages and create a repository named `username.example.io`, where `username` is your username on -GitLab. If the first part of the project name doesn’t match exactly your +GitLab. If the first part of the project name doesn't match exactly your username, it won’t work, so make sure to get it right. ![Create a user-based pages repository](img/create_user_page.png) @@ -97,18 +93,19 @@ access it under `http(s)://username.example.io`. Keep reading to find out how. > You do _not_ have to create a project named `username.example.io` in order to > serve a project's page. +GitLab Pages for projects -## Enable the pages feature in your project +### Enable the pages feature in your project The GitLab Pages feature needs to be explicitly enabled for each project under its **Settings**. -## Remove the contents of your pages +### Remove the contents of your pages Pages can be explicitly removed from a project by clicking **Remove Pages** Go to your project's **Settings > Pages**. -## Explore the contents of .gitlab-ci.yml +### Explore the contents of .gitlab-ci.yml Before reading this section, make sure you familiarize yourself with GitLab CI and the specific syntax of[`.gitlab-ci.yml`](../ci/yaml/README.md) by @@ -151,7 +148,7 @@ pages: The example below doesn't use any static site generator, but simply moves all files from the root of the project to the `public/` directory. The `.public` -workaround is so `cp` doesn’t also copy `public/` to itself in an infinite +workaround is so `cp` doesn't also copy `public/` to itself in an infinite loop. ```yaml @@ -168,36 +165,27 @@ pages: - master ``` -## Example projects +## Next steps + +### Adding a custom domain to your Pages website + + +### Securing your custom domain website with TLS + +### Example projects Below is a list of example projects for GitLab Pages with a plain HTML website or various static site generators. Contributions are very welcome. -* [Plain HTML](https://gitlab.com/gitlab-examples/pages-plain-html) -* [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) +- [Plain HTML](https://gitlab.com/gitlab-examples/pages-plain-html) +- [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) -## Custom error codes pages +### Custom error codes pages You can provide your own 403 and 404 error pages by creating the `403.html` and `404.html` files respectively in the `public/` directory that will be included in the artifacts. -### Adding a custom domain to your Pages - - - -### Securing your Pages with TLS - - -## Enable the pages feature in your project - -The GitLab Pages feature needs to be explicitly enabled for each project -under **Settings > Pages**. - -## Remove the contents of your pages - -Pages can be explicitly removed from a project by clicking **Remove Pages** -in a project's **Settings**. ## Limitations @@ -216,6 +204,12 @@ don't redirect HTTP to HTTPS. Sure. All you need to do is download the artifacts archive from the build page. + +**Q: Can I use GitLab Pages if my project is private?** + +Yes. GitLab Pages doesn't care whether you set your project's visibility level +to private, internal or public. + --- [jekyll]: http://jekyllrb.com/ From 5b9d886963dd9be2487226f34012bf0ed0de406d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 09:49:48 +0200 Subject: [PATCH 107/183] Add links to CI and runner --- doc/pages/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 50f45a4cae1..0d24d92a23c 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -5,7 +5,7 @@ > Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. With GitLab Pages you can host for free your static websites on GitLab. -Combined with the power of GitLab CI and the help of GitLab Runner you can +Combined with the power of [GitLab CI] and the help of [GitLab Runner] you can deploy static pages for your individual projects, your user or your group. --- @@ -216,3 +216,5 @@ to private, internal or public. [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 [ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 [pages-daemon]: https://gitlab.com/gitlab-org/gitlab-pages +[gitlab ci]: https://about.gitlab.com/gitlab-ci +[gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner From 81533b8f0c3efb9c1e11933f13e768550afa9d42 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 12:36:31 +0200 Subject: [PATCH 108/183] More section cleanup --- doc/pages/README.md | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 0d24d92a23c..17063ef3ba0 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -4,6 +4,10 @@ > This feature was [introduced][ee-80] in GitLab EE 8.3. > Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. +> **Note:** +> This document is about the user guide. To learn how to enable GitLab Pages +> across your GitLab instance, visit the [administrator documentation](administration.md). + With GitLab Pages you can host for free your static websites on GitLab. Combined with the power of [GitLab CI] and the help of [GitLab Runner] you can deploy static pages for your individual projects, your user or your group. @@ -14,13 +18,11 @@ deploy static pages for your individual projects, your user or your group. **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* -- [Enable the pages feature in your GitLab EE instance](#enable-the-pages-feature-in-your-gitlab-ee-instance) - [Getting started with GitLab Pages](#getting-started-with-gitlab-pages) - [GitLab pages per user or group](#gitlab-pages-per-user-or-group) - [GitLab pages per project](#gitlab-pages-per-project) - - [Enable the pages feature in your project](#enable-the-pages-feature-in-your-project) - - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) + - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [Next steps](#next-steps) - [Adding a custom domain to your Pages website](#adding-a-custom-domain-to-your-pages-website) - [Securing your custom domain website with TLS](#securing-your-custom-domain-website-with-tls) @@ -31,10 +33,6 @@ deploy static pages for your individual projects, your user or your group. -## Enable the pages feature in your GitLab EE instance - -The administrator guide is located at [administration](administration.md). - ## Getting started with GitLab Pages GitLab Pages rely heavily on GitLab CI and its ability to upload artifacts. @@ -95,16 +93,6 @@ access it under `http(s)://username.example.io`. Keep reading to find out how. GitLab Pages for projects -### Enable the pages feature in your project - -The GitLab Pages feature needs to be explicitly enabled for each project -under its **Settings**. - -### Remove the contents of your pages - -Pages can be explicitly removed from a project by clicking **Remove Pages** -Go to your project's **Settings > Pages**. - ### Explore the contents of .gitlab-ci.yml Before reading this section, make sure you familiarize yourself with GitLab CI @@ -165,6 +153,11 @@ pages: - master ``` +### Remove the contents of your pages + +Pages can be explicitly removed from a project by clicking **Remove Pages** +Go to your project's **Settings > Pages**. + ## Next steps ### Adding a custom domain to your Pages website From edc8782e85bfde761b4f2af52d62889176295703 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 14:10:51 +0200 Subject: [PATCH 109/183] Split user and group sections --- doc/pages/README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 17063ef3ba0..7337120298f 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -19,7 +19,8 @@ deploy static pages for your individual projects, your user or your group. **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [Getting started with GitLab Pages](#getting-started-with-gitlab-pages) - - [GitLab pages per user or group](#gitlab-pages-per-user-or-group) + - [GitLab pages per user](#gitlab-pages-per-user) + - [GitLab pages per group](#gitlab-pages-per-group) - [GitLab pages per project](#gitlab-pages-per-project) - [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) - [Remove the contents of your pages](#remove-the-contents-of-your-pages) @@ -68,7 +69,7 @@ as namespaces. There can be only one namespace in a GitLab instance. > There are some known [limitations](#limitations) regarding namespaces served > under the general domain name and HTTPS. Make sure to read that section. -### GitLab pages per user or group +### GitLab pages per user Head over your GitLab instance that supports GitLab Pages and create a repository named `username.example.io`, where `username` is your username on @@ -79,19 +80,25 @@ username, it won’t work, so make sure to get it right. --- -To create a group page the steps are exactly the same. Just make sure that -you are creating the project within the group's namespace. - After you upload some static content to your repository, you will be able to access it under `http(s)://username.example.io`. Keep reading to find out how. +### GitLab pages per group + +To create a group page the steps are the same like when creating a website for +users. Just make sure that you are creating the project within the group's +namespace. + +After you upload some static content to your repository, you will be able to +access it under `http(s)://groupname.example.io`. + ### GitLab pages per project > **Note:** > You do _not_ have to create a project named `username.example.io` in order to > serve a project's page. -GitLab Pages for projects + ### Explore the contents of .gitlab-ci.yml From bcf891719f89620544ab8f4ea7d91748fa277251 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 14:13:44 +0200 Subject: [PATCH 110/183] Convert CI quick start guide into a note [ci skip] --- doc/pages/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 7337120298f..f63cfb3cda2 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -102,11 +102,10 @@ access it under `http(s)://groupname.example.io`. ### Explore the contents of .gitlab-ci.yml -Before reading this section, make sure you familiarize yourself with GitLab CI -and the specific syntax of[`.gitlab-ci.yml`](../ci/yaml/README.md) by -following our [quick start guide](../ci/quick_start/README.md). - ---- +> **Note:** +> Before reading this section, make sure you familiarize yourself with GitLab CI +> and the specific syntax of[`.gitlab-ci.yml`](../ci/yaml/README.md) by +> following our [quick start guide](../ci/quick_start/README.md). To make use of GitLab Pages, your `.gitlab-ci.yml` must follow the rules below: From dbfde1d06f984ff37c5c953f914acf8d2ffe1c63 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 16:12:12 +0200 Subject: [PATCH 111/183] Add requirements section --- doc/pages/README.md | 46 +++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index f63cfb3cda2..42df9c79f22 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -19,6 +19,7 @@ deploy static pages for your individual projects, your user or your group. **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [Getting started with GitLab Pages](#getting-started-with-gitlab-pages) + - [GitLab Pages requirements](#gitlab-pages-requirements) - [GitLab pages per user](#gitlab-pages-per-user) - [GitLab pages per group](#gitlab-pages-per-group) - [GitLab pages per project](#gitlab-pages-per-project) @@ -36,39 +37,40 @@ deploy static pages for your individual projects, your user or your group. ## Getting started with GitLab Pages -GitLab Pages rely heavily on GitLab CI and its ability to upload artifacts. -The steps that are performed from the initialization of a project to the -creation of the static content, can be summed up to: +GitLab Pages rely heavily on GitLab CI and its ability to upload +[artifacts](../ci/yaml/README.md#artifacts). -1. Find out the general domain name that is used for GitLab Pages - (ask your administrator). This is very important, so you should first make - sure you get that right. -1. Create a project (its name should be specific according to the case) -1. Provide a specific job named `pages` in `.gitlab-ci.yml` -1. GitLab Runner builds the project -1. GitLab CI uploads the artifacts -1. The [GitLab Pages daemon][pages-daemon] serves the content +In general there are two kinds of pages one might create: -As a user, you should normally be concerned only with the first three or four -items. If [shared runners](../ci/runners/README.md) are enabled by your GitLab -administrator, you should be able to use them instead of bringing your own. +- Pages per user/group (`username.example.io`) +- Pages per project (`username.example.io/projectname`) + +In GitLab, usernames and groupnames are unique and often people refer to them +as namespaces. There can be only one namespace in a GitLab instance. > **Note:** > In the rest of this document we will assume that the general domain name that > is used for GitLab Pages is `example.io`. -In general there are two kinds of pages one might create: - -- Pages per user/group -- Pages per project - -In GitLab, usernames and groupnames are unique and often people refer to them -as namespaces. There can be only one namespace in a GitLab instance. - > **Warning:** > There are some known [limitations](#limitations) regarding namespaces served > under the general domain name and HTTPS. Make sure to read that section. +### GitLab Pages requirements + +In brief, this is what you need to upload your website in GitLab Pages: + +1. Find out the general domain name that is used for GitLab Pages + (ask your administrator). This is very important, so you should first make + sure you get that right. +1. Create a project +1. Provide a specific job named [`pages`](../ci/yaml/README.md#pages) in + [`.gitlab-ci.yml`](../ci/yaml/README.md) +1. A GitLab Runner to build GitLab Pages + +If [shared runners](../ci/runners/README.md) are enabled by your GitLab +administrator, you should be able to use them instead of bringing your own. + ### GitLab pages per user Head over your GitLab instance that supports GitLab Pages and create a From 34d75cff99d32f7f3a5e53fc47292328aa7ac1de Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 16:12:38 +0200 Subject: [PATCH 112/183] Rephrase --- doc/pages/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 42df9c79f22..4413a327246 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -82,8 +82,9 @@ username, it won’t work, so make sure to get it right. --- -After you upload some static content to your repository, you will be able to -access it under `http(s)://username.example.io`. Keep reading to find out how. +After you push some static content to your repository and GitLab Runner uploads +the artifacts to GitLab CI, you will be able to access your website under +`http(s)://username.example.io`. Keep reading to find out how. ### GitLab pages per group From 8f7eb4e3d04edc415af7a1dc58e96de366823c19 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 16:12:47 +0200 Subject: [PATCH 113/183] Add image for pages removal --- doc/pages/README.md | 4 +++- doc/pages/img/pages_remove.png | Bin 0 -> 19232 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 doc/pages/img/pages_remove.png diff --git a/doc/pages/README.md b/doc/pages/README.md index 4413a327246..6f1d2d27554 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -165,7 +165,9 @@ pages: ### Remove the contents of your pages Pages can be explicitly removed from a project by clicking **Remove Pages** -Go to your project's **Settings > Pages**. +in your project's **Settings > Pages**. + +![Remove pages](img/pages_remove.png) ## Next steps diff --git a/doc/pages/img/pages_remove.png b/doc/pages/img/pages_remove.png new file mode 100644 index 0000000000000000000000000000000000000000..36141bd4d12f518826ef330d234fc1dd55be7801 GIT binary patch literal 19232 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 Mc-4`>{jtm#<*;@2bbb1v>0vR_Q_2!? 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 Y7ByAt&5!<=GeJnblov04@!I$Q0QC>Ny8r+H literal 0 HcmV?d00001 From c9b63ee44ccedbbd1fd8b1e7da57442a25dd2e2c Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 16:26:34 +0200 Subject: [PATCH 114/183] Add pages job in yaml document --- doc/ci/yaml/README.md | 29 +++++++++++++++++++++++++++++ doc/pages/README.md | 5 +++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index f11257be5c3..ca293d54cb6 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1281,6 +1281,35 @@ with an API call. [Read more in the triggers documentation.](../triggers/README.md) +### pages + +`pages` is a special job that is used to upload static content to GitLab that +can be used to serve your website. It has a special syntax, so the two +requirements below must be met: + +1. Any static content must be placed under a `public/` directory +1. `artifacts` with a path to the `public/` directory must be defined + +The example below simply moves all files from the root of the project to the +`public/` directory. The `.public` workaround is so `cp` doesn't also copy +`public/` to itself in an infinite loop: + +``` +pages: + stage: deploy + script: + - mkdir .public + - cp -r * .public + - mv .public public + artifacts: + paths: + - public + only: + - master +``` + +Read more on [GitLab Pages user documentation](../../pages/README.md). + ## Validate the .gitlab-ci.yml Each instance of GitLab CI has an embedded debug tool called Lint. diff --git a/doc/pages/README.md b/doc/pages/README.md index 6f1d2d27554..c44d48e9cbf 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -64,7 +64,7 @@ In brief, this is what you need to upload your website in GitLab Pages: (ask your administrator). This is very important, so you should first make sure you get that right. 1. Create a project -1. Provide a specific job named [`pages`](../ci/yaml/README.md#pages) in +1. Provide a specific job named [`pages`][pages] in [`.gitlab-ci.yml`](../ci/yaml/README.md) 1. A GitLab Runner to build GitLab Pages @@ -112,7 +112,7 @@ access it under `http(s)://groupname.example.io`. To make use of GitLab Pages, your `.gitlab-ci.yml` must follow the rules below: -1. A special `pages` job must be defined +1. A special [`pages`][pages] job must be defined 1. Any static content must be placed under a `public/` directory 1. `artifacts` with a path to the `public/` directory must be defined @@ -222,3 +222,4 @@ to private, internal or public. [pages-daemon]: https://gitlab.com/gitlab-org/gitlab-pages [gitlab ci]: https://about.gitlab.com/gitlab-ci [gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner +[pages]: ../ci/yaml/README.md#pages From 5009acc04de0daf78472df5ec7061aac53bd6576 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 16:28:08 +0200 Subject: [PATCH 115/183] Convert shared runners into a note --- doc/pages/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index c44d48e9cbf..3cb560f0150 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -68,8 +68,9 @@ In brief, this is what you need to upload your website in GitLab Pages: [`.gitlab-ci.yml`](../ci/yaml/README.md) 1. A GitLab Runner to build GitLab Pages -If [shared runners](../ci/runners/README.md) are enabled by your GitLab -administrator, you should be able to use them instead of bringing your own. +> **Note:** +> If [shared runners](../ci/runners/README.md) are enabled by your GitLab +> administrator, you should be able to use them instead of bringing your own. ### GitLab pages per user From 69854d9786c5080c32a982f0dda5d29fe9f92f46 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 18:14:28 +0200 Subject: [PATCH 116/183] Merge user and group pages --- doc/pages/README.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 3cb560f0150..78100fa452b 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -20,8 +20,7 @@ deploy static pages for your individual projects, your user or your group. - [Getting started with GitLab Pages](#getting-started-with-gitlab-pages) - [GitLab Pages requirements](#gitlab-pages-requirements) - - [GitLab pages per user](#gitlab-pages-per-user) - - [GitLab pages per group](#gitlab-pages-per-group) + - [GitLab pages per user or group](#gitlab-pages-per-user-or-group) - [GitLab pages per project](#gitlab-pages-per-project) - [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) - [Remove the contents of your pages](#remove-the-contents-of-your-pages) @@ -72,7 +71,7 @@ In brief, this is what you need to upload your website in GitLab Pages: > If [shared runners](../ci/runners/README.md) are enabled by your GitLab > administrator, you should be able to use them instead of bringing your own. -### GitLab pages per user +### GitLab pages per user or group Head over your GitLab instance that supports GitLab Pages and create a repository named `username.example.io`, where `username` is your username on @@ -83,18 +82,13 @@ username, it won’t work, so make sure to get it right. --- -After you push some static content to your repository and GitLab Runner uploads -the artifacts to GitLab CI, you will be able to access your website under -`http(s)://username.example.io`. Keep reading to find out how. - -### GitLab pages per group - -To create a group page the steps are the same like when creating a website for +To create a group page, the steps are the same like when creating a website for users. Just make sure that you are creating the project within the group's namespace. -After you upload some static content to your repository, you will be able to -access it under `http(s)://groupname.example.io`. +After you push some static content to your repository and GitLab Runner uploads +the artifacts to GitLab CI, you will be able to access your website under +`http(s)://username.example.io`. Keep reading to find out how. ### GitLab pages per project From 7e7da5b2cbce6f4edab40f92d664fc4030314121 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 18:14:50 +0200 Subject: [PATCH 117/183] Add table explaining the types of pages [ci skip] --- doc/pages/README.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 78100fa452b..e36b651b9b3 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -36,10 +36,14 @@ deploy static pages for your individual projects, your user or your group. ## Getting started with GitLab Pages +> **Note:** +> In the rest of this document we will assume that the general domain name that +> is used for GitLab Pages is `example.io`. + GitLab Pages rely heavily on GitLab CI and its ability to upload [artifacts](../ci/yaml/README.md#artifacts). -In general there are two kinds of pages one might create: +In general there are two types of pages one might create: - Pages per user/group (`username.example.io`) - Pages per project (`username.example.io/projectname`) @@ -47,9 +51,12 @@ In general there are two kinds of pages one might create: In GitLab, usernames and groupnames are unique and often people refer to them as namespaces. There can be only one namespace in a GitLab instance. -> **Note:** -> In the rest of this document we will assume that the general domain name that -> is used for GitLab Pages is `example.io`. +| Type of GitLab Pages | Project name | Website served under | +| -------------------- | --------------- | -------------------- | +| User pages | `username.example.io` | `http(s)://username.example.io` | +| Group pages | `groupname.example.io` | `http(s)://groupname.example.io` | +| Project pages owned by a user | `projectname` | `http(s)://username.example.io/projectname` | +| Project pages owned by a group | `projectname` | `http(s)://groupname.example.io/projectname`| > **Warning:** > There are some known [limitations](#limitations) regarding namespaces served From ea8ff3922cec940024b03d7b3f87d07e8a02695f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 18:19:17 +0200 Subject: [PATCH 118/183] Rename user/project pages headings --- doc/pages/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index e36b651b9b3..ea46003cb3b 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -20,8 +20,8 @@ deploy static pages for your individual projects, your user or your group. - [Getting started with GitLab Pages](#getting-started-with-gitlab-pages) - [GitLab Pages requirements](#gitlab-pages-requirements) - - [GitLab pages per user or group](#gitlab-pages-per-user-or-group) - - [GitLab pages per project](#gitlab-pages-per-project) + - [User or group Pages](#user-or-group-pages) + - [Project Pages](#project-pages) - [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [Next steps](#next-steps) @@ -78,7 +78,7 @@ In brief, this is what you need to upload your website in GitLab Pages: > If [shared runners](../ci/runners/README.md) are enabled by your GitLab > administrator, you should be able to use them instead of bringing your own. -### GitLab pages per user or group +### User or group Pages Head over your GitLab instance that supports GitLab Pages and create a repository named `username.example.io`, where `username` is your username on @@ -97,14 +97,13 @@ After you push some static content to your repository and GitLab Runner uploads the artifacts to GitLab CI, you will be able to access your website under `http(s)://username.example.io`. Keep reading to find out how. -### GitLab pages per project +### Project Pages > **Note:** > You do _not_ have to create a project named `username.example.io` in order to > serve a project's page. - ### Explore the contents of .gitlab-ci.yml > **Note:** From d1fe6a6f840ea07304d0da0b8a8477c7946dd1d3 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 25 Feb 2016 10:12:10 +0200 Subject: [PATCH 119/183] Mention the power of CI and that Pages support all static generators --- doc/pages/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/pages/README.md b/doc/pages/README.md index ea46003cb3b..dadce43f724 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -12,6 +12,12 @@ With GitLab Pages you can host for free your static websites on GitLab. Combined with the power of [GitLab CI] and the help of [GitLab Runner] you can deploy static pages for your individual projects, your user or your group. +The key thing about GitLab Pages is the [`.gitlab-ci.yml`](../ci/yaml/README.md) +file, something that gives you absolute control over the build process. You can +actually watch your website being built live by following the CI build traces. + +GitLab Pages support any kind of [static site generator][staticgen]. + --- @@ -224,3 +230,4 @@ to private, internal or public. [gitlab ci]: https://about.gitlab.com/gitlab-ci [gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner [pages]: ../ci/yaml/README.md#pages +[staticgen]: https://www.staticgen.com/ From 758f5599bd0b4f4a624d1da247063d1384cd395f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 25 Feb 2016 10:13:26 +0200 Subject: [PATCH 120/183] Move Pages removal to Next steps section --- doc/pages/README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index dadce43f724..f0d2ecb995d 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -29,12 +29,12 @@ GitLab Pages support any kind of [static site generator][staticgen]. - [User or group Pages](#user-or-group-pages) - [Project Pages](#project-pages) - [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) - - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [Next steps](#next-steps) - [Adding a custom domain to your Pages website](#adding-a-custom-domain-to-your-pages-website) - [Securing your custom domain website with TLS](#securing-your-custom-domain-website-with-tls) - [Example projects](#example-projects) - [Custom error codes pages](#custom-error-codes-pages) + - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [Limitations](#limitations) - [Frequently Asked Questions](#frequently-asked-questions) @@ -197,6 +197,13 @@ You can provide your own 403 and 404 error pages by creating the `403.html` and `404.html` files respectively in the `public/` directory that will be included in the artifacts. +### Remove the contents of your pages + +If you ever feel the need to purge your Pages content, you can do so by going +to your project's **Settings > Pages** and hit **Remove pages**. Simple as that. + +![Remove pages](img/pages_remove.png) + ## Limitations From fa374abd9830fc2f21ee740c21f49d72179e9602 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 25 Feb 2016 10:14:13 +0200 Subject: [PATCH 121/183] Add note to use gitlab.io when using GitLab.com --- doc/pages/README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index f0d2ecb995d..ccad79fb026 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -44,10 +44,8 @@ GitLab Pages support any kind of [static site generator][staticgen]. > **Note:** > In the rest of this document we will assume that the general domain name that -> is used for GitLab Pages is `example.io`. - -GitLab Pages rely heavily on GitLab CI and its ability to upload -[artifacts](../ci/yaml/README.md#artifacts). +> is used for GitLab Pages is `example.io`. If you are using GitLab.com to +> host your website, replace `example.io` with `gitlab.io`. In general there are two types of pages one might create: From 3d91145cd0db52000eab67df7db0afec705b7f40 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 25 Feb 2016 10:16:16 +0200 Subject: [PATCH 122/183] Be more precise who people are --- doc/pages/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index ccad79fb026..db445f10088 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -52,7 +52,7 @@ In general there are two types of pages one might create: - Pages per user/group (`username.example.io`) - Pages per project (`username.example.io/projectname`) -In GitLab, usernames and groupnames are unique and often people refer to them +In GitLab, usernames and groupnames are unique and we often refer to them as namespaces. There can be only one namespace in a GitLab instance. | Type of GitLab Pages | Project name | Website served under | From a3aef3551a7d35241da51ec59060e83434da982b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 25 Feb 2016 10:16:44 +0200 Subject: [PATCH 123/183] Be more clear what project name is referred to --- doc/pages/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index db445f10088..a5c9b9c23c3 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -55,8 +55,8 @@ In general there are two types of pages one might create: In GitLab, usernames and groupnames are unique and we often refer to them as namespaces. There can be only one namespace in a GitLab instance. -| Type of GitLab Pages | Project name | Website served under | -| -------------------- | --------------- | -------------------- | +| Type of GitLab Pages | Project name created in GitLab | Website URL | +| -------------------- | ------------ | ----------- | | User pages | `username.example.io` | `http(s)://username.example.io` | | Group pages | `groupname.example.io` | `http(s)://groupname.example.io` | | Project pages owned by a user | `projectname` | `http(s)://username.example.io/projectname` | From 18478e5d4fedf80ce8b62acf3c1a765b3eaa127d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 25 Feb 2016 10:19:09 +0200 Subject: [PATCH 124/183] Reword --- doc/pages/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index a5c9b9c23c3..d6f8d28fa7c 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -74,8 +74,8 @@ In brief, this is what you need to upload your website in GitLab Pages: (ask your administrator). This is very important, so you should first make sure you get that right. 1. Create a project -1. Provide a specific job named [`pages`][pages] in - [`.gitlab-ci.yml`](../ci/yaml/README.md) +1. Push a [`.gitlab-ci.yml`](../ci/yaml/README.md) file in your repository with + a specific job named [`pages`][pages] 1. A GitLab Runner to build GitLab Pages > **Note:** From e7b2784c956a7b6698ec23d6c5a62f4a83bc2fbd Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 4 Mar 2016 16:09:25 +0200 Subject: [PATCH 125/183] Add new sections and clean-up - Finish user/group/project sections - Give more .gitlab-ci.yml examples [ci skip] --- doc/pages/README.md | 221 ++++++++++++++++------ doc/pages/img/create_user_page.png | Bin 66593 -> 0 bytes doc/pages/img/pages_create_project.png | Bin 0 -> 33597 bytes doc/pages/img/pages_create_user_page.png | Bin 0 -> 87071 bytes doc/pages/img/pages_new_domain_button.png | Bin 0 -> 51136 bytes doc/pages/img/pages_remove.png | Bin 19232 -> 27259 bytes 6 files changed, 165 insertions(+), 56 deletions(-) delete mode 100644 doc/pages/img/create_user_page.png create mode 100644 doc/pages/img/pages_create_project.png create mode 100644 doc/pages/img/pages_create_user_page.png create mode 100644 doc/pages/img/pages_new_domain_button.png diff --git a/doc/pages/README.md b/doc/pages/README.md index d6f8d28fa7c..8ca80b2f0bc 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -12,12 +12,6 @@ With GitLab Pages you can host for free your static websites on GitLab. Combined with the power of [GitLab CI] and the help of [GitLab Runner] you can deploy static pages for your individual projects, your user or your group. -The key thing about GitLab Pages is the [`.gitlab-ci.yml`](../ci/yaml/README.md) -file, something that gives you absolute control over the build process. You can -actually watch your website being built live by following the CI build traces. - -GitLab Pages support any kind of [static site generator][staticgen]. - --- @@ -28,15 +22,22 @@ GitLab Pages support any kind of [static site generator][staticgen]. - [GitLab Pages requirements](#gitlab-pages-requirements) - [User or group Pages](#user-or-group-pages) - [Project Pages](#project-pages) - - [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) + - [Explore the contents of `.gitlab-ci.yml`](#explore-the-contents-of-gitlab-ciyml) + - [How `.gitlab-ci.yml` looks like when using plain HTML files](#how-gitlab-ciyml-looks-like-when-using-plain-html-files) + - [How `.gitlab-ci.yml` looks like when using a static generator](#how-gitlab-ciyml-looks-like-when-using-a-static-generator) + - [How to set up GitLab Pages in a repository where there's also actual code](#how-to-set-up-gitlab-pages-in-a-repository-where-there-s-also-actual-code) - [Next steps](#next-steps) - - [Adding a custom domain to your Pages website](#adding-a-custom-domain-to-your-pages-website) - - [Securing your custom domain website with TLS](#securing-your-custom-domain-website-with-tls) - - [Example projects](#example-projects) + - [Add a custom domain to your Pages website](#add-a-custom-domain-to-your-pages-website) + - [Secure your custom domain website with TLS](#secure-your-custom-domain-website-with-tls) + - [Use a static generator to develop your website](#use-a-static-generator-to-develop-your-website) + - [Example projects](#example-projects) - [Custom error codes pages](#custom-error-codes-pages) - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [Limitations](#limitations) - [Frequently Asked Questions](#frequently-asked-questions) + - [Can I download my generated pages?](#can-i-download-my-generated-pages) + - [Can I use GitLab Pages if my project is private?](#can-i-use-gitlab-pages-if-my-project-is-private) + - [Q: Do I have to create a project named `username.example.io` in order to host a project website?](#q-do-i-have-to-create-a-project-named-username-example-io-in-order-to-host-a-project-website) @@ -49,13 +50,16 @@ GitLab Pages support any kind of [static site generator][staticgen]. In general there are two types of pages one might create: -- Pages per user/group (`username.example.io`) -- Pages per project (`username.example.io/projectname`) +- Pages per user (`username.example.io`) or per group (`groupname.example.io`) +- Pages per project (`username.example.io/projectname` or `groupname.example.io/projectname`) In GitLab, usernames and groupnames are unique and we often refer to them -as namespaces. There can be only one namespace in a GitLab instance. +as namespaces. There can be only one namespace in a GitLab instance. Below you +can see the connection between the type of GitLab Pages, what the project name +that is created on GitLab looks like and the website URL it will be ultimately +be served on. -| Type of GitLab Pages | Project name created in GitLab | Website URL | +| Type of GitLab Pages | The name of the project created in GitLab | Website URL | | -------------------- | ------------ | ----------- | | User pages | `username.example.io` | `http(s)://username.example.io` | | Group pages | `groupname.example.io` | `http(s)://groupname.example.io` | @@ -74,9 +78,9 @@ In brief, this is what you need to upload your website in GitLab Pages: (ask your administrator). This is very important, so you should first make sure you get that right. 1. Create a project -1. Push a [`.gitlab-ci.yml`](../ci/yaml/README.md) file in your repository with - a specific job named [`pages`][pages] -1. A GitLab Runner to build GitLab Pages +1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory + of your repository with a specific job named [`pages`][pages]. +1. Set up a GitLab Runner to build your website > **Note:** > If [shared runners](../ci/runners/README.md) are enabled by your GitLab @@ -84,63 +88,91 @@ In brief, this is what you need to upload your website in GitLab Pages: ### User or group Pages +For user and group pages, the name of the project should be specific to the +username or groupname and the general domain name that is used for GitLab Pages. Head over your GitLab instance that supports GitLab Pages and create a repository named `username.example.io`, where `username` is your username on GitLab. If the first part of the project name doesn't match exactly your username, it won’t work, so make sure to get it right. -![Create a user-based pages repository](img/create_user_page.png) - ---- - To create a group page, the steps are the same like when creating a website for users. Just make sure that you are creating the project within the group's namespace. +![Create a user-based pages project](img/pages_create_user_page.png) + +--- + After you push some static content to your repository and GitLab Runner uploads the artifacts to GitLab CI, you will be able to access your website under `http(s)://username.example.io`. Keep reading to find out how. +>**Note:** +If your username/groupname contains a dot, for example `foo.bar`, you will not +be able to use the wildcard domain HTTPS, read more at [limitations](#limitations). + ### Project Pages -> **Note:** -> You do _not_ have to create a project named `username.example.io` in order to -> serve a project's page. +GitLab Pages for projects can be created by both user and group accounts. +The steps to create a project page for a user or a group are identical: +1. Create a new project +1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory + of your repository with a specific job named [`pages`][pages]. +1. Set up a GitLab Runner to build your website -### Explore the contents of .gitlab-ci.yml +A user's project will be served under `http(s)://username.example.io/projectname` +whereas a group's project under `http(s)://groupname.example.io/projectname`. + +### Explore the contents of `.gitlab-ci.yml` + +The key thing about GitLab Pages is the `.gitlab-ci.yml` file, something that +gives you absolute control over the build process. You can actually watch your +website being built live by following the CI build traces. > **Note:** > Before reading this section, make sure you familiarize yourself with GitLab CI > and the specific syntax of[`.gitlab-ci.yml`](../ci/yaml/README.md) by > following our [quick start guide](../ci/quick_start/README.md). -To make use of GitLab Pages, your `.gitlab-ci.yml` must follow the rules below: +To make use of GitLab Pages, the contents of `.gitlab-ci.yml` must follow the +rules below: -1. A special [`pages`][pages] job must be defined -1. Any static content must be placed under a `public/` directory +1. A special job named [`pages`][pages] must be defined +1. Any static content which will be served by GitLab Pages must be placed under + a `public/` directory 1. `artifacts` with a path to the `public/` directory must be defined +In its simplest form, `.gitlab-ci.yml` looks like: + +```yaml +pages: + script: + - my_commands + artifacts: + paths: + - public +``` + +When the Runner reaches to build the `pages` job, it executes whatever is +defined in the `script` parameter and if the build completes with a non-zero +exit status, it then uploads the `public/` directory to GitLab Pages. + +The `public/` directory should contain all the static content of your website. +Depending on how you plan to publish your website, the steps defined in the +[`script` parameter](../ci/yaml/README.md#script) may differ. + Be aware that Pages are by default branch/tag agnostic and their deployment relies solely on what you specify in `.gitlab-ci.yml`. If you don't limit the `pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), whenever a new commit is pushed to whatever branch or tag, the Pages will be -overwritten. In the examples below, we limit the Pages to be deployed whenever -a commit is pushed only on the `master` branch, which is advisable to do so. - -The pages are created after the build completes successfully and the artifacts -for the `pages` job are uploaded to GitLab. - -The example below uses [Jekyll][] and generates the created HTML files -under the `public/` directory. +overwritten. In the example below, we limit the Pages to be deployed whenever +a commit is pushed only on the `master` branch: ```yaml -image: ruby:2.1 - pages: script: - - gem install jekyll - - jekyll build -d public/ + - my_commands artifacts: paths: - public @@ -148,14 +180,28 @@ pages: - master ``` -The example below doesn't use any static site generator, but simply moves all -files from the root of the project to the `public/` directory. The `.public` -workaround is so `cp` doesn't also copy `public/` to itself in an infinite -loop. +We then tell the Runner to treat the `public/` directory as `artifacts` and +upload it to GitLab. And since all these parameters were all under a `pages` +job, the contents of the `public` directory will be served by GitLab Pages. + +#### How `.gitlab-ci.yml` looks like when the static content is in your repository + +Supposedly your repository contained the following files: + +``` +├── index.html +├── css +│   └── main.css +└── js + └── main.js +``` + +Then the `.gitlab-ci.yml` example below simply moves all files from the root +directory of the project to the `public/` directory. The `.public` workaround +is so `cp` doesn't also copy `public/` to itself in an infinite loop: ```yaml pages: - stage: deploy script: - mkdir .public - cp -r * .public @@ -167,27 +213,84 @@ pages: - master ``` -### Remove the contents of your pages +### How `.gitlab-ci.yml` looks like when using a static generator -Pages can be explicitly removed from a project by clicking **Remove Pages** -in your project's **Settings > Pages**. +In general, GitLab Pages support any kind of [static site generator][staticgen], +since the Runner can be configured to run any possible command. -![Remove pages](img/pages_remove.png) +In the root directory of your Git repository, place the source files of your +favorite static generator. Then provide a `.gitlab-ci.yml` file which is +specific to your static generator. + +The example below, uses [Jekyll] to build the static site: + +```yaml +pages: + images: jekyll/jekyll:latest + script: + - jekyll build -d public/ + artifacts: + paths: + - public + only: + - master +``` + +Here, we used the Docker executor and in the first line we specified the base +image against which our builds will run. + +You have to make sure that the generated static files are ultimately placed +under the `public` directory, that's why in the `script` section we run the +`jekyll` command that builds the website and puts all content in the `public/` +directory. + +We then tell the Runner to treat the `public/` directory as `artifacts` and +upload it to GitLab. + +--- + +See the [jekyll example project][pages-jekyll] to better understand how this +works. + +For a list of Pages projects, see [example projects](#example-projects) to get +you started. + +#### How to set up GitLab Pages in a repository where there's also actual code + +You can have your project's code in the `master` branch and use an orphan +`pages` branch that will host your static generator site. ## Next steps -### Adding a custom domain to your Pages website +### Add a custom domain to your Pages website +If this setting is enabled by your GitLab administrator, you should be able to +see the **New Domain** button when visiting your project's **Settings > Pages**. -### Securing your custom domain website with TLS +![New domain button](img/pages_new_domain_button.png) -### Example projects +--- + +You are not limited to one domain per can add multiple domains pointing to your +website hosted under GitLab. + +### Secure your custom domain website with TLS + +### Use a static generator to develop your website + +#### Example projects Below is a list of example projects for GitLab Pages with a plain HTML website or various static site generators. Contributions are very welcome. - [Plain HTML](https://gitlab.com/gitlab-examples/pages-plain-html) - [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) +- [Hugo](https://gitlab.com/gitlab-examples/pages-hugo) +- [Middleman](https://gitlab.com/gitlab-examples/pages-middleman) +- [Hexo](https://gitlab.com/gitlab-examples/pages-hexo) +- [Brunch](https://gitlab.com/gitlab-examples/pages-brunch) +- [Metalsmith](https://gitlab.com/gitlab-examples/pages-metalsmith) +- [Harp](https://gitlab.com/gitlab-examples/pages-harp) ### Custom error codes pages @@ -216,16 +319,21 @@ don't redirect HTTP to HTTPS. ## Frequently Asked Questions -**Q: Can I download my generated pages?** +### Can I download my generated pages? Sure. All you need to do is download the artifacts archive from the build page. +### Can I use GitLab Pages if my project is private? -**Q: Can I use GitLab Pages if my project is private?** - -Yes. GitLab Pages doesn't care whether you set your project's visibility level +Yes. GitLab Pages don't care whether you set your project's visibility level to private, internal or public. +### Q: Do I have to create a project named `username.example.io` in order to host a project website? + +No. You can create a new project named `foo` and have it served under +`http(s)://username.example.io/foo` without having previously created a +user page. + --- [jekyll]: http://jekyllrb.com/ @@ -236,3 +344,4 @@ to private, internal or public. [gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner [pages]: ../ci/yaml/README.md#pages [staticgen]: https://www.staticgen.com/ +[pages-jekyll]: https://gitlab.com/gitlab-examples/pages-jekyll diff --git a/doc/pages/img/create_user_page.png b/doc/pages/img/create_user_page.png deleted file mode 100644 index 8c2b67e233a3c0cc88488aeedcb3f0772d900bb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66593 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

?My^3D9>T-WO?Uw*pk}j~oa(P{F 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 diff --git a/doc/pages/img/pages_create_project.png b/doc/pages/img/pages_create_project.png new file mode 100644 index 0000000000000000000000000000000000000000..a936d8e5dbde3825a8898692944092c49de85733 GIT binary patch literal 33597 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

oqku|!oPj@=h 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

-^IpX1miIQ?G{GpIY!tp$tO)>A#5p~{~z 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 F{{x;TkD>qo literal 0 HcmV?d00001 diff --git a/doc/pages/img/pages_remove.png b/doc/pages/img/pages_remove.png index 36141bd4d12f518826ef330d234fc1dd55be7801..adbfb654877d8dede23e21405c3dfce87ed09e5e 100644 GIT binary patch literal 27259 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 X-~YQoe7f|%a;mFkq*-wD{<8^M literal 19232 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 Mc-4`>{jtm#<*;@2bbb1v>0vR_Q_2!? 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 Y7ByAt&5!<=GeJnblov04@!I$Q0QC>Ny8r+H From 52dcde272a7766dd22a045fe7ca94e3690a9590e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 4 Mar 2016 16:56:05 +0200 Subject: [PATCH 126/183] Move examples to own section --- doc/pages/README.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 8ca80b2f0bc..f614781e2ba 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -23,14 +23,13 @@ deploy static pages for your individual projects, your user or your group. - [User or group Pages](#user-or-group-pages) - [Project Pages](#project-pages) - [Explore the contents of `.gitlab-ci.yml`](#explore-the-contents-of-gitlab-ciyml) - - [How `.gitlab-ci.yml` looks like when using plain HTML files](#how-gitlab-ciyml-looks-like-when-using-plain-html-files) - - [How `.gitlab-ci.yml` looks like when using a static generator](#how-gitlab-ciyml-looks-like-when-using-a-static-generator) + - [How `.gitlab-ci.yml` looks like when the static content is in your repository](#how-gitlab-ciyml-looks-like-when-the-static-content-is-in-your-repository) + - [How `.gitlab-ci.yml` looks like when using a static generator](#how-gitlab-ciyml-looks-like-when-using-a-static-generator) - [How to set up GitLab Pages in a repository where there's also actual code](#how-to-set-up-gitlab-pages-in-a-repository-where-there-s-also-actual-code) - [Next steps](#next-steps) + - [Example projects](#example-projects) - [Add a custom domain to your Pages website](#add-a-custom-domain-to-your-pages-website) - [Secure your custom domain website with TLS](#secure-your-custom-domain-website-with-tls) - - [Use a static generator to develop your website](#use-a-static-generator-to-develop-your-website) - - [Example projects](#example-projects) - [Custom error codes pages](#custom-error-codes-pages) - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [Limitations](#limitations) @@ -262,6 +261,20 @@ You can have your project's code in the `master` branch and use an orphan ## Next steps +### Example projects + +Below is a list of example projects for GitLab Pages with a plain HTML website +or various static site generators. Contributions are very welcome. + +- [Plain HTML](https://gitlab.com/gitlab-examples/pages-plain-html) +- [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) +- [Hugo](https://gitlab.com/gitlab-examples/pages-hugo) +- [Middleman](https://gitlab.com/gitlab-examples/pages-middleman) +- [Hexo](https://gitlab.com/gitlab-examples/pages-hexo) +- [Brunch](https://gitlab.com/gitlab-examples/pages-brunch) +- [Metalsmith](https://gitlab.com/gitlab-examples/pages-metalsmith) +- [Harp](https://gitlab.com/gitlab-examples/pages-harp) + ### Add a custom domain to your Pages website If this setting is enabled by your GitLab administrator, you should be able to @@ -305,7 +318,6 @@ to your project's **Settings > Pages** and hit **Remove pages**. Simple as that. ![Remove pages](img/pages_remove.png) - ## Limitations When using Pages under the general domain of a GitLab instance (`*.example.io`), From 50d32d6cc6cf8643c1676f6241a125623a5af06e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 4 Mar 2016 16:56:23 +0200 Subject: [PATCH 127/183] Add examples to 404 pages [ci skip] --- doc/pages/README.md | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index f614781e2ba..dc425f6a564 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -289,27 +289,22 @@ website hosted under GitLab. ### Secure your custom domain website with TLS -### Use a static generator to develop your website - -#### Example projects - -Below is a list of example projects for GitLab Pages with a plain HTML website -or various static site generators. Contributions are very welcome. - -- [Plain HTML](https://gitlab.com/gitlab-examples/pages-plain-html) -- [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) -- [Hugo](https://gitlab.com/gitlab-examples/pages-hugo) -- [Middleman](https://gitlab.com/gitlab-examples/pages-middleman) -- [Hexo](https://gitlab.com/gitlab-examples/pages-hexo) -- [Brunch](https://gitlab.com/gitlab-examples/pages-brunch) -- [Metalsmith](https://gitlab.com/gitlab-examples/pages-metalsmith) -- [Harp](https://gitlab.com/gitlab-examples/pages-harp) ### Custom error codes pages You can provide your own 403 and 404 error pages by creating the `403.html` and -`404.html` files respectively in the `public/` directory that will be included -in the artifacts. +`404.html` files respectively in the root directory of the `public/` directory +that will be included in the artifacts. + +If the case of `404.html`, there are different scenarios. For example: + +- If you use project Pages (served under `/projectname/`) and try to access + `/projectname/non/exsiting_file`, GitLab Pages will try to serve first + `/projectname/404.html`, and then `/404.html`. +- If you use user/group Pages (served under `/`) and try to access + `/non/existing_file` GitLab Pages will try to serve `/404.html`. +- If you use a custom domain and try to access `/non/existing_file`, GitLab + Pages will try to server only `/404.html`. ### Remove the contents of your pages From 231e3a2b13ef4b4cae59c1c3828a8a4c0dd28239 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 4 Mar 2016 18:07:00 +0200 Subject: [PATCH 128/183] Add example of hosting Pages in a specific branch [ci skip] --- doc/pages/README.md | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index dc425f6a564..effa2a35938 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -256,8 +256,46 @@ you started. #### How to set up GitLab Pages in a repository where there's also actual code -You can have your project's code in the `master` branch and use an orphan -`pages` branch that will host your static generator site. +Remember that GitLab Pages are by default branch/tag agnostic and their +deployment relies solely on what you specify in `.gitlab-ci.yml`. You can limit +the `pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), +whenever a new commit is pushed to a branch that will be used specifically for +your pages. + +That way, you can have your project's code in the `master` branch and use an +orphan branch (let's name it `pages`) that will host your static generator site. + +You can create a new empty branch like this: + +```bash +git checkout --orphan pages +``` + +The first commit made on this new branch will have no parents and it will be +the root of a new history totally disconnected from all the other branches and +commits. Push the source files of your static generator in the `pages` branch. + +Below is a copy of `.gitlab-ci.yml` where the most significant line is the last +one, specifying to execute everything in the `pages` branch: + +``` +pages: + images: jekyll/jekyll:latest + script: + - jekyll build -d public/ + artifacts: + paths: + - public + only: + - pages +``` + +See an example that has different files in the [`master` branch][jekyll-master] +and the source files for Jekyll are in a [`pages` branch][jekyll-pages] which +also includes `.gitlab-ci.yml`. + +[jekyll-master]: https://gitlab.com/gitlab-examples/pages-jekyll-branched/tree/master +[jekyll-pages]: https://gitlab.com/gitlab-examples/pages-jekyll-branched/tree/pages ## Next steps From 8442e3f05dfd822630934f6d3f7b4c478b78e0f5 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 4 Mar 2016 18:43:21 +0200 Subject: [PATCH 129/183] Add sections on custom domains [ci skip] --- doc/pages/README.md | 30 ++++++++++++++++++++--- doc/pages/img/pages_dns_details.png | Bin 0 -> 34686 bytes doc/pages/img/pages_multiple_domains.png | Bin 0 -> 63716 bytes doc/pages/img/pages_upload_cert.png | Bin 0 -> 103730 bytes 4 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 doc/pages/img/pages_dns_details.png create mode 100644 doc/pages/img/pages_multiple_domains.png create mode 100644 doc/pages/img/pages_upload_cert.png diff --git a/doc/pages/README.md b/doc/pages/README.md index effa2a35938..080570cdaff 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -24,7 +24,7 @@ deploy static pages for your individual projects, your user or your group. - [Project Pages](#project-pages) - [Explore the contents of `.gitlab-ci.yml`](#explore-the-contents-of-gitlab-ciyml) - [How `.gitlab-ci.yml` looks like when the static content is in your repository](#how-gitlab-ciyml-looks-like-when-the-static-content-is-in-your-repository) - - [How `.gitlab-ci.yml` looks like when using a static generator](#how-gitlab-ciyml-looks-like-when-using-a-static-generator) + - [How `.gitlab-ci.yml` looks like when using a static generator](#how-gitlab-ciyml-looks-like-when-using-a-static-generator) - [How to set up GitLab Pages in a repository where there's also actual code](#how-to-set-up-gitlab-pages-in-a-repository-where-there-s-also-actual-code) - [Next steps](#next-steps) - [Example projects](#example-projects) @@ -212,7 +212,7 @@ pages: - master ``` -### How `.gitlab-ci.yml` looks like when using a static generator +#### How `.gitlab-ci.yml` looks like when using a static generator In general, GitLab Pages support any kind of [static site generator][staticgen], since the Runner can be configured to run any possible command. @@ -299,6 +299,9 @@ also includes `.gitlab-ci.yml`. ## Next steps +So you have successfully deployed your website, congratulations! Let's check +what more you can do with GitLab Pages. + ### Example projects Below is a list of example projects for GitLab Pages with a plain HTML website @@ -313,6 +316,9 @@ or various static site generators. Contributions are very welcome. - [Metalsmith](https://gitlab.com/gitlab-examples/pages-metalsmith) - [Harp](https://gitlab.com/gitlab-examples/pages-harp) +Visit the gitlab-examples group for a full list of projects: +. + ### Add a custom domain to your Pages website If this setting is enabled by your GitLab administrator, you should be able to @@ -322,11 +328,27 @@ see the **New Domain** button when visiting your project's **Settings > Pages**. --- -You are not limited to one domain per can add multiple domains pointing to your -website hosted under GitLab. +You can add multiple domains pointing to your website hosted under GitLab. +Once the domain is added, you can see it listed under the **Domains** section. + +![Pages multiple domains](img/pages_multiple_domains.png) + +--- + +As a last step, you need to configure your DNS and add a CNAME pointing to your +user/group page. Click on the **Details** button of a domain for further +instructions. + +![Pages DNS details](img/pages_dns_details.png) ### Secure your custom domain website with TLS +When you add a new custom domain, you also have the chance to add a TLS +certificate. If this setting is enabled by your GitLab administrator, you +should be able to see the option to upload the public certificate and the +private key when adding a new domain. + +![Pages upload cert](img/pages_upload_cert.png) ### Custom error codes pages diff --git a/doc/pages/img/pages_dns_details.png b/doc/pages/img/pages_dns_details.png new file mode 100644 index 0000000000000000000000000000000000000000..8d34f3b7f386d2b51eac5c4312eddf82da875337 GIT binary patch literal 34686 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

&tIn< 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 u4=1FDGYW=q@<1STzHaq%%q+P?w4jw2KR literal 0 HcmV?d00001 diff --git a/doc/pages/img/pages_upload_cert.png b/doc/pages/img/pages_upload_cert.png new file mode 100644 index 0000000000000000000000000000000000000000..06d85ab197186e06428de2466aede378e656f253 GIT binary patch literal 103730 zcmc$_bySpV_%=Fpr_w1QNH-!SrAT*oOG|fmBcLeV(jeU+NT*0hBMp+$d7g2*b^p%! z)>-RY-#Y8e9|JS<&b&|DaoyK_J>d#+&oNMmQ6Ufr#tTVtB?tte4+4QrMn(khe3-Kr z0Ds`kMPx-FkjhxJYeNL^n$lQONfrX}cnE>`1wkN};4QyR2*jBU0@*fzK%OK+AO!ZQ zjf&9kA{t3Q7YBdA<3g;-7yLnTdLb);v;+r_OHCj0 zGI*B9t)3y_qL|D*Wn)+z`FRjG=eftXk?v?W`nJxA0^e>$%v7$J+58YJ&@h?p4+rjD-aiu@TalCWQqgb!7MI!Z;E|>!E>pQnW^*IB z6g^IOkfX+3;P0h5;uhGNCQFB+aLb~BOA{Dw>O1w@@O@T{to5xN-1GY5j+B3H?~yv; zgx3&23qxYxQw4+5hKzQn9}iuV7Ch$YKU*+m0ZLoltK)niFMw$#F|PY9a-8FzM% zf1K*~_x%y@d*jvfqXzD>U#bX|C?Gc?N1-^B4WyP4rNIi^G%qBJN&R@z2 z-xoBa+K4z9ukDxZF&w;a+oOx~dRrb%Fvfp z$jvgFdDO@M`WH&pBR1U>bY14M{gzq-cW1bbncA%xOmEX6hF$C;9@tfGcbi_J2OHY+ zOFV(!|BO(Dq;Rp&4WL?AmZ}pKDgQWBG%p=4q{5ln>z)yNh^)8t4VOn`KXDJ zMF|Pb4q~EbQ(T0$C3x$i3uuCE+i(6F(iaKIW<>>Tjpv%){w6k8CwVGkYOPN?e!f3e z#?BHs+AiVY<$9`@_SD@b*AT~{6+P_Fvd0&`j8JAcNt~$jXwEOxs7B}tM(iz4DA$=f z-(*%#NLuZ7V=Jg^+nLSZsVTE*tW|ey{|ziNX%lkWuo|_^}+5TD~t&`}4IAjh62nB(&Rga!pgRife<*h|Bw2b-l51>8D5p z%3sPkc*E(tYdg0JM^QG6X129@=O59Z{#o1)EN;_`6^!@5Pv{#o*G_OL=vjGB643C1 z7}Hu!H(IH0;g`zT|L6KmMZ=U^)Uh5sd`qF`ZRnPa3dBK9-djpIXnEgHt15&En?Ml>Js zh+xeO_V;s~Uf?KVn3|crZ$$go;Evr2n;n|JTiMJ)I@R8h*Z}igrzw|vf=A`i_4vt? z|6X~{wrZF&@uJ7*cw*HEPOfWIb+vJuo5q#zPr$_yB(G+ET}FD3=UK@#{Yd&(d>Yye~tD` zva9V{5N_q~yyfxZ(^+lSz~-E2ZC0_L6v@dsWCdpK*3=sC!L9$jE|yo8vGL@yPZDvz znmM<7&8GGXmZH2}^r%1HfFtN0R6;uCEVH&d(@$c|4>?I%4Ot~DZ znKY}?4Hw(2xv$}^zwdu^@?(5Lo>qZ>OU-HR0qbIS`(Jc2me3RTm0>1k4DW!)qaROq zOU?45iLO4mgT?fS-+$*k6z5hk`Vnu@$|IWCx_nH^LAOxtIPGuR#Sv^Ms>*IYdL^nB zwIkI)6{Z;+Vd)_HzMtrF78Y^jkIjdX+s%A$?zenXtg$ef$QW?+dg__nQGYbs`QON{ z!fT<8QpUPp-+CGKKD3!iEO0esow~eUwwK_K1>gv_GL)Z}Nvejp6j*h>KmlO`~@=EJt-h@m9^K!A$g(p zNwd_qiGC^lYJwf~#{56oXY;V&(L}`PS;(Z_{Sf#*jJ^x1+#K|n|86oDLTA=S%V_e) zQSSibkqJ#;2OT`Rbc6q6H)Lm}00eQAHKojKj#d1je6&P&Oxu3FJ~A8*cCY_AWtd(ZH3_!-Y{7 zOO?sNFgl%aVquF;m`Fp5>MncyIVm}t!D1?NBrfY3T1_!x(uf48z z>!w@1`Saz{B2exML2{W!>x47+j*8# zLpE@gn}|ktKSSfhhKZf27s-E5uHyCk2YgMCCKejX?`jbp&f(%zVG@2iin|xSCW3t% zYE$#g?)PSEZ92nnsAcApv~pA!6g4!$wCik_Crh=btIV-Nuqa~A7CcA3>T-T_TU}kH zq@zQcD%bBBPU9ad)#mj&{7g_$Q^W0i7FDKMWrmE3`srk6>hMR^$ooeA-^0Yxc5lBl zMCvadng$6@z`uGJzBkQ=>F`^-L8_EwXKKq>F9VYZTAjz{`V>SFrBHz$Bjt|V2Lt!3`@wb5u_m%yRX?B@( z0V1oZvfze>hQcOG6?}YrjTVoZ!X}Y_CSW2}Lq^YQ$)>9>ceIW9)cI8K@L4EN$L^q4 zNqCf)r5=un1Hv$^xxrzjG$K+jO#7s$<+gGWxBOAVgfnaN0jz_gW2MuUlCo>wWY}k; z>DgIodU|BXjp5IkB7qQy=jji2=P4N;@{{ceu~1C1gNu!fl~4*FzjyD5%5)k6)&}0D zG+bq*iqw=2Z=`GgWD4_18N)Vfho^sRY;3HzAFADgm|WCxQ?>|VOhrk(M4C1o}Ypf zMGx~-LS3Ktep3Id>r25Oj~(G0@^JCZaGSB00%L?&D(bkNI~fsAKGXX=VB^i5P9jK@ z?y>!dl-~vY?rQOl&1qBN{OXFtsaZ6uJ;V1l{O)Gox87x!cyMrVv`CdPijZBD`OMMD ziR+F~Rc7D5ejNZDr@gNa%gt+e@_Y@Nmo7s;a6=;~q@t*X!&SLqxOuGBPNh#gK;k++4maVPs*!+?g!x>`S1F zfA26#! za|~{!C$=X7kFf&Wqxp2ks-m{GjANw9W%pB=Zj=4Vwkmy+)cNJ5(Nep=+j1}gSDBFS z={NKEw6tjBo|xtBiDHc!OT6stY|4bb(cBmJWZrQgfSbTWNFUonNO*0#CW_S!O-+OM z=9`Sy2j5lH)lE&Ci*0OwGtc;(#_w8T(ueD`IVxgIVYk>CU1K#x#l(cJsi|o(nhB%l zwu}%(F4)*@Ec4r>Mc=Er1EgppY%tC6)3TI}fEWOtRXzCKj>G;Q!7&&IhfCi1I zl1uBbgED`eUvw~$&@4e9ZUd?D4DaWXf*FIo1&f-A^tK_2(CYoi`nxXo^&oQ0EUcBUQIW;0D@_TcnMwQ6> zx9+$Qwo+}h<5Sn3+i4mZ@V2eXoAbk71e5#sa}r`1Bc#`~MxxWvgM)*UIZRQ_M$)4x ze9mDQ)k?6vtQ8e;7Th<;z}!CA#70Glo0!m;4JK!yvLV95x6jNF^YQW3dz=($u?exV zV!dV6S0mHb1m*;xC+3 zugRWRiXQ&`=D;sszGx9~LV8C=dXm|V?XOO4CTgRi(0_cZLPEkMZLeGOi6j^F`06I3 z2hc`|b{*!$#YI?5%(JO7UDYd8RMdmhS-X|hz690o_3xh}p%E6JMF$xPDU{&^|Ni}@ zDShA1*%FbIZwFLoM>2$Cc^AFzF{+n!*|pu|*A}(a*AoJJ5;ZcS0wGW{Z+Lq!i3I@x zVfv#fn$N`=L1Lo7Il}txY+a>62Q0)O^LL#`?)v^qsmemZj`P~lTJ`P}J|V$k_(VpkCw%APKtLwpe@JN45rl}!jF6w!-H(^`bAsc zIYyW;B?`@zLs5sJXY8E6KIvVki{5b2&GVIgDjr%AT8H3{6VJyQ)LjEa1D$1*RTYHq zfp+z#T&4vgCT89s+_+ z-{*o>E<>oaHexQ09nNpj_l^uilxLkG7?mC;mO;V6hKsG<$wJ;wwx`O=T`;)~?d)Rl znYADg79E8<(`j{m@8ASF`A^;5h#6i-C^4jb0niXIz}gm+kZ_MhuQ@DM_*TPe9*pH_ z3Xe^N?fer`VINX70+yhb7Qs~qzK8PZQG%YQnZ9?o=RZe;U!5Nq%A`KVdHRj@`Ea_R zcAk*fhEy2TKYVAL18H!fSbr2J=s0klmA*U(JYBEGuG3P1lf> zR&vk(EU{UXK2fTkUqJE^o6ApM!pdcR6a2(d4(H06Al&w}R8iG;u$qrJ(4>}(tP1O* z-;-lOz$Sk%KcS2kDHK)Qz0|cXrNoJ>aBx$fV1--QC-d`ATQ6$F8ZVg!za!WfxuXyOUWd)Tpf4I zXypRA!U^Z2)fdU{-YqAo>-j4cimRw3&fFeRx?5OS)C?OyR>oanI1tD?nGnUCXTR*Cpn#5)l$6c1|Ni;K1&m)eWkZ%Dt$2oz zcZJKYR<+d>=9@Qf4h|1Ho>O?kLnP(ppF22w9P57fPbuFk9%M$x?p`vjB69SkK=NVF z#j4D))p}NqjFu0MIAo3iKD9L0?1Q;!O!akk5fZ(B#5+oKp;lagRppULa@Sw7SXta` zT}Z?+CMDOOJbq?2gJP1`+`_^F8!vCqPzrC>hl9w{?WlL<<*bk;fHFGfn_MUQ zBR_ZF!~+XJfLOkiIo)5xf*_-#i-Y?Yk&XlCq1yQN=wlKFxj?;?{(yR^wzjr$cNCFo z%%jDO^mIMb46p}XXspeULX{hs&$92i6o*Rleg3T+9y$r89K}bStDbeFaX+AWesS;R z_?Da36G}p82HlISi&s_&csP6tK)MJDB|9UneG&1#<7mJnWjO(ihZh*Oe_OWNY1ZmcP`(*}}l)r&2+ z7ArLC$9S=WpK!UHYoDtbvg306wCCZ2yT{Mn8F9ea`{;F6lRg~4-(pgFhDnZ$aVR4l zGO@9B3m#}aF=U-HHCF6y2L|=^^&i%D%Rqj_gs}PEdJSd>>szJa1xA7x5|WZa0c~%X9QMOR%bkaWAdyR&n1;X$0Y>{#B8`UZkyR7C}w z__Jr<8#dC#z!tF?v_oN+*v_1ax{uE{{nyje(<)nz1#)TC3$THWZ;CAtB}F=Zii}Aj%W^O_JOjO9Jd2QP!+gbR`w}1ctrv zq5omT>yKz`=JH&6sFqRX88%30oa}q+_l-OUF}m_i;2>|v+oImi@N_rBbi{>8Jbs(% zzmJs>sw3tzb5}Mm5%!i7zhxSQ{l(V##im4H3{wA>;;8*4EY{~FbN#)YQ%ECJ+pck+YNE!zk9h|Q6_DG400uQ0&zFTJX^_xh2?z+954%Ya1v8a0 zj=r*#m6a6%;%A*(FtJL<{b^Hk2{yMoq&Uk_^mjT zcWrn4dk3aFGao3_Q6Rc zgq}g?jRXfDPij<8NW!6N|N7fRBUuwt0B2C1@Op3nyT$aqIUzXx@hxyH=XvKBCC6JY z;MS0KP;!N3iG`w;wFyX)laWQM>$xKUQwjjn+kOz9m6Zj}Pyn5O1*Yw0QNZKSKniBt z`K`+QL6X#1ldD~|-6FYc3QsVI)Bv6bg2>CzGnB?pU_P8GVq%i3<>Q#igfZ~7P&t~z z%&3wO)Ow#sv&3GV{?NPzWyN%pE9?4jT4Z)MRp9oQhs?|v>g8{|W2#?{=SyWcBb*PM zCA}?`ok(N+ZK_H*AB%Km*|8+n;H&acjixS^8VU)mmhy1fOY+R@tExmp{H`~|*O-`@ z57$%dM6(nOz}{-XWvpSTm0ZWkdOva6gv3kcdQ=zzAgonmnb#kxOO7t=eaQttsyzTl zAkDlS6tQ8VlS^#}lU!b14FbS&rD?yl6AT-NkTC4>WXDQZw%P5_D7T;>2jpgp>52!S zW?Ncb_6KXb0zsp$s(Q19==V{0 zTj7fF#l=N>@3Fi_M~Q#x6qQh-*S^>BVb6sJkz6B=n+tk0D12R+h$e}`H7_1J^ACNU z&+3u6yS;&q2S7Oj6ciNVf#cm-0*F1pM1g91Po6yaw75tCs1q?A9a7u*#=JLDpC=~p zfMq`hlV#Cugbx-qv@gOFODZ6GIaNkR%x#%l%gW130KQV}AR{6IQ){;vlP{ZMG?py^ zwE-$>>Po9Amce9pv6Vo+Voo#layCwILAdXHg2C zex~jjASaeqjzFm#r%h}~rhc2xvt*3<_Bkav(E~x z_SeLt=C3JQ`J6BTv@Vp7CKm$;IY5%;695U^dv^1U+?S`jXn9KEz&{OFx})n|_sIc3 zR8mr^@V;_^mZ8KHZqt><&mrw#4v<6y0Y+2|3<*n127rl?GF_BQA%0a=oDk3igauI< zMEYlHYWPsFuNVm!5B{QG`NTDPT2M6xYjgg+~i=Pvxv$L~caY$6Z z*n=7wW@oCrCtc9fz}B{`|G6Fu7uWr^n^O`nX#@Zl06^`2)e`SKZM zwdHy(9u^ZtNeqHAd?3sYzUM~M_ddaU7EPR2E6;@A37|Z{+E6|(-E*(OGWuO~G{)ue zRv?H7%OJG0g9XS`Q)5W_KJU7S=amlX)^-4Gxb}7y+k`>5fUB(E-nSIKSiP5?Y>2@B{Psm8k9QKRDJMzX|M%}k&_L5K zwK+Pf5GGF5yXH`BIZ-r8f&9|O2FfKE9e)2D77-x|OatT%XtaZJE`Yi!#C20;NCH_F z6$02SL`i9Bv6n9&`F%{90c099#mmT`L#y6?#&SXdXxIarBM$tv5?D>DfIBV#+knV; zZg0=l9ZL}lY68`rR2v(c^+zQ`2AAqjlAkgV5yT1MH36d09g(_T0oR-9ILX#3ewQp$vc+g*QO{d-uU^!`H zB1cC@uk+>5&98+Cg-!noG;(*d(qcb}2~4m_%84HnT1=J@L8BbhSOGTy<}YGv%VKS9 zy|lI#ESL5KuvL0~DJdz1O;W#y@0?Ogc}`70%GyK8hcjmUX=n%oP!zR)I8*Us@xoms zSB{aty%n4Z@zU;hR@9Qi|7p13Od#g;WLSCrr~iO6MqSJkxGG9@spI@t7NS!3j_Zw% zwuplw_u1dBhvNLnsPTYIo#?--DlYDfAGrGWm5Mn#nBkwt|JH}^-ZjGE0n!a{-+v_% z*-uvEQD2*zn!fwnKNJ^N;iQ5{DZ7f&=WGA%MrS{X6mj_1DygW7s>lnS7{!lQ`#^WV znc(*phV^ohM^-bUzrLqf>Ht#l-w(p7jT-U(`*p(qH0S;wzy1HwhvZ3=4ZUB<{xkp3 z?f5V>VTS+CEITyjzb}ib{x=3QF!;}}7qR5w_71QWw-zGy0b(EMK5bSe zz18>oqQtk&|4oc)ouc<26Nq0vZ+iij$io^BKM9bsAuzw!yQua_&Fi_LVblbE-skrS zJSD}cqD`fw*3Q?aX;Y<#+jn%eGF7s=jw%=x=z()XwMDAg3iO1O|1=G=i3^oQj{4G4 zr~#$*^R5Nb@FuekkfDV-FrLzmT-9|UbqUO_*p|smsA1u;dXI}U;H>0jc5kk(plYK% zvNc^dd_IVwQ=gz&+IUj){Lf8QX17)1bf|E*(0jFB3>_>e+t>>V5(uX7bXAuzSDsIs zuHNrMw~(k4d)23Lj|izy932bEN@jB!eTDzrxbJXaqRjC$ef-&$HdY%V@@YwI#%)XH ztGj`Y)`McZSogbB5f?+00D1UcL zDa(OEF!kx_k;BpR1}`7_C)6OInmXjJ;t{t9zSHqfA##WEgjssS`9j_hztUipjawd! zG;E)r)Ln_!3 zmVzM^PRFnOTk`m_?~PaR95LwG?nI``5{uHn@ zKUxYD=@V8~T1Aq-& z9VXGk0vLT{=@94cuF{_91-*+ve&H6zLm{XveX{Z@eqyC z{lvuY$Gcnt6JN=oT}NdC&-xk7Wi>N$2xVLDeaAD2m4?aagP%XiUOi+dOOfj#IT0!O zJ8m}+CLJ1-P{K8GYnhqb2ye?UEmkcep<@Tz>w>SOk^Dkte*Wr)F#y>wrIdDW{S89@ zU+$7&ZTx2>r8)z<=`70q-mmJNybjgD$WO&Qt%e#Vll&y%OPTU;K`bpgt96Szng1qy+`RtYs8Qn4vryL!G{&csh4X=>VQEYVbW0~mKwdQ})6j8n$Lf?wg zNA}EMx>G&5pzGUC4)q{iMMmY3eo}efGjT7=n8GHsOr~CvwB(pWvYSdOCVY+t*qm8j z5hWL)VOmp_N51@w6y^zE4n3yur|lWY-v!fJY+J{cd@`ajSziXje&}Kug14+5r%WRT zM*lb|E(EanfS;wFE*7$8&Q+jKwCu12&hK?7gX|OA5Wd2@m!*>RbmSoh2ztw3=4DLq z-dNc1K5BYuJl{BT&+;jndQDa6H- zhV(0t4GjTT&B!Rtb$Zy5X^xbw+hmY%Djh_8%ac%~z>uUse?Lyra^DZs`{U%sroJye?QPUfW~#H~c$c+Gimv-~rT zky|*(;qY*P5dSM7#TiCGO#?jdm!B%`BLoypLCZGYznHTZv|(~G&(DYML)v|(_NYJ? zDpK!9P;#!*wbyY+#Exe}u*UHst-yFcT4w;U7z}M$!P@toz>PRnjqEyIS?3)~`47F? zg>J>3jLD1o7y6WppMzIrpz*M)#LLO{N5lvcVy6t#Giys8wdY+#bBH)&onPukl#{t$ z2{<@7VB?dAU!dU|4sPOXPSw@DxcoU^r=RR0nJ)P}hV*mcxcR|C6Q@<4&oId^!-zKo z`B?yT+4O(JvTI(U#p-_1HUqVPku_sn5|rvHiEJ!7B*cM81iQJ%%fw*NtyC_XcqM(j zpv?mbyMUe5!?qGV{Gi>kdFP~`*e_N2Y8>BbXNE{kO)Y;9pXi0H zUV8U#_WO0pf6{HXB@`LaMXOMSs4#`3q~URJtm0j_>HP8qSFq9b^FU{)C{?_{Y3j&mo3B2p zpa>C%m0SZ=1f)iew>MYzDDnbuBI2gr?%+2o4Vp4FPg4th(9>(wG*_WoFr8T~dT{Qe; zwNhcagQ@C`Qg?4G&ylV~rxlsP`+z0p`ig=oM{euK`0(AXXvRWIg~u9)(~oKL;;9ne z6E>pYvdZ0WRs7l6av~2CVCV-Pk(1+u1DOFV1fNycAN16ilJCe`Z!S4?T8LxekT8q4 z7aDhYn-8uHN;GTGtAsBgaEQlZ7ERbcrfU`PD3ScUudp(e3otcW99||>PE3UeHLefE zH4?CD`lD^Ie!n?e=>ACCmMIarUQtmo${(_Z-XQdkG>se|v5&&ola{b@7{?k2r~eE?c-X}88&g}9Tp0@ z{b$kW@9zk8=??^0%G zKM$B>3LgQag@x#YxlHr15Ag((cGD)?$~-9M=H@NNv&GL{n+12^?&ITAactA9dtbiz zR(Y}`2Hbk6C=ZBq@V}-bQ_*MLSE+5Z`TA`BWQSj*P=$)`0uxYOU|9dG-*de2Qqb5F zGy-A1!hA&deE00maBf-=y1Ue@@*t>nN2prGy?-C_WCXDeVjZ^O(lJGRkkXVMNH z;>K$k1bS>tx~-@$Ui3j@+upqAt9`wQvGRY7A)FCfRg>c&VUlZ3{Jugi~sFns?ESOuKHsy&%Io7HKXZ70{6KQzm>|Fay-ILzfi z2OxuUme^^2?TN?z@VWVU#c*7U{)lCA`Fn`}K(hIbw;KO)gE8ZQ{x76_d>816{}k1Vz>6B;ps0Dm)XF%z`5HFYeD!I^)88Uq zRk&Xi*zq)OID}u@YWka2N+aVx82dQ+|AbUJl1eRMJ{XU~1Pvw*XZ3#u34Q+X209=t zWkE*AsF#cPZ&@(0BmHuw_Kd8KK`r%031#j_$${eH)4^F+L?VRG`FlM7Yyp#u#@=)k!)FeizRDbFk}LIMIYCz1C0%8Q+Gb1yJ88&}!& z?>U7qOOZ6x*LBtkSa!Qn;%0{IA=QW7k^Vy1k>Xkdyw!ExQJ`SFxFyUv$!d+J4MDh| z9Gyo|Q{tpCCoq@_h8LfcVm~?fE2p2}b7qjVeTbg}0J8STB>p|(J)niPi`tn_mGFZ)MTqzuqqvkpMxMN{t(K8Hz3eg@K+ z`~6ai0QHx{U%^12HR+ws3l6QO9I=!o7c(Kyr7WyX1N>COD!MWj%AYk-OjDr?TrI0r zgZKgI6%cs-1o=(nig-qQCCNbeM@wp0+J-H0sjPG%yp7<_N)I)G(;FzC@FP}Mly?!) zVtL)~?Dk3coDmUdB}-UPno%KU7fu;L4}$7=hKPYv0`Ndz+Oj3)*%75531DD+EO?vucP(l^(2&B{0j^I z>cl@xPV`Z_$CuoPRP2Is8o~$3T_Z4bpT;9?qOj+|SJ!3?lb{0fh8<>prVs3ILUlVv zo;xa1sT-KZPF7U1b(&upDNwyNmbuDLK2aeVoF`#^EFryUwx>ac6AFsKoSaz3d}o{R zS$un&Q<4s|3gil|8fJ1O)(U9_}9IWRZ{rO$yAyDYVD z6kzKUep1@&QZj<~QFN@Ci{~>2KN)?k;&|@!;?eqEWJp~OhL2Axb-v3>z)uz`j{NnE zO-0p$FQvUnQj%R%CiwROH+0CB5cYsIN&OKWPBdAJX}Sh4uC(N>Q@5L4qY{0S4tRkNYQLY^MT`Z+rhb`j%Osa^HbU zZeA|1K^)yN{L;uHTqCF;#gj^iyJvIP?g?3}JQ6(A3Z}gec^o37M!UX|1HZLE>!UMU zqXuG#75+rCr~to1dHo;0xh%n#@;9r>tx^!M zI?!pvUg*eqePl!@DgJ&+dmu@wt6<##Sa(icMxw4CpJ(==l7g|Pt(O6Gi)Oh>c%TJzSt^q$N-;PNv z&414t{U5h8vac3A@9tU8e3U)Wdzi4?lYwurx_PKk=^&eQlb@KB6usY?VfVUr<$IaY z{AF}LIqWY40iZP-r!x|;${^6_C9k!fkEz;VdHOIR%iYm8iiDpyoqTViNWM@7=qH|1 z#ec9N5Xto*BPS29FFw?0J}{T0G4eXS;I+K664kyEy*b>_M@F^I@euaypb82G8H)xN zB{ek_dxnLO6^jCqk#T^*lwqdPT@bv#&Z-f#xk*@}RSA!ezg~1hMD#BC=rVJ2~3CpFY*vw;E|Wbl)urNgCV|y(;pnTfWT7-E_f5!)mUdV- zvOr(F-ffE`hFmZthD`JUzSS*tM?iqrb&V&Mo|j=*rVnb%#<8xv<%Z|VLm$oR`&5<4 z<>(Y4fLG|STIqp6-mvHg1E@ckdKj|A&Naygv}II`{77YGERTJcsRCf(;Ll_&^nV4w z&9QFK2oyi=y#l!Z{ED|@nw@%ia}ycp3eZWuuOV71XXt_#{z~IzQT@P^>}N06p^<2S28~^_HCad2G*y zz8BC*)Gel{V7!eht-9JqkAAK?{q*Ou>)GDJj2iro{| zZ!uP9Naz?GtPm#td2%B1X?M1Fh?ze&H`fHHiVM}o-~K#4cxtKjvlSeEp z9l>aCvAx%sU>S3V#_}pEkPC7~mfo3PlFX&Iq>(*w{^3_T>w;|gyjM`2jFy9li6Pgk zkU1n(%uSqTrWQlg-d0(S+5ZmwSg3E z-4_X>aWH@j&U_F8IXnWydj6CoUp6gBbAQkeoP3%rTlD*y$W^)}r$;1&E+HY&D>>56 zZ#ObJxBeOrs{L8;-M4wn_heaI<1rHx69n>`(rG?f=bCFx3zHYVLM_#*5%0mwN*37S zPS#FVf2qI7mnhxpNI=FPWJwlzq(a`ddkxp(aiTEmy0m0S`q)k5?dkn0q`vWzK^qwq zC2wZ(9f+1XNRt-PKw*qn$HZ!p~Da#RQKiDy;m6YZT!LN^eF15es) zKh5z`h%kM*MhX_)4js2YM3%@+{Ri-`dr9@p$7-_)z;rg z0gVgsTUw$F3Vx1?`I?jN7d9A6@Q3#6J#rfw8=unPJ^)mQj;=1(8*tL)U$Ul_*LwQt z$;_aP(N~|m+19L5TL?6oGLS(K8P=}adD}IQhYS8w|&i$ zzrX*7(%CkS1Ckl?my?r|7UTJNx8HoRf!fdVDzX0~WvS!MWt=39MwR8GlG0#=7;$V7 z6c}mKnh*MmD1i3u>vLVwd~_2GsI5?n5&t?@Y60F&O)ksT;1d#_E$hWN$BN1t5fSDM z*!`KBdvD4$0#VANC$#b@+n%mOoc74>7kFf!DGORTr|{6y(n9(j5_gPEO~c#$c&6+2 zAv1^wYCAeZLqqIG6U)<$&Y_bH_Z&uuij;dWAKNdn`OH_Ka4w{(m9~&=UInJ1&&s4u8?7wd3UA(6u`o4wxh{5&vZvb3oT& z-@osdH=zFD(IcXU26rHfWZj#W7Ex5h*_&&KEbusa3be7_$(OCqmxpKhgZR`nhSyOuD@e3+wX+>zGplH+@;Q<~sFf_C&bLCkq zxj$p&D~%QxL2&ZwK|-HB=pXLHeY2@0k>Q&V@#w=*9RyT-^}N=8Z$q)f(ERz%B&cw@V~E0I%}2(yp7jbAl$R&Dd(;~*`4v=J zo;t8LkEDr3p6(RO)lV+ zlg-li@7CLfzhz5B?CVhJln44m@rK{oLfXsMwKq2@0M`^2hJq>;`sk&vfBpNk9Q5Tj z(=XW(^k4A!FkDo%r3MP`v5r{;xk>!`x7tBDhpq}L47|>D|6Brn-h5ExDB=JLSoMIL z(Y6LU@oc2o4BI__`CFlASk1!1CJh^C#id*~4i)!;W8?o!0z8iyXMql}1Y}`z1{a)@ zqg1Cq{Mklee`t%J{&Fjr0M2`~_+_US2All;Brch^4h#e++-=QPzwb+jgLWt9$>`>; zE1&|P^ilWW&|WDxDX`Skxsj#7AUiMdyV9Yeg7)tC`kF4epL*I6K%P3t!2r1m^c-LM zfT{m0bL|1G{m!g>X5p0 zW7@XcSc@{(@>nkLWu1ck|J%x-*yWPvXM3hvw1M3t32P}HsNUtpHv}wia$rohDQyNg**V4)mY;DoG?I&J z%}q><`giP%+WmY8dCd}wN}_AHAIg%JpLlC%h$r#X+Umtp_+BDl5Z4; zbRQQ=kJBG|d6@sR?_RS44c^TE7iVu7mgSoD58nz1k^)MHpn?JlNQrc#pma!wba#h> zAkqrbAR^MGbT4gYoTy=V5!JTo8O*W>Vu2P)jxd98D;UoG{L+0`(Z)B5XL6FLJeOULn!`r{*wGeo|`x=)5-& zH9fbK!RcS#Y!3Zd5AY}0qz@%?LZ>_r2!SPIb?j(mu**vNf3aF)c;!$ z2SLH|iHy9XocAKsUsgoVqK~pXg_t?(erzN-Y#e2Lk3}=j23Z z+k`|9w3KJNWJH?9jJZ7yLaSFY%g=cnF9W^Yw4I8jv}Sy);o&xjzIdi6!^@Um)3X4W z9aS}`7J2z!^{8?8)C;vR9| z6q!Nd=$}%AHhH#l+wg)i7z|~mORC45b}a%b?^#?)~|X2No_u{Xqqbn?wQm7f%U<$;)bw&oLeGy}_mX$nz|*xg4Y;&S@%Upr3p+ zrlvTOmors9YExX$2uiT6b}wR92MLmT%W)s+&fWW>qA$q+7EF7dq&(($DoVn1A4jHT z5X2F~1r{lzQOOs&%z64f%mT?!(xc%O*r$H zHKs>K{dkESydhnKJ+Wbf=b|x~yo6;ZhpYecZ$!S~nk)O(4qKZlAGYRWC;1Ah5=$+J z4sxp8-p*&3Ezvz=sN^xblH1s8&OuhlK$x?wb$BY zN=e?_nQDzA+GI2*8g!<)`)!;4OBGf%wOM(UUtE)b5X9q;MM0{t&*pfuwJR>)(xMCj z^REzCRRwCmNqolVy0_@3#7LB)KGb>mE6XZ8$?EWR_<-4Ih8h@*8 zQ+K%31Pc?Z-BbCzT$k`Za$}&StfO zYR8Ey&RpB|Ujk-le*FTZ=N)_i9nN-SV6c9mS&hxZqrSVeb3HpdyZd6P8XB(JEkfNZ zMGCUA-h{gNB;3CY`k^HXp&UyQ+cA9Px*IK|pJ{fUX-AEDXLmO@o*q{$liZYWjex(f>=dSa#vv z3w_37&leiRr<4*#C`^6FH#Dqvv)D%p^HpM@ML-?njueW*EPbA`(`k zuH-LYM7yIQGvc!rA9`avU*0G+C8bZ;^e2W(QNCjVs-?#FzDg&`&UIbv?Cca6uH{AZ z+VDo)IG-q-ElG$zSt8B^cZ3`gMSK5YBfa=p>_bYVd_cN`JBU)?$qlAz>lp&AxWn@0 z(LTVju1Q7ZG`9BS>9(z~koqvjG&1Ab zXl{z_zN6z}e*sFd~l{AR-XkM!p4ecIL=u zOBt>mQK2XI9;Cf&_&G=bfj0`p34*VrFGN^j^5Ug5w?48|j`RS+>Dndh??sIha1s-#9=WXx)uvmF-8`+{k%4rs21r^!z90pmc%6;$NC_M80V!|^ zLlG8Xh@@pNq#VrF42`jR)-vi)qqiE34Sp;aig*&*^AJ3@EeTc$~Wt~nH*IVn%7MYd0C*x^o$#Ij@EPHO8K z9$Dv`H)VkoW~U2JJgPH6@x(#X?MCFskH7q>z5M(oL@G~vW$R{3tS&B9@;eaFRdV7Y zH89H@D?JZxt6ELmh}53ax^a0Z@`%gg^_Sl~Ky!$K91wn1-jH)A>}RK^-8D6vEBB72 zvCS>k`j;K^ze_xSo;!`a^MyA=puR&}FdNvlP~YB%f*($wbxQE*@z67a8yjZH3-m81 z-~8E`ZgYpbFNDYm+~TpvXc}CrnK?-C`ek~`(cx}Z-dV;9ZGf==z^tf|A0pd!Ck0~X zsSuMfM|6)J&8@msvE<4qVOZYo&xrh8e!cbS*50e{;M%|nP(?MfV>1t__8W2gY<5S` z5@wr=`9x>_T5|H|%_(0LLx7D3RYAy5MX@(1Lpy%nHXf+OrvhGk79{dW8T;V8p>vsq z{$K1X0xUSbsfA1zYI@52Fp9EPrwGJbFwN+ggj`KH1=mKX-4oJ-c1G*p%aoO5tNyex zm2J#;g=N5m&VsbW-dF+AJ!WOGhLm9RBtM%rHA6c(i<}Fs+lhu@{A#K!Lr;}DF$Is- z{=zc=O<8@$m6nMDVSf=%xG5qF?A~nxp^UpLA98cA-b0B@EQe$Z~ms_UU|e*BsP|~JV0onRw4{8M>zA-8CrZgCME$8mR4U@O$f6TO{qol z849Az&nqx;`Gtj=ONaNj%5pfk!hdSY9zJ}CuX=An5~zVnUcRltxW3J?P62@9QbwH~ zA7exPYr*OgP)Vf(Sr`PX$>ms9S#J^eVUzksN81@}bZLubeY{T-`SEOEe7NTpJ~?qH z8dg*-s0Am=9edm!IIWM1_5bLLW?$l9Hy=hodIDMBokc<_Ql&YX{92zC$KBxo)k0^| za>u2h@VsiK?B|z;!iAH4Q}p?GxeiIQFB}LFf=rpmQ{meb4eFdZ4WR<=!RlOu`R+~2 z-&}_5)*=-cB2_D2a${m*Z5{VN0d)6ui&gh`6#Q{c3?MQWM^nEul%{2^x3%T1yc;^m zy48}w4~aJA0P7MNYQfcZEqggYR->M#T@)TZ}2ur#&K#3(XKQNpW4wDBn zHffN>m{)`M`I(ME>V*rrpQk70S@xjH^VTy3m-Y4I;`^@~FqzBuWNS!$o z>F8CFQRjwl^~*&d#ehVI&^OGE!V1Lb?jD;=+wTkz-U=lOtRRtL&;NcI!NliD`eOtQ zDQKU<#eJiZ+<4&Ml|?0siSYmRzGrj-zon2jUm3IpFKH)D8g7+fnkx6IDn=@;vgLIjI$AE17zJ7nz37Wkp3F);{1gAd7`3>%jOAyb&49< zuB|B`Y!x1RGhLf}3+&*{Wjn;y2U_n;4e|?)(-`d1Ok;lk<^gORph;%q4g^33MBpxX zTjNM+qz8S#CmbfG{yrRM7Z<(exICn+Z*#{i2oQ;^{k3y|iO0*bFbA{MYw)2jY0RIw zB?qW)vCykWUgxv~AKuSQPkV<=-)z(jmVl<9Q@hmr(T(=%&!IJ#5}iqfcKg3u(gvk?i_-m49E4(anJ^6*6$> zZ*K24{(h1-Go5@(^4YTiw>72stleGgmqtcem&b)$^7J-~c)jVeHizEFF6Vaq9Jm2~ z{W9J`P+51%~#Rpz(tIVaFZ#B znUE=X?9lJ>7Zw!MMQOXgiz^{uu{f4y(x}Ad=En2<@ny-bCv4@b#@vb8)Dfxe)Zz)L zY+nJ(OD(fmwuiUBZ9JVl&fX^?uXxKzF7<>Tx^RTZXM4QbA2N8~3_@pBRhTBzHE6@yX%B(_SCD z@P8T$zCU{-96-obi2j3Pk2@gRj1R z`-U0IOi_V&-dKBkN*lwE-CcVS9@JHYM|J8S{4LXkBJdZRlHcaF4Ut{&z63Rk}Q4`w8bBKYF@6cmDVc`A6=E!q_a({XwH3Ruxs(9ot!rY>r0F3$a-3qb-w znL0sX#cw`dy=oFD;vb^uQv18k$G@jQ#4Ul_fiZ*>0g44*-$F6_UDC!DZHct!ln~xi z8B?=NOr$R!T#HWqv#w9(0D|$TI}*;q)kbO#RvVW3j985J{Sj^Qxu-`9+SU#ZWz>S- zysh-9HOk%5qVgK`e!o>wY?-W#^oChofAf=3zQGu7q2+}9M#EzKdY2ri2gr;iRc?yD zu;v2kvDUz2Ekc?hIkEZqd6J`LYx9Znlr|ALj(d2b#Kch^Lk??^-;0Zz{i!Phr<~4T zajbCa92edbCl#=8P%->B<_9xV1q3XsBX-U^!9$lYY7#4}s%WCx!>xJngV*!StH`KB zmG7%zkSWk0kJv68>7b>&6wvzy5;3>54XkW-c5nsTPDEt6Yp?JKh%9Ihbs2}FU=#rdyr7ePNfFVCg!`cg{%8Q-#H`|M3*>rv%7%6m(vINc`WPtBQ>3a>I*YD-s z%LJ}WQ9L#(M3fbftQ?FGgkyjJE68H%I(~_K*b7+=#xoDqHPB9yN|oelev)m)&!nLg zd*-k)c*AL9vLR&3krq&nj0_XiVt0rro);i@p-fdP<8thf z)Tp$-3ILgiqa*ISckiqhH*2EV4(tX{?(9Wg4SnmIl$6IHh=zazhJj%mytyd}Jdj=; zb0Di>f0oj5$;n1l^kaj|@MeMt?1_RhI_;=O02!|0 zf`TjGEM8gUJ8gJ^BOs$0`Uah)Z;{Afb0K%ab>+a?>TNSWziNr+EznWG z3<#gXxo$T{bj*u=t1)OXN$ukw9OvG82I-Wj(9<274HXTlC#o5Yyi+pnZN#IZT1V_^ zyCJ$yG7|H@eQWF{_MbjI3WRy7`R;65j^(qzSuu|ni3TyN=RQtYAmxYP$b6G_P9$n} z>lR_Va<-pagCFu`M9d6*0ED&NVJ*mEh4UsOBRPz!Zvz7*La8QDgEPX82yLmgD(xWz z)uOh_?H&|&pfBZ8v00lPQdX)59X$OyE2@(&aZq9%aQ0_?+MN&(#-Ln* zaaL!4ybI}Z0Qvjr!ITZ^Vrw8k_A+$2w}(E1V|M2U>DP7foTb$jKby5Uq|weZ_+6gM z8#+sWU|&?1hn(umIT%fI)~4Z-BFh8h!vAWEL6Hw3#?=y+){DTT+BOez4HIW4zgqe!3# zfB*g+ke0ej+e)x=Lp8`k-BS#z#pbLGmSUD$PEHQ~wQjP$ug_ltOD}hfj+hK4n*lOj zcWo5s@XS*u%OG$*`e2DZ^HWAQg#OjmBA8T5QLC$iIliW(uYy6n9=;7^W0Sy<&70Q> zE|F((hcl+`ys?~HCkGlypsZILa>+XaZQ$OIe!73)kCeY>3~pT)D} zw(*13qCj8#*X+nn<|^YxjVSFan9RT^qC4D8tT^ObZI(w1yj&33(L*F7eI($?Xng9M zso`XhNCioF?JmRLEYJ)Ev^TycHve1$o{yI^d+uMy`2szxG$p$n|TZc(d^`I7zsXWsSk7E*oPy<@%NF6Jkjid#?wb!EAe$pIW!o?lO5C>UaF4F z#Wr5g2?)*lv*YOGCq%`Jd{* z*dYBHpl*Oq>`kasikcbMqemDUovv6CAyf_hCRTc~N$buwm|M9Cw-}nD_6vTg|W&GC=iC6rm#<-joBZkYC*Z35XZd*WCEtV~1 zDL+I+1Oe1W%gCs{$$FeS(01Zt0d5I7ZZ>e%0Xy5;Uwz`qPsVQDuqc<8Y2gLr3(4uK z;O^>(=By2pWvblI2no^f@{VtvhJmLg!{^iSoGtg~u11}P=KIDypSiz2Pt@5YSte&g{7+%( z>tmC&T6BH@=RSNoPQ8lV2rv;B&s>G}D0P~3+WA z!j8H+-|>7c$&TVtEMQoNOABxAGTm*IpsvO$P&JPzBiJ6H{hi42g?Zr2Wi#LN3Iuqe z{9h&!EOB%V^EqSqL`1K~$9dWJx0*L+MDV-67HtNC@WeFe58u^W)Z^o~etC(AIUY}j8PEOn2QR>IC#2{9?zH6wYz8ERFl?k3F zyyl66MJk0rf(;ZDs|D^$!j(JwNDb+Y5(0wEfs$=jZ8#>7YqT1La)L+kFkR++L*V{_k+Z1W_S$~VfyYEIJ~1wx$-9zjzK9A68A1(K<=l8$?g zf{r)4!FPgsAh&2V++f(c;Ph|z zY?fWam)rX@wrKa7WU4%zqB;EVhm9=_*~9D0zw{0_T5P?=G?;YX6t)*R&4bVh=^|z)};5_yYQx zGuXT|gcCQjF@>t%666c@3zVcR*3*3?+wsSyBARFHzF{^mYINBEB+yw56w%SwM}bEj z1Rq~4*G@}wu)3l{F!;G~skz@pL@36G@cVRhNQ1JuaB!vWZ!z)fdD$pBPfw((3_xZC z9C;?tTfTb#UQa3P4DLcezg}f!1umC@27NYf!YwQ&CiT0g2czU0tp?$ihc%xNNB}7^ z@&?(-;Z2ZXf!SMe&E*OL0^)bQ%9)Y+SaL24Azj_%X?`?x^tyooG;}=5D~Na=uS+jR zUM%)iM168Zw*DI9O26mkGTJnu0|r4Ev^;G8I2-jFW&Qn%r^P#7;Mk5IW%y+aUjPEJ zPYzihB_&I9iW1)?NqKK}v4|*tRY`L<@N@SZvd8s}z<(?w2V)~7AkLm07=mF~T%R=A zE;XRKi-_jHx4XK3cR|SF0=wG&=pppWJ!xGE(B}(Zq@6$_FiML|M3Kw>s*C+v?^TeG zLw>CU$aS6F=6A`o-Hz$jN8q|@+7CzS~#);(Q(jZ&>5~75_vSC~UQ$_|)X@u_Cne|#T4op)sFhcY0 zNPAVC^(kL8$f|-x0bu&>8ttN!vVZVPw*Q4+y3YRKZz{`g|DROW?{i*G@fO^*0*jW7 z!?lu&nYoz;_A6Hz8Nz?)*wh5&a-({emS!F9pCqxku8IaFv7kXUQ^{esx3hZ#9%jq& zN;E_%z@M?4Ca>YBlsadaa~$oFa}6kmqj>+Vyk1;2vXNsYVtx@4o}vv+5}Wn1ec`Al z%}_p#KDR@gsIP=L_-ILhkb@{Zf9UgtcCwz;cvi_NDh35cJs|H-`@dAtV*J#*HORpH z4<7C&h*Im$SiaP7$Q5kN`Fe6@YZT!EF?@}rTYUEXU-__2p_ux5VJ6LD0wnu&FR!b% z`V5_6mGGSO>xNnm(;bc*NI8ubB{!}@+D}1e(J!V`tK3Pt7q3JPP0odbDEO*Jhdlqm z+p+wUw5cExt{%piDVc`Z|7TQ(X!fd?{KVH7AAPa@5_7&JB($^RxBR}i=?_=OGw^zBY zI+K(;th^?Ok9m~?FgmdAA_C2QfAtJKVSPbiki%c_g-({#!gJL! zcYOZiX-%YZM|4vA4Q>Qlg5~60toPdG&ZOt(&$=tiY8bp7!8~$!l!5*Oh|o&*RWV-) z(V6)(L(WCAM(J{8p~lfJ3uw4G{E$LHWPDP}YF zEMn#2@`tGm#H={W3jVmsVNX*JJru9_I{){F5q6-Io zJfuJ8W&vvEQV$;dW5rcBmkVEaPSADJt}iRWVyAa)Cl76Ro!$4Fh8D*T?%6MmKPmF+ z`DK9o@+q&AJJl!ic9K88WO@h-G$77~ObsA=pb2oXy5&eI7V|}{LzgN{9+uiM``rVNavO5QJPj0}S0DyDOG&jp2@7&xRB*JLU#e4SrHrTrhRb1sR;a!}&4l0r}nj z{Vp9as)R+jqwCIu-@SLQ4g|I~yEXc)?d|A@AZjkm2c4auFFQdN4Eu9EUudZC{_vV9 zE-ak}8uEFqYjD>%wuGz=>_=Uod;L4rk*zHLr$YacW96z$-Ywdc?o>O8PxAb|e@o?I z8ppuJ6;^eA`G#P5E>c$57kxl6dwn2$R#E8t_C?rA-qa)gbc>D%OoK>I)6ZE+p4Gv+ zW@agm)3At{b-`coHh^1Y|NK;}&~$`AK0|S{6ydF*#j#+M;ddhz)V%qHg@TYd+I*vG zZm0pQD_duw0{S`^5F5HkT4je3k#l{0G?fr;+wQ;32btfVow`G_a2cj@8^#&VSWb)A z5G_01d$v>RvO{!wN|rT{CwmteUD1W(d1b|G0u^x$T6$vLe`96X;KU7d$}Dm)!Qq5J z&c4$(pgm)DKkcc2n8}7(Bxhn^(tWxNF?jv*q!=;UfF=<8VK*1A)#%oqoi8dBBtT*; z&HU;MaMj$VpAr%mr@M@Tf=xy)(r(gru`!qGF_&Lljdf&xmx-yde2<*v*FY4&`JalI z|D8uNMHqC^zJ2UM_JOX-570p5GLIf}wHGzjRruFQ5R8nByb#+BaTdZa z`+Xu*QP6iq0DvRYKGBy56*br3C1TRJ3}QX|mCTE}(#Sjo{SJlyL#pX_Yaq#4W#P-f z+o3>2KBN`giC^KFIo>0WJWjp%F4oGIoW*?H$qfM-a5u7;)ITaME35A*p!)he<}CpC zpdpN}vs0Z&s)LyS=>6l{FlOBwi&H#OaN28{B*71mdF zRo8!RYTwm@fTWiih8%gXk?PtsH>GjJ{AmWjZWdVB}o-##LduQh>A0NJP4NV%N!0?Fp z>^6xi7yXe25fBzVNI!pvcPl4{)!xD36%-NMZOWp`5x*cGQCgv~PlE9RUic8hZEbBW zC~18SARuHu42b+M2?-epk?xccoNp7rXJ}`Mjt~tmtVN8L=Kb=!AnCdk8)x)LhM) zSIxm5HzS|UER2x=MFOG#33++jNPEV)EC1@}$T}xZ*87ypi@5(GGBcID#|a0EvK?qo z3wVk?2lyVy=U09Fl|}Vt8hL*|hugvY@1-deMgP*d=esR{@y= z?fIw!QkJD7NDfL{a&}Gt_O9%elrtP?wi-3e-yfS<{`Hwv#!c*Ra!9u~f@`yW$4AL! zrzqz-@iFNYj0$B38~e;Oj!;L3JP!oOzW@`$*xwL%J3{THvq?c>mIzCySZ99@>6(~a zt?}B|Zy#KXe(3Ugy3E$bMhHX?6p!ptQzWWi50_?@x-p{xyiB&zy*KiCCGpxQvF1Yj z`&dlav~YmwmGIKS0*9Wyg#k}A_h(Wa0xA@J(Tz>`k+W>^kkVOXNRjqb8p}c-b9a@x z3M#51a|s_{?;(H8mFb;3+uMTB&K6lLEiJ*P4BE0h|H05)O&;B&cN~z2w2ax%-_MSe zg|vS>{j7#xzBR;Q;G)K?^}3{l3u2=Q7{17tl2HSvrVW?}lFZv*`n6k1r5Q$zZSh(bgZnWQ86$v8c|P>%Xuxoz`V2B)!~=1optf&5>QjoA+h5=mD;vyT=bAO0)^J!_`JXe-!s>Ui?_?4&GlH72i*ZXA%Rlq#o zox{<_=4$*h&n3agS_9A&WwIjcBOTq+>74}5@>~x@sSD#JWU9}!P?t`7F{SAe|L)IM zVGrRNj^o-ijA}xI&a8+$bkdSh?Sr z!AAw*0_k1?(Ww8zy&xlzK(DG=Vu=FpA3jVNrKQfWjR6M%;ocY;0l^K+MJvDu;KB|Q z=&_-`U4zwkR%du-*e&2nfj`m(G#nX+B`1du&w|))-V+z}-04ZLE?HRfuwG4jE|#S{ zgLb934h*0WP?I`H?J;{90Y>U~z&7Zege1TYCJ9SRDo*p!*W_GQ0CF!}LNqls4Oh=P zRr0#u1qoy0zG&k)w&!`4_U$LBA@!Mo5LTRlK_4^K;8-vdMpISQYErTTd1)} zYEmNP-2dZ^%Vs%hg|#!cB#LrrnTJxP=wTFZ)UMEC9Ln!5Fz>14wzah@0GBFqVIDBz z!v|fL4mtN(ojn=be*CCQz({~E4HNi9+S{rg9Od1f zBo#Hs$GIDc3U{xo7Uw?DqmOl4d~O!3vRyhy(4i_4b%PT0r3Z4K1M&*VoT@i>D$AmEbhKQl5M)+7Dye}`^e{H-rjk?}!w zLo|@TOZ`W{l3apHO*J9^`68eBWF-y=tg)n|unH{)Fu}#p>$VDEMK)kcEwT_e-wghw z6aLi`X_cC--h6x4K7Ch^4K@mGt~CEd=Bokd`UX6KGhh>b4O^8a6S+0_s9k07oRC6n z^O0hd?~^AOa9*sfhLNYr4=LjZ^wo9c4=mS(QK)ERbH=L&bhFMDrzAejvP01>F0Q}Y znfaUMe~z(wH1o*b{SAuT>#(}1oVsI30GE?<(0H^FPi1ztr!asTaq}(<0!e2=Lo*Q4 z$Mpa)J&}&jpKyW?#G*Gmz)J|@k6U=jp~E>KiIbSLqAQF@&qfCR)yy(EGrKpR5;(w$xJ!>C?t z9&&%o@smpEdbikZ0$or!A3E>z`s8WCMBH6a2#g{ zRJn38JDX`{tdw=q&FQO}x3pxEZ}$)`0`R~bWFNnv`F))6gBNJ9=lWAxE=PByleTc{>u>cOos0PH{Z#q~%g5OM zH_O)>ttB;N6qxHKtgY=Ezq8BrRi}NB5BtGo{5J~GD12}=vtng`c-KrPk-Lo_nj4yrDY4pdZs2e{**@_L!T!7 zN!kohf+n-^!#U(hdn2BR3Mn2g$5C(2E!cq;5(m-xpUfA;)yQ1N!wW@3@b#$v-)O8{ zyCr30w35HaWn`8qY{6_=^%TlyT@av!=pRWQsP%y4j#oM1!4lBZWar&OO!$^7zE9SN zr?jv*jK;y8h6yw~heHXKROl~Neg+SMKZr6#r!Ph?Y1!DY;Kq6_O(z0n{kcSv5QyyD zE6HR2SbsU7oTmK#z-m1}F8P`DkrvTMikhJP$p<8)y{K1XTn?9~U%)~+s$bnh9phAoP@!>GICW!2y$i9w40E!!0fSnzv{_OEk`wzOQ_KKiM<)*{$)8)}|8w zP9ZuKovArd$WPT#gVlPZzx)k!RZMf%!q;MJvl4M2qglDg!t$cHG*02|+>OzM3wLz$ z;OY>F85K8kb1nJU%6zXWw)Iib+7vEkQ}`hdwkgn7OIxdKD4XzGdIGrt?}MA==67ix zad?lr+S(!>6etbyXZ)VbQ0r8*QH&|hw58~K1{ zS#b=x6Q-c3es5Pp0T0w2fBlk6kcBiG5F7hcRB_DnQld(jVfi`_wk$%#wBxs?kuO1N z7AI}S6?De4>%J={+{heTNHP^S)BmvXVT@{*<&K<<($d|de$84B{B~&fTZy!Ms%G_TIeOBMLse1TmWaflP6nor#KzfVwG;2RHdhcfRI_!gV4300Ldx(3_6RS_70ssHz8hnEZvmUqhNz#MA>wc#74 zR9Lun5ZjX_>--2kXLW7ffx=|dImc(3Qe8r6&AG-JQ@9|khdgfBvqcQC>HQ@Iz7O2G$TDz_p4W73*VJZ3+7WXueNhtWG#-L=I5bZgrtO zaAQoL78KNIm9^PWQvUccW!%lsa788Ns|9(iTi*DKk8APV1fpWsHN4fW5tw&(>Z@u5 z!Wg4$(Dhz=PUR8jxw$0n#CuRpTd%u(yCnI%i&iE@xBn~37?j=JFMAy0K=y3W=kK(>=?H~1k6Ial3$>mR=a)P5zSlng z`tj6O=d8z0+H#}o;xYR6*1n|efGJ&J2HtP0pYmG0-)tcaON=KeGyCy%_^;;0F)7`% zmR2P^@7zh4-TDz^rI1u4@vAf;MkJvv%$_@l^}C zQTMD=6nX>p9Jel~MiIfs!>wdZi$UIvRJ-xrpPFA&mwHi<%{*fV+`nkrh_QanU zxmp2CN>nr)j5{q$&hp=FCA)q!iJP2<78k(JmfW?R`@T5ak6XthUh~4ikF3Rdd#u}dZR9otp1>F79#BqZpMch|*E91jFyU_WWc*PrTdc|BhT1xZ6t>Egi73$-G1 zOT|+c7Z*{Az?W;~!TovKp*H&Brd=NAP3D=(IrY;*XAZwy*Mdu2_G8i(yOR^-9F%24 zg5#>^4g-u88E)(?AF-M*>SVetdo@3G*+t_~R~HW@!}Wfes@L$<3VyU89;bEru`Hsq z<2|}h;Q}A%YM!+0uPu&z_kT{oCXgf+$*_)1`S6R zLvl+|RpsJVhXT9BXsN2koxMGdsqBUAIYw!Z4GW9XgRh;RVkJ1}g2*+~HMR5wFFXs) zdo<{urOH2ny*zbE2eAYdqKz9F&5MP4G5xfo3(J|AM$auP$wVw z`1N;cVS#0v$B+Jezfr@fY83a22F~~I-)Fq|e&v=QPV>+ZdMmXIqq0XsQ()BQc4kvL zzR1>grorZ85B=83$;7;cPNVbjkw-i{m#mq#AAPS$?0W6;9l*BkGQ+9!8sJL_rHFo_ z+`M(m2M}<$;sYRjslgBT%ad81l+kfnFHcqd5+)D#f!Lzc{jsFSThd-$m^kG9%mvKE z)lvKy`psf5a#YLwz`!E+s|4duS(?VCzrFlRi_aA zL&+?C#=K=-e}UWO%(3jN!-FNj+(Wf^s1r=v!>LzIjY)qhXb3UqN63B znv$)rTZ)P$-M-5r=w=DS2rN}9c-h07J5^c-8M6UVD$h&@vY$Yp&xW*1k|E8r!`)d@ zcp7%w{U-bC6EcDGG|>}oM1j?Z7yKm6l}CYybnTi+lp&`lkkQjwvGFX;qk^;Je3=bG zbw-L-m|=(EaX0;sK(!KGLK~ZfBkN}N_=JSFj~wF@cn-Q-YiNGPa?C9jNGAMXjC?Al zxcdY~cb+hTWG;(i|6TX>7g`DImx|PW%G8`TM;sV#{%BzfdeC)RroH5TF&irI-R^)2 zw(i;F{oF0dVRgAL^R;lMP04I?Z;J--zx2}JwH7oFSl$=xlk6YOXT&dZ1@9BUEpL7? zl;xcqyMA|?^0X*jU8Ts?2woUmyS`QcY1@{Q(v2-+>Tc3T+1izj*&}uZ-@g5dNej-K z4GOaNJfGvza;`QCSO~U|(%=4UmF;74a7Uqj*(?rT*3F4Q>X_X&RNhRb*)(lWgL&fb z(E?6H;OBQgT_UdR&*RFO@(8Rt!QJekXNzGwkwU)0%MOq(!1EdJbV`Qq4F-1SP8 z_Tm#5k=x@fUyza!8*u0yUK7WrzKqN=|IyZ_xBVr`p`L_{9`25F@|W*V6QOc{NJujj zB&1&LxSoAA^nOWID^}e1jh6-bMpj=R>a1xug&1XQtGYc3De|9vmk_tLz=|jq)nbXB zuRKe8%6ndugty|Jl-y?E;LMcOpf81OwdgLCh;M2e+LNc=FI^F^hc;xH%X63Xl zF)>`|#L)tl{UsMi5eNJDyba+O%ZcT9QRI5R$aJtfCLR|P`|dt^P4(eek;Su$;+z-x z);~nde5?j7YrDEsjNbfKkm1|v?(JJR+`5~%IEdzSaO$5{VoW6rrx?MGXVNAkaMlJF zMnOpl`ZUC*0pz$ml!a9rTSSgwGU_|YyxZT*J)P7trG`+ei3P|s8(FrrMCTvVr zVQXp2A?q%3?J&Aq-ovz`o7llv`Xna-A)m2>387+L;C!QZq0uF_dbo8W+ibyoC`;E0 zhXZ44u~jZ3znshEeY|@h|Jjvg_o}V=ia7$OOIB9Q546kbVIk_PQ@+IeOiZASWpeZ| z9;SKi<6}JjD0*}A7AQ_kx`N4h-gS%2fC1%0R8&iu-2yyMdN#Je6#N$F<0X2Wc^U0f zJat1uBK#KGrSq31@(t!kmW&%R7m_EC4FY@KaBkDg#O@D6zFxpPeiQlO_s#>EP5YD; zZ$oA7IwJG9jEv}MLnTZ?VV`F27n^Iqb9_lNf(7fOkDb<4S4SZ^k-+8D*KyAtx-x3d zT~d@QI73TD^q)TmYF3yZnF_~pg1jiaxD&f5C%H0KO$N51f}t|#m5&6AU9>`HQ0=$^p zWw~%))x@Lf=?y24sFYc6$$L^gPy8H`#|))pbfrtXH2wR#%6zDBpHpP;ZB`$>dKFyQ z5^Q25fsU?LY}^UGPh0=T=6NygBdlm!?T2@=lZ)>~FJ6(&8W223HG2LW_bJwdgH|>F zWMgylQdMsHXZ^bZ7eyvLm85Yc?p;n(XSIQ6$BX7`a_^kR;hn6BVaHf@IMzbLQ`F0P4c8Ady_J9*w5k|cWZs^M@mrK{9+!!wn6?c44a z*oamsS8)adXprqJkS55#evC)a-1sDqkdVA(!^_-gcQ}fa-^zcI`hhK9ONGV_B z8)tMDC}j;jbeg1HJK!QDj5%`gjGd*Ere$9ZlRFyVdJdl!#F*mv_=o5Lp`AUIe?)y2 za}V0&)7IN%$c|c+29m)EpVpk6)8F4%v(m_TCsr{6yBz@+sE9Ua{b%!8k*3|l%U z9usQm>duI_K)_I>6zIY*aDGl@IbFQ+DYO`irHEWtHiZfSCXAR`gxBfvp`M|k`0kIB zB(iqcuffh9bd^wB|2Qc0$+HTP?_poZr7Hu}a=wZu2gJW8c~?^zp=^Y9w7Z+Gyj)aQ zClntXaEFKI;?+WUi;V18d!T-J%cWAXBtdcU$9v1iG1rT4H~8b!=hzz1)tn0$da_Cx zy;e$=z1CX@q1WM~`f%P&hDfl#lLy?u6J12SCH{sf!jKBV2Z`Q6#+*wc)jILL1AIzz<`+gg5PmD z;N-;2zh!PF0&xxDtjhyCefzI~n46hR;d!Ffod>4UPIPLIr$f03_R7lSeVv&aH#|-% z1))q4Z#>h9?S;eI6kTAs`e%drH6Jc^Ye&cWmI_WNkO4BK&2`-(u(j0suvHG09&}Aq zEVhcdFl_b)dK{i?$l5A0Bp7ioe)#y&uc~VOBhz!-kT0oo0#?i0ky(I{zc-MY z-&r5C`nHhgL4hf7IihR$nY6TCQyWJmrwc9P$NOKN#VL<^-CmR;m)@9#yxG&zHKR3w zmg=eF6gdaQ_mD9ZNhRZC)bvfv*;x_!qys`_boNz}*lZhFI4ws%_fca92jWtW;!}B| z4pg0g?J>r{;SoLAKN#B@E|}%r5+zn9(#uTsBK|{YKuk@2o~2B%Y$XUMXh3 zJp4-h9H*$I!g1+Kg6PyY7s~r;>}=eYMdBiGagKOR-$dy8*PBdaqFr~{moPSdghSSk z+{XGwSZKCchywekHcnJ{cwP3$4(P0JDNK40N=Tq1{}({(M~yKhhPxO@S#%?8tejd2;f3+`{6AG#GNw!^7#fYeFACX}E#|1RUL)`vD-1 z+^F86OY+#h#5u^>aX3u~qU3D8sq7aDw4u}zDLr9f*1_r}mV~OmQx08Rmi4ygiPTJ@ z5E5WN+1^p9WlvazvAwul>~ zi!5R{EmEuaHY}@su(vh;E43YN8#^Frm;1L;g?x_V%cGi+1I<=Xg7^K!A@ZTT`7CCE zx!$IBrWfV)UmTUQIN+k*8UHTxeLijv5q;18!kq2v)KYsO>m2&*hSXO-j($!wd4;}1 z!?i0~t_65tfCB{utL~{Wr{(d-nOiNkEW6j%#)`yDGWy*#U-dT2zB)Wag}xs6w~!vU z-wS=$kDiMNJt?{4?Sx=j9M3dIBx@aqNsBznsWh%1p*+R-vi!I5l|DR%QsEv zjOw^jZ@?o-k-*i5LJ&mB_nO-CG-jgW;$d>)y4!ZZ19oFsYVeJ!aa#F-mhhGYc2j5P zN9Fr*`ntM8^+hL1FIm|ika4DmLY7NQ$lQhg25_$4!VA>0;H0Jl_@A7d{IP?%4D@Z> z*F9CO`1gW8WNlrg;CGMMnr()BIoN}O#pkwvb5-^c9APTHZ}fkZW7Grc+r(s!Z^Z(m zdJ6PU^aweptR}>j9XXk+j7RzBnj3MZ=ad3FI+$?ZP=8e$$h^bP@_>Eyn$1FIU)t1} zQW*3&K~&s9i;D`-ez6!&OCc(cnqSV}SMBK3bTgjFyaRW5BmHgTgQ$so%Nq#&=4b2- z=k*_+-GNVzuEIAUH9LEifO=Aez{!aV6kAuX4m}-TA>CF{zJiWMaKi!q*a^X%4Wk%fq$w*ZWw5;=p{oYr|+Sqo6F zu-CV>$(Jwf;mR zi}Ei|J~FJYi~n_+0|K5PQgb~yzT$Wt?K!~!_8e|h`;dbrt}vVxWnCj9DM?8}*hu@M z|DIKx#^8IPxWT;j^v1#IRZ$U-iJ>8XXy|MpfyQ)bzxTZF4hKgtC?J2$5^-1q`vb*NChTgq7>)z)`l zh%@ZMx-HM}XR=(1->BDn1k_Y{BeBy;v_OaB7bZhOOZphw^h-mzQ>~lHJ|oro!~&Kl zFE&sZu-0duha9K`6NB)+&ywv!Vq#scrrRT`8%>dnTOeyUre1tOr9af!84@ZG+c`+> zsaN0lgWZInBTVQFUgZ8bP90lnE2Tw3S$Gb}t9X|FGsxO~;9Dk3P5m~kEc7&0zJj4L zzqk4S(e~AGQFmLvm?#D+2ui9*NJxW{3JMS19U}}O-CZiuAWApV-Q6PH(hbrr-F4Tf z&pDp=ob@BPQm6FAK5`R%>e`qpyciTUhs$U?Bk9srVujooT-_?9xPh)r?jv{wp6 z6FON%4adp_R8@(f{OT)7R4hovlqQD9lhRe#?+P5bJlI{iFU>5M2ijQm`WY7jFAq)8 z=a?KFwHVp&G@)N4_Y(0b%Vsf9%+**BbZ8rtM#uX(y7xJ)2pl;_S5pQ zVo_gtbl!@5YMQsTI7XGzsO|ge8Og~W621EQThc@Wij%L0yFu}M*E-axB>esT1q20S zW!v!hdOu}k(D>jnPj{@adx@y(G2yMehxyMph6|6q%gTl;E|XA|>)DwsZrv4)e%kWo z46RNUBdNXwM0gAgjAV6#dvX$rcxVRD;NgQ+6n;@lKRMaHq#uXW(-ZP1t6m-|NE&b+ zv-~k40yR#)9TED43u~jh+#CIf!({2Ez{^t&;w7e}277P-4Z=I;I^3#J_veD^;Nozf zxT;7BdB2?l2|c|>DycpxQW(FITgFQ46ybE}9LMiVZ?MP?1Dni4YutP{^nH^Ci*2r) zoLqxBJdCC3r2EIdD+5DAl2wkJaR;SYzL?;e_zrOlL+B5~!o#JC3<=?C_l=7?2eNM4 zZfbcSKopSD?6_JwI!&O!8_3tWVrgkfR*(==9>UhQJsRxEK&mfbAoWdbwhbW+MJ-6* zsJGby1?$$Q$gO8))-Pstv$~>Lt_}*pyk>);{I8>kgnq2TX!Vi-+;&r<^J)I&2)pjW8rJw{yJ>rQKr!hM*W^d2< z6q_?|xSYzv{c3bXN94WT!pLV*o}19lKS zXpFK_g&b}N=f5V3V7VuYsPhs!s;Fn6GZn&W8GSm<42f^T3XU zD@V%|KZq|wHV;PKp-|(~;Dgv^iJX-rgz#g%xB2-@j)yB^5WIu#dX4aXU|@oT!)Q~} zC&57F!8|MCYd=KB&N8JVqDSp^f=WFJei&usS}&N|J2zZJsvV>W4l4-{@qUAL8stv^|fgQUr&9N^>Q`Zmx+nJ)Diphm;-7w9tS z_K2@qxYmnXTC$8A?nmJsZr8WAdc*ea0u3aT4mfYHBszs1PI&=?h3EiHKxEI49P+;d zK-ge7UwwPA0FKsMaM6Z@JQ*r75`r*lsF9MS8g_b8ri6s`BP^+~qz=t=RvZ8T1o1=* z42(y~!rI5K`zRu%hZ48e;nQu@$sr)>`5MU3p8&l5E!!AO(&wk0)bBG=wY&-sc7rpw ztPG!PtY9DNn-NO6L_Ukr!t2+r%zdr%f>t`)u||u5kr666hm-|CshIWp?mc<(v1Aj+ z1YR{a&&=JEE5*S$^#+eSzgPN0TsVr~Q^b}gp^I5Ky@LesO#mgi87P!@$KTBKw2&t1O0NU-6A9gkZgidQ&<GIfy z)ZEP9)Dn~-p6vq^rKqpC{(KPd|B%ejTN%&Gt1od#xAhD&M<~Mk_-hBDC2pRuy~C ziCfc!r+UvM4oV>2FeV-0HoRV+$9m6+^I-U2nWX`wJm*@ztfEJzylM#q9H)n7>s9~7 zYl}G$AjMW_B)5Ff-o=td3?BiQSY~T0-Kscy(PR@8c?aKf|Wn-}MVx@Kwz;`;pAK-X?Jv}ZyN38#9uTnp`La#5m z{cRPmjdnAWshL@dQok>K93RZm)TT4|GS4-ZXh8)ia1#Vy)vpXcvX)M(PxDtztgZ+g z0AySf=%;+WZpA~)g4kUfa&qz@T9sGQ++FwYaDcQBRtP>>vli<=w&MDH}l zxz#IGI1Wb3!3vjG+t6GRSC=P!Q*d~sliv?IP_bR%xg?Rg#A+nbJGxy>a%++paw?jD zq`OP6Pj~;mhy*1KT-;M2Yq#GR47NX-I2`g_9xt^BFB*&bn*6Ee^%(W^87Z98ivWvs zwWGGKH8o_?UHo{ep=S{zXK}fKiI0GB#_QG?t|QHMg?m$d9-f$!bK6a-5CZhI`J?9^YpPto*pLFe!5fnZ!9t~* zJoBW(8335KX_VbYRekV(_N+dw$o~4rpZ|_Jk=%b5g{Z&y^W*6^{lb*~{yF!Cp#P^o z!tr3t#tLtV)i85j|1ZKzCcazz_g^jMWA1GiaeHIn6GuObc031T2r}mNQPnCNH6`0Q z)Em2T+W=To0**Vf%X^8}^uQ9w$`%Eb2M&z_XaF99H2*3-+eb1VXCJ}S1OARE zPV@G*BYPZ~)M7^S#%FPh-z9#yU5MvCu{wx*qzP5AR`)E;3nFfmp8{gd>-|)tM|!35XMN;9Iry?iSsmE%IQd^4KZQv4$*&IxH!}`Q1sPG-Zl% z$t`HKC-zl4V7RHbV1LL?1k=8V^NOY*WIitiq%bBUvL2s9FHgQ!1Q?P2$qxHF=d*() zdSV{?tKTYVV16!r>2+XEQwFjZl&%w9oq|a|Jv*r{!76w8^>kJSx70xlJ@OR16V!;D zu#f?H0>VhJQR2cceH6R_Uz4>tQ1?HdB~-|t{$RQ2TuYC@o7+_VpP}}6HKu=YXSHED zYtBC;M5CwR?%f4c<6&E4f6#<})jB1*J*T$zg`{M+v6U4GjSK}g!Bbo5xq-J; znCb=wD!QYs(d@ucgfrT;+ZV15Byc*N99>8hIsI5_aV%eMeErI7TcYhXvBYv#XXw@W z=2FrvUI{8RhwZpe@a7+*!sPRHdwC9z->)NI_oYAL_kFpj94DCqm$U#hoN=4$6kEIw z@ZA(0#N}uT2n$cHSS5j~zbT2h0~Jktb$;9z3jfctX+EM^Oi*sjMLt@cnwJ+w_Q{_pcOTQ zc$oYK$K!2GxTctpe7@g?qlxmbZz}-jNKbEK{{)GeUJ}C(eMX5HCY}!;e*J*s>*WDN zw((^c*GjFxgM@^YZjPvniZ+y|WK1dUamY!4v=gv6SDUei3rU1$N zMj%W0OY3s=>GTm)UPnjvciA-V!A1w+`Ff9(*tg9RqMz%m5F(jUoArVAE+p zOy-`b#KPc80HpiseVpN0LP97HT7%8Gxv zc|$dvLqgl4Jm>h^Ku+|3VDMyS87{*yYV=Xy=OgYe`M+^7O^`E_oi8m}QJS<9VMIvW zu2TF=WBRh|b>fW3dmbOS>cIU4e9y^y_c-TsG)hd`a0n#K&K=(+-h}RDiQoz#tI!nt z;G<&^Ilc#f37Vh3QD@Tlv&bygF?%_?$f6?L;4J0N+522vX8?YI(XiM0go+F!fij&1 zLhx^vCz!6Y3v6x=e53D?lh4KUy`|TrmnN={&+r968wkr6M7~`_UheLoLb$gQFD@)h z%~%yu_dO==4lor|vYDLp>b-b; zzLAY>o-l>G=Y}_29$HuHz=dgnE@eM;*`q!R1U%VWB9LJ9Mv$MGt^* z@-Y-c4z+}o-=SQ(QW1q};XZf1-kS*?x0zk|!KU_{Zt?`jhTshxAQ~F+QdybAIyyQ7!|e0QtJB~A zQXC=#i7VJcnt`qdwkcil_!@MJU#hEB@nr+kVvVA4FF|)OStrCdLrYKp35*U{cfR`~ z2obHVt?pc*Rd|__q;e|_Gq|*n^W)6P>w5K@YIs_60t7I>cb#1|DLei0e5lYsnqf>aq#m(8_gKIq%QPl%au-J8ZM=72(P=mKDU_eH{?z)9i6 zF`@PY0Yt$#qS7?5FGDd`zsY{g;ZS7fsKA$Zg8yp$vC*R2!J@=L+`6WX^g$fGW=W_u zR)M0356@}HqbA5lD6!wCByJCBmP6PJ0_+goc7#!&m{G)uF}Vv2kM4-(N^t!^ju|U3 zkQ(2=PuQ<@CV`m;VlWkTJ33Bmx!@v8moNOdn^frq-Z;>`bSup0Cv*Df&>}a~lhfP6 z-C&gSzT*;$`c(V+i=s@v-nTgxC{#Qc;oy^`&8)gS%w9E$OFi2hm}XDEiR3z6<_*9R zjMQ_*#DA|e(=6?*~(;i2waiXZ^Gobk$_ko3+n(jC8wYTdr z&giBrWFMYx;!I9X@(i<-VxCC-PfUF4G!+Ll0L~Y;!5s#{?%#?{ZU7mzF#G&Z4r4Vb zsqr4ff!?mJ2=CauHW8~r8qaWW@A&gOW016+qI_F^EO5EQ2|$Th@~3T z(xe-ECQNwO0Ld)|F8}S@GeesYS|2a66a;7C1bF_PYA)8+MRt2YN{<_9Z`>Atcgu#+ zs&9Qj!gqs-$!+2%!ozN2QEY9!Y3uLZh}E_l)udAH)tZTg^Tw{4!9Wc-EgRsr62wXfLij#;;XgGZ=Kbl%-dDj6Q8OO#Pr zEM`~Bob#Br!Mm2jDy3k$M$NjaC2L;zldk`0vC`!MHw-R25drpJ(meuj^qZKP#=^-L zM6L219&?G#&hr;$#MGZWoz3c_p=M?lIW#c__azTcShmBVXric1S&#je_3R_K&}%x~ zkraUkPBN&IdZc7?Yk^hVuC=;4s*}V`hj?f2xX%F6(Kms*^Hf>=7r_6?lP9md;dGRg zluViY@d3~UP*=X!>xY}F`*QF{V^pjcrCjFLx5Y0#x$|khxrr*D)tnEKd+{dQBgx6g zg1)@rlUEoWZwAucS*2{pn1j-jN^TOW=IXYLTu}mFL2l|4q6F?R)gUK3C#FPdnUPU? zMn)2@`_Gl;@cS~(zG^M@KpPPj1{mzS#B1haz+(ZA9CB+8>LVIr5uq4MtnW#pKhC9l z`N)YxQC?mgkidFxqU!k^+D-c`Z4)yyl6V~~%jKb(?{8^MSjd8)o}ZkNX_?+(>QSwq zt_#U!(Yckrse2VUc|6lUZenFd!ps~9H&lRNLZQJmhi0@d|AA@va@@P;dJvO?#3ZB%~HHQlb3Jvg(|VEA-s@O^x_$CLHPmW(p6}n=e>PM zJe*Tdkl=W13vh8)yZ~L9gJ&cbeGi(|kw}qW zs62m4zg(vBT_3)h?IqUy2EeRJ9N1qTig|Sn)%5iR@%i9w%DOdbG=7=|5d*NXmUQcQ}Qsuw}J=+yOf~Q12jcIsNW%DUsJfzPiP!rTvDv zlwyx5Eps_f%$Je$ErbSHp~086*}B~q;e#YE$O_=a+)W{w!<|ZQ>Z$2Np0ZRLy6l@( zrh<`VK6{2UR_|G9Nq*xQ``u z!=Oieks^J|>W6)b8c)sjiK!`(1Uvs5e?Zay1g{Hhs0D^uG9=PAL69}@N(G`)*N2Uq zHh_qLw1s;slnsD*&c5gos2t4&_}LufR4p*Z4U{mq9Bg;bT2r7J7w?RpyZ=wzcpQ-u z8W>ntpx2LLk1ZDWZh^Wa<<`y^OcKwe3x zs6Y&Ksy=i`9NYQ2d4yK%#Y@KRV@zmW$zWeQ9A3Y1<6Ckq1CIV5ATip8oVYJOyB}L4o}x)K*~ ze^ri`irBRQY?!mH?Xyn-p6s(f_CfLlYKn@MZAKTFOxpWO=bx)6ZugB%La z>8Ru!Z0$GeyNb|?o^tw*T2dus+%ppI4GkN#j3|xz8W><6l{}ZiYOf$&m^bU3gl9Hw zdaK*f_Z}&u+ks4k&XQE%T8l?AGhX6R>S1=o4wsCMa>cE@6VJ!U)SocPjsFgDS`K`B z4hpG=b=oS=v^1(Lk*L2C1T&BJraCAZ$Ir>fh|@WaP@pfs@I{65u}G(ktnIE^*Mj0iZLU z46&w2*|S%#(9C9OYKO}TExoYNAjwH}vEL04Y&AneK9W;cQqi-4v*TTuLTUU)t3zGH zUVF6L{JblYqutJ8+)>r_ZA#Wt8CFnY*T*T!s4n+IYVQVjDEsoZhV!XBkh-%vAI)C?I_Jk6ds4Zau8)g-o#T_>D8yGIJt;eRdYxjHLL^vG_ z6;xGy3~b_-V~n=PaoflU0Sq6(Lr3pS+T9#7P*5z?ZJT!Fc#`g|Tl;;#a zuAD2X+@a2>@NV*oiM|{sIH+2G135|4Iy4ma{2W4~sVlJcM3>B{oD1boU2je^0#RVC z5bX{r6Sb3RgT}FMHBoW*^S7-?gM+wXk$gvS zbTq&2y#4!&56}vKx+vB&mK!~J=^rL&uSYo1L~UnX{_>{4DNtxA%VMpe`D+9Suvrhn zwDJ0CJKA!31U5Vj@IOZRw>0z8vo&b%Tv8JoVLo}{&>Nz0)%Mim)pZq7>Y}~cI1)9> zanJU5H+TXT585D<%gSNRo#-aW2i>7Dw*E1wP02=;0K=O#09*6E!x92ID2hNFpdZxK zB)Ie#iCoxjoDy{r*VP1sgfx;zJ2W*ASNcM=fL3HUI-60HM5jAV7sh(U<_q=<1So(QRTM<&`!d_DY{l~C!V@5Gb-#2g2migaPHjSL&rE}eMG}Tz{#&G`$1yPtDd<)iSE;SlfFm!v;NYkt=IBc z{CcFI4&G>+ke`%dB$8}_R{n-BaiE|J9E%4&_|mD;3)Lj{?s%-%C)-$6XAU!lWK>5g|V!N+_po5zsKaSe53QE)pMjyJjbjCXx@ zLU$+31>Z@v6uEw1n2eikyv_h%gPM_%TQq`jEPCg$OsaGW ze6Ag$`hR||svGll{KF(V3p4GX6DyBy1Xgu>h4MVt5+z!AS~8%(HL|EMT16?U#n^L=%+PsU; z-0kAIJSeuzrx?#NI>oCz8LiBu?Q?7P z9BO<_HVw&ieJ)}q9S#>?9nJPVqjx%K$+yExlNMgVjQ3qpF>y!Ul`?qv?$hEyC43IV z_mqpw$r^et{ywBZyJWlFW~Mf$zL#`M2G%XIaJ={XZy%oO6*Agv}7iA(9*434`982xFSwa(v(x#-r@NmLc)*Z%HYl8uGl9Tq_| zSLRKs{!*-;xcRR?+}&n!!XI{&6;=^w3oG0=#S+=s+VIC~3Wv5(XDvpPGS=@MT z(Pdb?VZAYak<5Z-jEiz6^?W+dby7x}8xu?O2zO)!^#e7BPiAxl*>1}D-yQqv+icnQ zL_vumH-UIPWg)`7N^{NrEvHF;qvXtbjfW6Z`m8W)=ANv{*6%kE@Srz#zXrpU!*>A( z)SCQU82Do^iNn&)uVG9$0g8Cl)p@bKnHL*9c!^e+iTO68A+ zM00gxQ(a-qOw!yin7+_HM*TF>VJ@>fo=YHEVo<`-EU9PYWEt_#zWpzPlX9fMN z7KZ4KnK8UI;A%xoYRuM@$WZCLB4^TfOFX=@S*+?UF!-|O65F;*POCYNQ(7y#l!2bW>Z$>x1i^W^1N)wmOo&s` z+oN(PDJ(2xksAcTFQ>Y2cdHVjgM(uo7OP~rP+yfhI;sE9k7*r`a=KM2l*J6Jqx2|M zCqr_bgNJh6ubDt^f15q?S&RcQC`N#r!D&4VJE7gCfnfG{BX7SA(q=K6f}BSS9Dg|_ z&$I0n&8Rek7m??Z8lIPD`UDdY zB%}1${aZ6h9K!WdaxnWdE(Dzkz~vU-YxIh1s=^6NFM#dH0XQ5(nCC8e9|vqD}X zs)L~!xjBeKn@3XQ7fB%dIwg_K{z(4~9-%<98HpIaC zz(`~;S!wKLR}WpTfVKsuZ#Imh*MMy!$!0S#VYW?N*+o-ZdvnD~>GCzs7ZCO#5`LZ$ zE_{$%qii|Hu5I8yMbp#M8!FPP@jA9e8XC6u=n$_>`>PC=?V`aL$i&QOVu?=j&eu!; zj(n8OP^7czY7vgyIRn(Ibzwud{z$pYxDz<^P{p7^nWghidCbu~Rli>X707L})EAbh zc;EVM$N>CMu_BXylvf$y0cZ)DS1>h%{M1olHR$|QodS?zqdED+*+K27C9A7wchh|S z+x0iVt%k50-PDt%VGb#zcc>FT0A>C&Xvv;f9-Rm9UjlUUSO_AN>tcVRAsTqt1keX| zrp@K?Z5MrYc!uvZ-1mY%`oZJ?Jplp19eVpq&@lBi%>}I%!VF$d&>@_Ic_bi0=+JCm zrd?Z0zW4=f7@>O`@_75KJpxJjCukod1KZxY2mv14P+eVFn=eAhUq3?Yo#C9jHRE?s z3m!CKU4Dmk{6(pkEplVXZBWFa-im;bP#lmXSMgawK)XR!e!m@&X!=WQWa9IWgw%{^uR}?LbRp0{@ zovpdaw(?#>jdpv97vf)DcXGU^3GbhfYv_+heaj)8B!a@@;&%mGf@5|l6+yzD@cez|aMyPu4&|!Z!LF?!wj2nk}<{UMKhDwQNWDquQ0*~Z>wwErZAM*jj$|7C9bfX+C6@eD;7074h6jRH*sw(4Y< z5HV_Qci1f-LhDxxAS4*1JS9`6b?wi(VG?T|71I|N7lUM(2iWLK?AVzVm5|oi19*&( zP>X4box?tTg7VOwHwCZGK3$LMLG!9i8rfJu1*lB!q2=V5OwZ2V`qnr;UwrKmjRCJi z-N06~ee4y`**8EZ6a=w3+S)CRH+ggN@)BfPX65DOKN_znh7Mi$p7LY#(j{nMn*CTZ zls*LnfQ0db=pEwKA&@YbfLaF)bfmG`9YinqX6G2$79YYaqi;Ky$ZddFq0L(mDNCPW zaG|&OY*57Tfgd@HTK_SpW=X4quA+j1>Y*Xe?xc>-vU(^*3DE1oO6)&t3XZEAv_C5h zjxHGC)$~{`mXeAJa>Dg0`b&%1pmGy*BdxBW!KIH$s0TrO-%23UE zGDW7c2RSL}1=gZwv>`g?R69JGiw-7%0a=R7dN|xxn`B@`xk|{TIA;x7{DM^)>efeM z2OCJi65&VAmG<*`w7fG;lOCBz8!blWOMNb2^*~MGfE%2z!Qd|)9sETqO#y^X3UwiR zfn>^y1j)b@70)^+BR{2t=HAfnz<8O1aecfMrqz(|!fl zLvpFq=V#7|trl6d-1PxzuMadIIn%FbdgHY5%2#L+%4MpO9#?-C0X`%B;T=Zs1^?2e|AFW-7=IB;P(8eNysj49@^NFarM?UI z6!!X;;zQpY3vJzfaHF@w)KoZYAV!CjS}7;2pS)_r82>qHi6+l9T2i6vChPbDyTIEW6m$q?7 zBXdH$MXDKz!mq*$Mw&POfug>9gLZm^eCxA!yt^_mFaRNIDY7c{ zsU6nCO;70ye+WMs2gNPQ_R4BFQVaMEyB#~zV(uxU=+9M-zV~<8CIhO*0ITr_l#36+ z`=;L9vj8l~n(k%HSaS;#t%Y0}R6qjw3PKOYhT-fu_YM3tg$9Q%di|N_=m2Q~Rg4Dw zC7=ZjTH9!>51ovr#SR7r*CngCS|f|xJ^WQkn3%3Qow^R00||w~emE?MvdRxG65DS* zf&jSs*!OPzcRkaR%Ey;?{eIH-3HD(ky+NFw+oX8W>QSqDk>NtHQgtK!{xQ#mnwY6@33m$Mw3g zhvejp>OD_n(+^ys_GD%gL86}Jj5jIAZCiT=DN{?l`pN_#`$M~garNG$e0;UQG5P}V zcxm%@Xb)g*J1X_lyCzv@PLTSep^}wh%&GoylI!gOI^KAUZvz6b0Y{WKoWc3|Q;gQF;`juAwdrp((VR1BCp6Gw=5kVj7;H=x-)k0`T zMH*~4t|>HqAvS9?b3XNtb%5f}4-tM{}yee6DTGHR|YQ5plNEP) zw@;g2J6NCFLkHX^!@e7Dc{k||1S@~*Z47w1*%nTT89%q@_bd0LO*N|DO2uo<-k4VK zz)}4Hpc;k`-{$wrm)37QZ2h6ssqn`d`CdW+d(X}#U#UnHdXh*#8S)4bfNF?SR*Ay{ zr{T_^`Gk9?H}`RgF7t5gV=Vs=K@}@<$XanJ34NDAzA-l+X+XVCwSxyJ0!M^?3k{}E zYDq2?SyXMA0@{0o=))sZ^_Ox!fBZ%sjzXevfepDK=J?DqEHv(j;#yyceXPmOe#5xV zFIV2GZe8E{T9tZb_1H3iqu?T=ORTb-HKNiXxZRQHg-V$Sk1k)%W*^RM`M^Lp%a}}K zbKBIHeSH0=mTx(HTmf3t0yJjGRxZKuHu>X`(iR`~Q0k}Fe0IkGRV-%&P;(fE{S$w@ zfCdm!6y{p2OECV2zMvP%BI>;{@;|iFwi1U6GRZjsm+e$+8VpadH+vGacWVQ6z5e5= z@QrPlt&VKdE72)Z^biv4eu^6&g7Hj~U2n)*?s;GmUqDV*`Y*H_sWn4wbpt(N<4G!9TY#1*>nHCDk}_)n9^I*K5KL*QK5D29y+U_=mVg zhm)+>r=7{3(&s2szqMB?cV*JA%Q#zdQp>1KkDG|Hzg+pR%9^j|k^rFm(qmYCWLmXGLwp3ZI&OOs4m!7B5Ylg)me85tLnVE@+ueU8r=1r&37|HKJ5R<>@n z4+ex8+KN72J-u0NcEY?5zv_dEh}LoEldfFpQ}fL~52jk)^#NhOb6fh-SI{ne8!N(F zSrz$!3*&UXM(Hmr3$=U(c$~TwyLM#QGl4^%s;xhy#2l%O1I%=XdsgJGM27~al0sDpc88yiD@axJe4)ar0FK#JXHRYJV ziBs&Wt7*c4b5V>r`E6N+&zSj=oQamPJME7hmT4WP__^s=acOfZj6a@-7yCBpvVQ#a z_o>RvRS1{FRH~ckx%L}#NZk&itGYxnxR{^IU(hP;w$vkDXG4y zQ5 zi|e$@M5WDx-Y3&t&O4Mf$NA?sSWEJ?yPNUN5kF<>e>kn-da7@Rq@J0WI}zzLDHHmD zko|eXTw4Fw_|WEZ$(M{v3V$D9rcbUZ$A88zG;k#bU>=B zuVCf}z$axpNPgN};HM-+r?Ui5AC4oJAdHyPDMNV($&9yeP_XUB<=p3jQL$v$B;quV)~E;LK1v`6 zQ+;Y%#keW@FPnmY4>A%^@m-NqDrM&CTgelki_ffY$g^(32%1Z zpwNV!-@@^XDDr!mo`c)bey<4l{-v`~p z{hXVDtl72w4Tg^HpwjwtS<8 z4#>0u%g?VBkfvPUOsnx9ExkqL7+*{Gp9SAT$e5Uk^B4E9um{|~Wv{u7Vr$~1fw+0% z$B+7Um9jHWZr&CV)Wv|N)P8;+bbavk^8|2*{`AW=56)1@g@Upe29Lz^Z7;IT{aYkT zCcVcdLCyfuV@QSn($pkD zu52MD24klM#5$7a7abpJYR}yOaMvB#aG5&a-~$)5!vsE0>bu3bbxdtPT)z(4auFio zMFCGcU~>=zVx;jXm156MP5I>IVSQ?d%Wr^ON1d)Ge`&wt22VfW6HJB#!q$5?0Wcv- z&fM-~mAz&Y({Bc&6t07!v+8Jry@O*^MVlRRx>1K}Z5#x-{ZuWP?;5hff#U@q%2)!UalLiZQYA=D-Z*yxUvV5|mUX*cqQ#bxx!gYL6x9Y2BdQY3o4Crzgd3ZN>)yE)5!(h0Oj6b34 zauVo+WD79*GZ9(yluq&3J*N^GNWtozw)}lj9kDkidcb`llfR}>;Cy~O3Z};}+(=TetKaS5K4-TbX1FGh+klKp}c(2Wm z!6b#vHd_?Ku7z=|N9w&Gc0mn=M}if*n`3xtL2^nTOe%Ds+(FBL$`HOdohQq={PQBCE*Nb0$c75$Md8rWRtzt6UX15ZY8zV|v(FIA=mar>keOJyH4_%J%+ z3J97z6Gf=}iS`uw+;DJa{->xl;0_3&U3erUWK;|-S5CYD@6xM6RG2=3c$SuJ`D*T$ z7yu3byO^{)yaSRUKy`96`s152JI4V?fH*^^HS!{^d1xv$31sG&kZ}?ty?Dy}E#yS{ z1{v%um~|b8Y}m0qd{J|8TOy6QHj-ToybEB>&dx2ZdMN->J`l?$0mV3k{1vEi)y!;@ zp}T>a)V5py{`azgfklTn`KiV?v@u63JTZ<=7oZ8GGo>%Xl`Q)h z@jk;~-6Qkg07_F2oq@sOPwO9d&l+_nk_rW7YD~MwB)B&H)j_fA<|q5UJ(BHPiIkh0 zZ8{TxBF|2UW?Tmb?PRg(?tAJ(z=J9Zy&3h?gS^z-x!w zi%jI5hx-#gxJ8%0B!<<3Xo+6K}Lx6b$k>f`Ty#!CynZVjT$844Dn21ck5T5#|_JYY$2==xKsea7nJy%R(9xyY5 zmcI`_^G~fKx}L9_E8*9M1||0A*I_)70K}2R#JDvxXT7{lnD*gGhLEX0JnZNoe|94cJ{zn9RC*u8XMNg!B8?-xuYF9 zoPENJ*6Nj3{URfM_zP0Y2eL4?mwK5gF+tiL!+ViIbJ5$BM#(}9GiveVJjA)qwsdem zvakz)-Td3_9Lf2Gx8|#k>iqqv3t?_dGvOBacQk8*sOooC{jdqsF!7>)SKXa4|5aV) zkP=Jv;!+INZ`qe`AecpkBevK!=i0ya_9oQQ0lj`1i=1(HiMC+)!1#VPS)R{LI#c^$Af{lfdwe(Vz6cZq6lN{&n8_o!!!p_fB zaRIpz3hNpQBp~9Dg#8qI6)wBR)>aJ&o`Nx0GE0-mt}ht}ehX3~QL>vr8~L znvL$YB8FT@39c>rgbT`+z^AG%Ja0oEaJQq&M42p5RZ)tGnDVh4cBp78^^i7mGs8y9 zhj6hVh-k$l92*?;C>o1UzHhU=ndTggh{O<3`gQ7nCd5&G#Zak8c}mUR!aH^i@k9ET%P|L$PyV?Z`?*1jw-Gz^X_5vS0cJT%)YC2k^t zNwbp~Wk=W!p&#)ucT6nos5DDvmYz>Z;+LFN2}~k;{K&_@-$<&au5x4pfs^a0^7ng3 z(a`me~|B8CsFE2a5iGb#*pVuK8H$OG&+5hPSa|Wlfw$I>c_zF zhPRun1E$Mo0sN4~ndKn<{_+YfD?tVg>MKg+%_+aLY+$Mk;nWwIw`1T7w8}GkYERmk zY^tOfB9)X&@;oX~9*Zcg=%}3~h+_%i4{xVQO7BvIwLi1l)VJ46LweVhi=P83ao(=f zlw7H9aZUa~CfT_F#j&zMN;G&UcZXAn+H^}yiW&C$`16!Z9D@}st#Ir2XEK^L}Ac%mYrr&P$ zqkoc?sh!huI`!Un-@i;ZHtuw#UpF$>tv#&b&*oUB9<;G39#mM;|HP0e!`a%3U0$+6 zhlVxOmy{QcKHO2y2>&(piTq}7)N4ac4FHxSmq*GO>X-rdf!yxUjU|aLGwF-O?*<1u zoB=a^c7pN4Kq43xLdt0WIIZM{?F8B8ZZt~y1xSYJqAkWm@TyoN_Z-4YGe%q9e(kEm z*@`?SLvfBwI6YBU_iPUU|HrXJH4XPS&H8EH;TbsPbLdc$lrDZf?(LN?jN-pM;>0)1f{kndWU3p-3K(x{#)H^=D7(1KFxsOx(E-?))g57($AhUHi!vsBF>fHd)@;*omUn^i*X{LlJGr~fMlk=IsbcaL7mj|xOJT781HO?z~yl%^ChYz94)Jy$*b9)8(R}Jigjgs&K z5e_CYg?VT1arG7#dj_oo%G6TRp8MH~s_-SE>&In8@ex{K5RQ4O)U{LHOlhmay21L? z0mluO+rpVjA>nsr#@-nyc_Nf-2Y>4r0kd!FuTV5z)@4}ot^X91z)(3@ZNvPYk*3BHZ5Gi% z^L682aq`Ht{8wQ6fjIew2nX^OY0N(t(Q{qiuxNv#B+O3~_3Ra`#ldeu9qjrG{PwzR zFy()DJdsO=KtDU5E(uODIAcrf4<9#R8Pca}ssK-s zQzW?oICAmua0r1Mk0Kea(Ws`l6aw~7ioUi;2Va3VWw1-=U1k+{OuBr{1auw-FuXO$ zU`m3g%PF#qPhS$RGLSF@Q8ze$z$mTiuoYMVsP09#y-7nO-Y7m^ThYvRZrqb$?Q z%M=(m!99-48D-;{2#Hfd2n2z4M?C|SOxgM62;V;fB}Cd+t3I6_xt8%&ZW!fx7Z#;TzTG}Zb9PA)SqeKz;?(Q)E+XVu3AecA1 z$wE=_@b+}C_$hUStXeordiBW2$afoug{7A0|B}96#nK8^u7TnOr4P~WP+d!8MG-^1 zOJZq~Tc?`0z+6?c_;5hIcsM@(sdGGHi%VY^K^KsiGWC4CqI=-zj_KqJrZYZxc;#|o z8)hPLoe*jul4>S(w^S@l@<)S>H(;~Y)ZxdseJQfOU7^*Qz=OmD$wg>)$3bQVtn6)&;gmkQJa#Mxe{DX@MrOjE} zu@k9_OP}L6PVZQF5XU{f>(BiE6D3K z0nhj5CT-O^pl`%as!sW$JC6REI|Mt@Sm*{<8&BBULIWcNx1vn;%cEt{&<#SdJ2@p* z1@K>m&hZZ=kVU?M47R3-L4>-tww~k=*yx`J#;I4A4@NvS3mQapXz$1|nwX{On@29{ zL8gZGb4AyoA`0GP{_%Yae3qNIR#5;j)IjA3I20=Q!RKg`y#2qX)Tg$U(W_le*hFY1 z8(gLDAt#Do5z!Q8#}~|_o{Gu0OUl`&uZJmiI-Rkh5^6rjC574@s<5>6N=BlO%_5hO z^ISSwT6^byxU?J>e~^G91vB_}lFb=Zc9YZT^GKxADWPH}Rd-+d=(E4N$-s|84k9Cv zXwbojMtOBgE$~}NuO&!&fY1F1pXED@XJDJ-`7>4`p?+e1zR_RIdyR8U9r0RKz8gQEOg5M6}D4+{|rKMG$(w$^toSh^T zEDFk#HHapM7~ttI+ExiK+5H6e7aUHe%KJ11+|le8UM zkU-6=6Qs!zV7bb~!tMH+2@i~NC<8X13+v9@P-GeAu2hff5C6oVLleR{xSxm~|3Wd} zqm@@w6x+{WSpAq5!>Mf`e+4*`zdy31HX8CG7rM3QM@o=6W`KY`1OXt#k>%?+?R9Ej zy~7mo6R-mv3BE8nJGZhs?66u-bE-#Du^I0sdS^Xil~INARQtg-6bVRhlofh zEg&Eu(%m2+BDs)GDFNw*b3Ze)_w4=d{l4cq*Wr(uxvo*xTF?5$9pC%&1u&>Iwdez& z&Luj4=nr!EG`kst#-_-Zv9Vdg`PO41(J?VyP_+(^XzFS31)w`}I;AgV6dxt1;6OpM zdb@6S$d$Vb^h%dF^;+v27%4=(NVL88f-(F8qF=pommK?O3Ff+u$o~;GB)JyW*#sj+df&xRi6)r;Ez3~>*<5x8_~ zpJ!yeS{e{vYg26tbRg$?El0Jk|P*KD;fn*SzenH25=d4%J0$GCAL>&i!2ppIn69BsuiGf z6uVgKLisj1E>07`(xKoL%M1J^aFkQEJY50KLw~+5euj$qr>=(bWD6+P7=Vfr=0(k*$1_G^h7}&imzGD&)N!>{-d4l~9mdEkDnd8n^sMuKW%jH9Xr$}U077o-kXOFYjjBISWU)|Z<%6~fX zA0&qR^Nc(H$@0E`Jd*NAOKW;*DXlA(*Y1P8`qs)OCNN)gsx=Rs%>yEq2e}0`!H_uuuXcsZGFXb+qQ~1HhUU-nnmR>;aM{b=*rH>`x4YMFUs#M!Ct6P!O!SwIVqm9sSoD2AV&JTlrdEsxN zGc<4>d91c2du6osjXf%~1oQWSIEfi!K+?t?56puxMp*dUQW(RPS5iWO{{2zKQ$mDX^H@SE+jcaiQ`8qYt@ zm3~UXmEqDSr@lv#nSnHTSx&i^QhLM;zE*B*geWe}Uee-?|L?fzx)kB@d|eUb^@Q^E zyEOE@Ix<3Ip?dvd=h=U9w8bBn>-9ffxjPgdd^>Lt#Kd16x+S_2YzDCmGOwPnXVo52 zYU6+o=nQ`B%c%HvAn27GsszI9{6Y`ohlhD=4<0;lyk;n97HOK424piPLx3SVhN+`YE_uvgWXFMKnr18DHt;X2M$4Ui#CvY zZd^q0%Y1yNZrnY}tRPsunW~;%>8b1|Gk+m%t_O!;yF1OC>>1e@W5t61;snlZ!c3cm z>iM1dhC~tPlql~emCihyuuHBv9>RdrldEY%YF&54 z-w>}}L=vQ{V2#5GpsDK@z2cx)J3Tr1yWZ}Sye0%ffC;--?o0@z=tLekt^uo-1;tLN z(j#mqQRfxI-}SNAsYp8^3qh=o)Z8y!y3`NkN8a?iJnp|bA&PzkVJbp_|BHY@HTWk1 z14f7SjCA0#fe9a=b<@cil-u)mthssoWn) zxQYy;V=U;FXqM}uR+x%qV?r}BOtS6B)!|pb?DI4!NwHO>;TRhVB$pou(Ut0QiDYA& zmpBG@0w+_XV2*rxbu|J8T>!6I_;?WyZqcBS2$ri?Pj5bd5K)=iiUFK**-@eo9G>+< zC*~ht(N6DutLod@p))>=1>&S${PhTHT~S`EgS*8RBWLyX*N%=@FLrxlSY6)szxMIH zBRt-4Ykt*X^!IY%omwT8x|;a7Ab<~~f0um%|Hbq(4ke$@(Gm9cOMBpU1`P7BaUXW}dD`!%8+!Q0roa2$( zyYkEO+&Obdazx`r=B%tJjMt)3NIUEgnD|heK}G5ouNKD|^XLrHGfewSCXx11=x=^= z=JLSP5@&`h;^H)c9^OMRDE$|0;tJR(%_qRE)dwxW6}mM*PXjhI@@N|^0jh12#ywsZ z1trX3G6&EU!?%L4w&75cgTeFR8Ssi>25b%{7G(?t;%w0d0EvU`<#_5qpjToY?i%-H zat{6Y5&v4>cp1D~-?)r>MX!~ed;uav{B=W){N0T=adFZEB&D)E5AWNNoBR_}fuN&h zT5?sguX`O~7gUSUewnB>9w|+>10Wnv$_20+az*b<_b>9llW=(Bi=~H|0<8?_SlR7& zs6QS2KqLk7S;A#BcbSBN1M2&m|E8Nz+A z^H^zizzCP|yOqkrMvx+m#2a7$cJS;`n>6@`!R!*!9RZ>HIJ@yb)bHsBt$w4?w8blg zb9GmZhxZL50~>cAu(Qkg?YI94=J){f6W!ac0ie$Q^kl_7@e^EsoOZcn#;fW9UisIQ zQl=Ut1fBk!Ednk8^?-<4ID9N`NiBK0K?R-yzTYHUWZBA6c@git*DwAG%a!l_4VIX- z_)i*xlG(;yi&xE!3r?@Vi-{SmS-`dJv-7bGz)_`>$7_nGya2im_Q>s(HV=6qvo-G# zYhHjVWca6lLVoF?$p$|4!|n7a_+YT^&v6=d#{4HXxZMsg>0=~N;D-}{Wez>TERv*-IJcyS&flHI3 zlC4>1#}6S4((`PQ)0kAwqYS5Lmw!&;IOH4o$QvHPyk&HI#9@58`z;o0b&D)B)luf#3jU10vB839K)>m zRhuZ6HceuU`hls6$7;i*Owl>e})y!N}`e<*Ohu} z`r?lMQ}dtmMPlS`g!K1f|75w=(OiOK9S0|;DF9v}WKdvRrjL|De*2_n+GJ~SEEM$O zcOZ!zQLHoTrm9qgSrR_eRZJM{DBx#-CBkd0h8*?ykN%Kh<0>Q22_Qri%cF(Py5ODycxdh^24BJ61h+aO1cpc;xK%GOzK0$8fu$4@7pG10Qb;s}BIF8yMUC zhNEVH|CN*X01p}Hl2bu96plJGYHC-oCC zQhoO^(VtH!q7jt$KuQCh{$*Gw{UV=AOP|2VBE%caMRf`7R0WDg;CNoWu|x&V7d<6% zcZo(vqS0!1X1H?xuO=HaH#c#(R+F~P{BH2eOa<5FuJ&Pj=*z3Lp#uSOzO;9MIu98n zuuK*qzki_hp9I{*`?WYLSgkwVjpk%K&;m5qs314F>RHwiWBwzpV*E(qzw;o9NKt}0 z8uK%enMH<8Wk{b5DCJ&GOFWJX^j4l!c&Zt)WPXcry-zaz1^ege9?Q!=bK{^Awlai4 z1&FdQz?&L5^_Urd$Qp1Mz-t~m$l8&Xt;M;P@1A-Ry4U&d9J5qC)af9dBco8@s?06fy>Ka!shS3R! znDoG z3{REK9aET;k##!$$(d7GCE+SfM$f?T3JQ1pMneX`%SuzzLK60sk+S{SBJTaYrd{A2 zotXq3TlMzNMVWAFnawAy1(VOzm;q>IK?Bfs-E}%SL3eisX;jWiI_c>ysOvWJhF@rt z-%s-geY~6}H^RY%(^Jg{VP0&8YXIL|a)zb45qzBx68qmQhg;B~hH)8ihhP*ICWlck zc9D_OppAUL=XpZ@>$1zq(^EYC^Xwb7^t}TF z>4{&;diV3YOOw3}VY@J~PBe~>6G_&IAO|9#1=&O_A|KO0{{MT)!Ukxm>jh6fbZg9d z-dw$p1ZzAk>E*9<5D}0&g5rhWRwwf3mWB@znRD0fmCnBnMu2)@=+&P<%0E6P@{dT) zzfyyLbX)#^{*h@6WM~)g`nm1sY&A9*I|=ml3oLT{Asd|^{x5^vP^KogIbIVVv>CPB za6){r3hIuPx!kU<*{M5*#ct~%-$PJfcUl@hJ^UB0at~5?Yw$B{iLm)o_L#3<*RVmB z8ypB*si_+uHMsvihnCRD;0{A{99=_R?g#cbmTQvQ>8jR-Lpil%2ZFf z^$&-RR$O3s@3R9siDc$4s4m9j=svzBpVBV#T*3Q;KQxwbM=DBgGoe7u5ELA4+)!P> zEA0KLg#AumznQ?-19#f^%kkBR-`>W>$D9twHClcApLAc8>wXNn!fr;U(1JCLN+FPF zWvkVD_8zu=cOsZf76I=Sb5e8G?uoZ}@|n#iQc5?3;W$}-$Df%OeogVw zfxIt_$o_6i7L^r4r+R7w;BwFX+jFCN#EK70o6d7yRMUfVuV7_=qmgxtX9wW*5Sz}_Y>8Ur(`gLUO@V@M0ot? z57Q*RA#Zx`SMLAKRJN|ijqfL|ZbF*zv!C-tTLIi+nLrJK5zPz$9&RlkJ}=dUfVh5F z(vtDowRniIc;LhVXM$pMvN%=eXhB_l@5V{ZI=oQkyo0zIy0m0`3miXl%Dl<0E_YYq zC|}sUj%*V0f8@`V&Wmc(?|JKCQX7HAwx_x2)Y$>r=#YfFAOWbfs#H(ras>Ad_P{L1 z*f9a2{*LWt_QA@0;=O$tuE<}*+vIm6s0SX@zmxR#Mb6rJhwhD~_vbzgJoP%56Gmvn ziV{i6OVZ)IZe0JXCni=3aNYm#J{?PT{mc7QF0T(_F35h30MSEmSZS;gP$G zBQ{TNphO)Snx#AaOC>5caC{C}eHj1hp4l5D>XNj`FTs|&nzd@hlL8JhuV*Sa*8Dnl3|>9#KFHQ_OSA75HGgYtrw(3WB~H_FEJ-H8(2c zJt4L6imcIGtN;qHp%#DUcVkk8?kLKB{K7IqesR3CHG{+g=HOoXQk0{|iqT;ML&u_Ue_* zh4ThSOVU37YZ02T2mr{y{!iV-7YChhQ$vF<2KRP4l*-^b)%++UZ zrE6ZFQHGKe;)?~wx{&kuqgfO=oSs)Nl*56Or+D9ZG_KvPTsZS+>G$KZd?2M_Y-l)k zA8ha=uvo}ZlgZtqpjO`^7h8gMGMk7<_TCR4DO05_Kf&8Az%)5_(@3IiMZS3de-wDJ zAj!J3kZKFi0VFeo6bTt#eF8SsG>Kl|8 zQvYK6*{7h`J-M~W@5p7SQYJSa0l-$}C0PkiH?FRK;Y`Q<$(feCv9z+%0-EV^2UhP} zoPYA6(;vz@FAv6l{AdaN3c~umSpB(zx*hXwI#4&iG)xE2_u4yv-gl}Uciq&D-dw*` zFpo(1p}*m^KZg*Os+38K!Ab~8yuT&N<{v^Q<;yp&L$(e?`F6NAg4_{MK?fS)U2qt& zK-)Y97zZDi`oR?xS+&c;1@)Up#jcmJa3T$U?cF~rteNG!0kzf+pkPplK6(EwXAT6T z{9r?o_-W$fSc>=#;3&grB!i&{zdm(0*-lX6CaWdb1y{JaWxV;fMo2nI6Nh$f0tElK z1m)fm5Kux<*y#6nWkeMT7u!M@BY^7wtQpp&T{~a3<~7;kuKtCx?J;-F zLYtg3^4dCNYlT4bNBlnl>H*%}MM9FTD%olXJtc{q&oH;g44znvu!S%}RcHzz^f}_) zfOp9YA2`v@iGeKvP@jnH2@%~5S$MATVBu`LMqBF13V7{1;B^*vebLDX0HI(Q6-Il= z2Njh$Fw(#TNH@V7 z_Ilf5cz4Y_tseDK;C%Vk@dRMI1k_@;=1p&zgI(lDys;tl*d4FrnVr!fGw&dE01VsmHvR4C0yer)Jm2KYI{NXTLce?2N=@S^mQr)}ND7aGC zW6VlI=BdJ(*t=>6acZ;QtG~=7d}u&n1tj~G-2z3g7ne8_KxJ_aILwGo9#k7&CO8KI z91laY!krVme>2>j6ro$qc4-ZeoqarhG;tB!Q4Q=R5`>Atjb>sUu)WQ#iN+hb zq~S}%FbsmcgQe`l+1Xdiv?04$X$Tq-<~6LDC#JX2EEqh{*Z~uihSPC(LJe{ac$hh3 zw-9tO;`2eyFWCx~gUMbed7AHB)-pHu$%{4)?-;%MR>7M53tYi7qoa7Sp>7DH00_Q= z-yIuX5s{fkN<2%Z91w>+RX+6C)EqBX6`0bw=pvC3!Hd@^5Pp&7&#x!9=J#Ni!<-9q zK`oyzO#Aa4`ze|YeZqiQmC=G+@-8vL_`%Typ#QevVM^GIz}{fs zNl}hR&e=GU?&o*pl;`JoO?T!|U*oG@(ug`Eg8A?qmoF1*!((}TzyooN;MS{r4jw^` zJv~v^)@n1xo|^5TH_gAN(Aw0lvmq|&Pj_@`52jV2Jb-I98<`Qq94s6iVpX;Q{qc>B zy&hzezBWlT8KRL)fT4ihx&thS>E#=n=HT-6w-Ks<70K6iQsrM`WprRQ?Y9^k2=e;c zqa_P5@!WNziE$JGolwv|{u`eC?b9B>=fV_4VgTWiML~~neptm_tc+5V`^MChkt?DU zlGXy?{n)o3ucQWjC~#Fc+Br<#a?SJzHm4&H_lLm^Fn20xQ>$P**m3hkMw||#$7DxI zy&4)bGRFVfB|=sw5#TVi*}5DJZUKKb_qwdCtdI#MINX8l!ti%53tB&aTiu$^pj{#n z{u6D#amlM^t`3reM3&tkW!>`1qZvw?>N=(syNM)5Vtf#YHoxgwF4jLoIt%FuXcrqq_A6Ga}W|94%v4sa0vm7Y+z;80-bTCTtUZQ z-y_)6N8xct4#y;$erx5Kt64;l+f~DLwl{?Ty?gbb7DVQcYMyXn9n=MZ^We9N|D_!h zsR-=O5Ganm-fKT+{6_@xKg?sr)iO)DO2lMZ9RwwO!TAFy<`j@G+Jd<%U~Bwo@Y)c2 z0l8=ASBH8SH9wotDmtz0c<-!=a4j81V(l%5ydhCCS9(pti>k7i`YGg~YT9*KwQM3& z&UnhbRMpf)fqcoa^uZnJLLS~l>+6%(S9fvj?QQSmRwqT`L{=TP&L5t5)%w=%j~<3r z=zICyjf%R;!O@)l<=)n>9zq!z8R#wrDBQNlV|J9?T0>L8kQ0xP(5=7}sknp$gQM~u z9znsIZA3TEhqeTThx!4!GgCgv$7;mI=+|EGbt>xT*1A(!v50+EE^`uQBUJwG@adn1E>0kVYqmQdsj5I z8JhAut_!%;g7zF<2PRkaeoc@UfGLQMC^!udm7B8zx&PzFr-3wvGjM*cFR^nQHnno1 zP^pvc-iIY)7R8DB2S>HbJvCHf;@b7~^-}Y^HcP8#`T14nghLw(ChF^bvB2MBV7eQf z-_$mu&CF%Bd!2zHEhk5VvE9Q}pdss!i~e@bac&`D7}K7i6lySFG<*3asH{Fjya=4| zsc^%BCM-3IXC|Ha)8ij_;44FI`t2+u7Zda@!j_K=V+-lqOOCzL*7PCq)bO`WMH1vel2-u z%hSW!&oGBZe;&7ALQ`;YJ?MUxiivGMaOIk}%%cq-KlaZ&rEM%-5#?PUTkqIEKG<%k z^*l|nnHWdD#&3zDx2|4n?C-z0SsNcJ?tK(~O;u8Aq%?1R=Zo-q>oQgh@#$N^L-sK0 zeU=0d0o5w2LQMkJyLYQa4XIh=pAkbS81i|d)Ec5VZS9hIy$27tB51`U*wOh{*x1fO z*w+!*jM?I?_M_N@sx^Z41(=IYch~J0EzBr4l2u4N?|w14T60Jt=IC!9f0pnokKs`q z9-W24VY-bdy0PFkSFG{Y&^fkF0rTGSTk~Mpsk^l}P&Df3D+T;G_UiQmsZk)t1rpIU ziO$F{rs!%@OSn^~%Mi4!h+YU%yr(AGp-C0&`(9_vI#?PrUjA&#ksngeX-LasKUOVZG$$eLce~vb2MCb>ho>F#b`bHaZSE4*LmFU!*yh! z*dhX&GPF}utf;&{9$5iH9nY3ijQZrni*2OWDUy+aAt%H=hKU>x`^(Eq^z8bXP8FDL z7r5>$pHSK=BVzw_M9A$f3q)j{IrIw(S_;u*2(XLy4>Jw@dENaNcJ15UjE=42 zQ|e|g731+3%z_Akm-lAp8;QN$?@{)_s28^|;3<=ml7g=+<=seH4L4g#6!i^zf)zi^ zfkE`(rJ_=%3NSLBwZFlTo;G#l+V9XjR`q**gRRVR{u|Awjj|HBL5s&d-6kim#Kpzc zcXk$Ce^5yAx$#!aG1tn2X5C+7yTrApJM1x0ugD^ApGGY+hlPjt^rCT~B%+#~s@uDp zcdQ*F*Q|c0DPRz;6mS4n%|1tr8YIlaxX9W$5VLW03d-z!ZhJdl%6_+-7WEY9+mu&S zVxop|&LG+xXyLZ*f5(s8?}`3Q`5+=9PQr-~{OAgy{`gU=h?dTaC$I5PnUA6L{m#yq z$F3bLwP&Ye=s6Nk`wHr0F9|9xu5YeR^_`IRHwGwZtG&75e8hx@kKa?_a2L9i({F9O zrJyVJdX93dLIOfWyR{Rnk9$tMloG3*M@N{DgD5|;zcXQ+XXU*mZfZ#>DhdP4pF z2|m3K=7A@pG#y_&0U-HXig#I-OO8K-KSEO2j>f7B-mB>7sp((u`No=?v6H>{u&Q>C zf=iZ#4VPXAqe4!3G!8}>lae?M%*}m4!c{K8#hA8O**j0GOVg$jO|;@?yZ4(H$lGW7 zvJ{km>;OzrNlD2km{fo@h>(DQXSyqO2*ko2O`LG(CT%(FZ(4l&*sb=aXMQ&^-*~*T zMDpaog8SDm%btz`W&%ROsnwq$RKhk>Kl4T?)(354b{xlMf`Wo_()41quPJCR#*{Qw zrQ}72f8Od?kcxcz<7YqDxF&x!K0$NNX?qbLZ}ZO!W4wGRkC?UdB~+_i*$)k8f~+<7 zv^)r`R*00()Lzo1f@4;?F^iprfx%tE&CQ(w{0zO<#)z}kLWYK}Qy=ZxR*2Nc@E)vO zDEFE!;5}T2N!7jp^zuM)-HuzeU#rwX)igAO>o-mj1Z=PwqakJ{CMJXFS@XU5WN+x|5@rh!AD-U8jsJMQwpPMj zSEF*ZXTXl5{0mprCM}SjL5LV9>i9^SL4ZWQESr2IFqwd-6jz{~-46h0f ze=F$t?(|iAzeHNik$_7F*DpEQX1(|!r`=@z6$-GNui4G5YP)dZg8H2r%KLg+#E~p< z>u9&fOBCwX=9WHmXR!^A_k*7P$iI~;rQH^Q>$NvacZEiG<)y~ci)5&0wWqfdx4U{o z3pJ_iaY&-h=MO%K8~uTQ2k(rt?x1ebr_9$Yk=6=S>EXt=ze--vGv&Q-p=a~XnBOJu z{e6$?qBo{PyCqs-sURnh0R#^9KF)0Sdpqpy9h<9dei|(gCdP>z-@ZuJ-ZR>S1(w!o z5h_l0&aI<2k`2cy6^3f&6;VEAv8=W44$n|i{~9Q@u(W*7|MT;q1qfCL>NuM~_*VF= z<*B^any03=_KY{3y!w5vH)ccMDVNbFnHtHIR8$RPU4;Cm&rYBR!$+m|NRe$1ratDlx-xTPC!|uRB`QN(YY&oJib)BuV<;P`x?;H5qrK|PL z?Wa=oC;;rPccC5BUkUsAu6#1z6mnb{&2D7om-X--zG(TQ5=-CfnA6@N1B(5GBF>h? zzEU8-eixx`F=z;;6%D<;m`#C%H-A^0&s!m+6T()_@(wC$xUw*G1y{=MS|Ef|*YYj*MWVjG>gcq>B`?%?1F!XqePM)q5regWGWLdjt4L-;idwqIGakU#2#;FL^qSBZn_nOpK@Yz+RR)Ga<;F4$ld(+2Am^?>yDN)2p%HAH&>&P7RO{zWRc8v1wF4gvG9)UzR z_e@S0=B|1}0I;{eZ`ASr91kDgt;khfyQy0g*Mn(NO|47lS7;~QMTf}zrNaK!o(5X(+V8}< z=#OpPl3t;cljo_$oiWdzJsZbk$2J>jvF0IQ5n|K%tSEanF3igVTOv6_M6s_%W@KOe za%DtCcJ?{31i?E?_W59{l4kAK_3+W>xAK++cwQT{ynNtl(HG2S z%Ah?A9e=Pe{*m{| z35`}(Va(3wNdAP81pfPa_(tRe*~{mHf&@YoBA4jEifA z6D&=Ts)SouJ|5wbf+u_beoOx%ozdd-KFA|-mThy$3W&QPDEYmzD``BAc6e=a6Eti* zo6+6t-}ZQ+B>rLgQqt;*>dFi+D?`o%kcy8`S5X&Z+&NYF6;j)ZLyn}n4tonICpPuN zN`Bpxz2si@DKI`2`^P@tkak?kr+dC%#Re{{I2@F;zWDI?SKWn-qh|}w1eN8&0UC_> zUq`7z+7qig6goR%4b3cEu&<9uaa^31m1ir}s}2mS#tdI!GO;*y|0I1?+RHF%qbN$# z-_UBb=SR(j4ZOMQo{|_jPeZz+7f_Zp> zB0dhmZ@YksDC{y){!hFDn7o%O*p7K?J@FlF2C*@3uG2|;6PIs(b!xp}S@o;-E|V#P zu9lC!qU65Tnzq&3#>OEj6G}J)`_dGsyua=(c>WsSBp=M52!ea?nb_v-Vh+KBH}_~N zbRwX!37}F88c}R@@xs>0F8|&H=stPeDFUVYiQ?wo=nvGjw)fyh5~!Olw6Y2|}4JxOEJAm(v3_3!R*v$-u7 zQFqAV3|jSD7(25Zp#=YP=enclS=9He7vh8Os{Are$D}oYBM?{Y8R*=-u)O{;Zfn*mbRcz^=|bA5IW|2Y*>p~vpz9<_tU^I#QB?0-1@E?(-S!iRPML&){QGP zQ>HfIoxIm5N3P{*ICyM5)zfL>)p!_mj86Y_>OMPT6$jxaArI*0nEiz}1fy3<3J z_Q=tQJFYszGgd9BHuyNj4xfE*VdSmfVKSlDX9+ZAlxu$Sb9%PMwdSyd?~peBO?cq7 zM+*y20vTP5dcF=)Go&hA7KIDDXEV*CjB6qPC&ouL@9Kk&PB8C^U0&JA-dT?$z0gzS ze}6;d_fpS{V8d!3v-Qh+Oo~rrseb(`T{9Dm?QB z6nfiEt&Qm685KvoQk`n;{EOitF=M+;WyZgsmyt${2H)`@7k%gXGKJa+%#Z*2>8^uB zl3V5K_xgL!pSdXyQ01l-5tH!ddpdk14*pOo6z#AxQ#3*6Nfid_UVFPAY}sZS)0gb*{XYVv=zY>(T(fS?S|-zXbSHX~ zSbL5%n1WQ)%}CzBfkVWp=2=t7wDP|C*zRUjJH>_X1QBmq7<=~Vm4Jow$4}b99ZfVw ze`d7H%=(Ovl>E}{BXsKIS3m3^Y)qZ72l=H!>3t68FC{)NTzT8n+K+ol<}AfT>J1}4 zds{)45)~`!>vX>KrrNH8pRrNW8S|5}@^~yRJGau$w(^`0o$MRJBf7-W;WRArqy9$+ z0n1q<0*jTi#=*F(Yc0!R-MLZBGG_$*661Mi@y^)e;i;2MWn}#P+0@f}@kszl+`ZHWW!B!~X-T&1`Ab2RR6z-}^b{zJJxR~~ zPM|3F41~cuYq5blz3%q#@l1iJkTe+>f+c_+5=2xPEH6_ZU^*WP0<22Dz z5~|qhHfaxGAy5R}p`cmtb%-R1r)T}g%E<{_vU>HbJ)26Cxt1_h$wRj6!#&^XTW-36 za)K%89*kki6|93eH9y{arG`3I1SRVd}06D>CJ6;%CGWt zuFhkfqH0BwL&S}y;&WpqQ6Llu-#sd-Yn<@djeV;Jsq${7C+;2Q+eE zfe}*f?lQW8D#l3zGg)g+{F^h}pG-y~*lY?cMIpPX`>OAMA-#3FOx*0D}r zzC$=XkevMS2qxt7u&vK1;)5?JV;JRrX2;g4=hitrxBWYgYvW|7PX;|IaS3q-jiSae z$$)6bK|vMw`P*Ffvq*il#3J1?{g+5ajqu8CCZ^Zkwp*|7lfE8Nn_n8R-7DGhl~Oti zFDGR^ZCa~3m6nyogAmcM(ccb<{I(Vb$*^#UlpgH{(#@wUGm1hm?$x;l$#+*pjM}Vrbo`|T2)UE*n)Qm`+S`ifR+ElnS z?73n}J(tf0Ni*clN6E7^!E4y<{JH@NdsEi{h(n2<>&s2Zw$acf`wUnN&ODID2AVUE z>vszn&2)%4OwTl1PA17Hcnc;U^bC2b81pFvS$(rtYy?WgTvpV}<2^pV=Rmhs`U!Pws$mcAEk2vu91kD9Oj8rm0q!<9r_SZ;rMr`q<4 zyv3+S!3*Q5G?TeO2N#zn;t9{ZofEAAi>$O_UAaEVxYW%gt;-qRQP9NV(6690G>ioS z$qSgGNU?f5D6*@{L~@fWmho4wiRExLp`Kn++}7$IURzqX#M96y*i?^NnQp-Cq)Y}v zU{ccY%b`&Bc}Gap04=-qoeyQ-%M`g2m$`J0LObRqPe(%;+aeG+k`@}j#`eO>2o5F+ z>cf$MEt~RNTx%E`BQxzzx(&TBOstV9aDQcMCyJL3u$V%xZ*+32nbCot`T7rXhn_qO zQ;xkk4=DOgF7%LsB*>_QseKJr#}9|P)_M*7hQq$Bln)dVan?I|Q^Px+-Y-V-r zaPOe=2Q=2ChBDZ%+s|ItI*{1EEqjOa(1{r;QHA#VB+aZ$li;-$keka7HI>{v{ioXP zP7bFhSVy(qAbCX)a*1&k$9`~0*h z)2$LR8!W!&HF1p78c9Wn8mh1$)}z}C;k%gxbr?m-WWRc!(mm_(Sa?ItF!^G)G3#wc zcM+AU^ntfjSkHqj^vzL-upS+*u`wg$vBx;;9X_#aiS#%r{HxGAOHpkj>X@6ezajB% z{TkBrDC|n8b?)eO=2V{-FOmM?@VIN3@Fm%oPnPYNAa19^zr@w@T;IzlFE9Mtv6#aS z^&1XdBGm7VjgM+#2fxzuDWHXsq`qA_iia!#CthLUyQf~BJkJ&kEXO|+s$>`6S_Z3v zsayKce60*7jc_GDC zE7D#{yXkk<)nZS{wbU%4>uuShj^tM39j2`Jjok$sj9J}*dd+a6&dkKq)%W)o_pW@9 z%Xe8DB=guk_A4&ldb(KfIbWwD0Q#HtfM|ov51z2ksV7fKFI1eqw0GSp%hxrqv?LJL z&?q)viTwSWs$*Z>yo&2}tu;Utc~0lodiuu*TU%u)j#@3gw70i3-HjR;SIi*$g7=#7 z3q}{33k3%)O9Ti=K%x)TdVkQ-Rqq)|&Q*ei{qxWwQkH7|OCa8Rd5`J0&0OGOU*^^B zn+k;`<}<|5R)Fph0m^n;>v}JLZ%>s*B-?ILe!1L8-6=h+zR8IWMPm;^&z091D_S|< zJaR;!W_2CDIhkrff;Vq2nN3KX*GuBGg>~beHv#kLzlIAhk5xHkI}%jx6z1@fT(}_K z75B%jkR#)HxN}QSPa=P!{88%m!#ur6A7?8ly=Dx~Sx*G}ynTz$L~;%?zVkFRe)aXv zb8jq$`)S}c;G9aZ*U}s^DYlx?PgU+!F{f?h(rv{*K76Y41ij|LW4aoBA!`0xVjJ|e zz^Enbosi+Qnht)Qp7q$fcRHihZum9*ObhP2OTL{N`D}LsyAzJdzP%%CEt@z-t}+Jj z!vIzp>}JMsPgBbZt-k094?5Nd?^L{MAw#l>j90Q2jZ*H>{OovxrB<|7$1N7QL{ppY z@_ILrgi#1yiq4d8dl71wad0;T3}X$OKiI`xcM~C^717D}+&oM8J=ODk>Do|+OPz*) zZYVwxjYJ3x?3QeviaTPHvMRQ{Wo(^XwyC}AD9`bROWW6^(_8hC_1J-eX7zhlrlt46 zYxLJ=#WprKuf%rRt&MvEKJHpbD(IB>)Gq>!^@w`yk-b{wiMGn7b6gNZ5qF;Y>jz`*@<2CLET{&dPj285tMv|foWI^18XlVTBbIC> zmh95l$5MbvYv3FphY^{@!{ZR7x5{uS-D5W&k%K}%&}TYo5SoPF^R(^6t)NLUIq49` zx9uHwI41@^=h^Q>afoSTp!B9$^t577Vt=Zt;AKoq#?kG&CNqyd*_Sy-)0Oa&oTFrDm`K<4Kd#_}9&-Lf&r)hKxTnGNCsi}I$YDQ3B zgyLZ>a$f3+3N`ii-kK@N)iE71cz1&|47#Hk-`v^-7vImkLtoN{Sz|apoT2x8!DTH~ ze?r22ce5@qV)Dg_XQ6c$9>L`!kxQH>WU!A#X1F-7U&nSX6+)_#mBa^nqGYL@URLN= zwY&XLz>LJ;uJIAfebY2vd+w*#4PtN-)BN2T3B5srQmcuKo!#3{p0IrVO8G&)$jl%D z+|B3aJ4PAV?7as|A`hC8MfcI@k43j6o)^Q&ru;DN!-soo_7_#MFD?sPHfq}t`9am? z+guZuuFTuPJQV88nKLhCGsQ@m#Fgtfu&uMle&AtzB zZo|IM-Er-#V)rE;9-eyGut7oPp=f=NlE$_V(oighn0NZvT=n;1l4Mj=)FbP7a{UE- zNlAUU6;r^A{@#NJH(_WJfDv{QTMR7n6oD)XlufX-#rwrW{>scnAH16()AsOvKno z6%7rq(B6?rGmW%{67X|~2P`SIW==eh?-snNwU+Jt@}(K(CU~C>83akE*>9$&8=rs1 z1t)PtU}zCPr2x-u2s)q%vbDsj;0U6T^#wP0lQEKfD8>AiUi2&+*H=liAeQLA{8oy- zei=)@AtJGE=0#iI_R4&%r+Y&|1~+sQdy4J~msgh43v50ARxXE6bfaLy$5>&j#<_zf zQZAB+TTc6EIGj>Zy>cE`tu{gbjTF6<#p%Ox=M|AOVCP z(E=cH^1H|MJY3=SoIv5;eFle#6Re=nPz@Gbw9QZ$MB=<4q?rqPtP9OdUeDpj*k?;` z_hni`#+EfWCr5Ih@6nZ4w2@+H!L+hwpICiqooAR_0lLqnRrYb1p27%pfVnE`RBna- zWWTqm?e^2_@bGxo-d4EoT_sV?^zvcud`QpFPm1Y_8mn|_UF2_RZ-#3JChl0F+U@_h zRt6fBaqB%eK_S7w({59ba5Fi0&$kZG9=q5gwCQdnqogDq)RE@06H%qPc0+DlVcvyCA=ntWc)(@}J&!uXE1n5gH<`*uwK;WlaFoUxIK{YHI zNqV)|-t|}|7fy`IwQQ671Wa=s+V`1AN_JWCH#ZnR^Yih=Koj_5$ar$e|G58_#6fKf zad0XhCwP6ex3}BcIp-*8;}Fq44-P&D$dPBl#QA39W{-C z0eyoX4slE$=m-q=t~YR!1b;8JvhaVQ+X#ovh4U#|*0++6h5ki~vHm(A1L>}VS~S=K z-AKo(PVg_SbxOK#rbb53ORcnLJ|r|xM=`d2m$<4l<0n7=^+2APR|uNlW{b;y@L+B; zL`iX6y8Te|gU~oh5@!PYDgAE{PS{S*dLu2%EyJsAuLrS@_&wWF8CD43$vkGMU^={ReD@e^%E1ssj1|=prZJwS46AIQkT2YMrzfeCoHvI0RaNu#fujWrv29` z(N;s>wQi)9e$3g5&bQbdT8{?tnlz|Nc&GCWk-!HIZwwTuMUeeOL@V~O(DT@c-Mc&6 zOcFfNrSG;uYVgo>lo|XT;Q&5IPJXS6yJs*qugy-2sjP8*~hfUJGd}Qgaw^U7#3jx1+y)PQ@$o` zmhi2Q!e>rPGq#0T>*y$CnY`@j00|Be_4C5QLRk-!U5o!y($L#B&@fI5U&57`pI7+! z@PMl@SXuqIcwq5|7b!?+kYQ%%ZXoO!hymo5MW9pYBhAnNDjD_~{S0W0|F4~KkXo?7 z)Q7*nzcf^9;u7N7H2KSGIgP?J4HZZDTCP5&bB z_Lf8{BoJPF>F8*!^~3?egJyrVDyKK#% ztCA3|WM4RcAZNc4BMYZ88x6qDsok#-L^3c&MMsBnYP(3W#UNLpdkZ1T+`Q$!gPfdP zP2?4&3X7~?H8R>qmH8PY!>ifqhL)E8T#2GaaMj*Z?1s?sJPc)%!*Q8yKSEX^X))54 z5C)Z*5Bch+yJC=_2PN?mQfbY=Ee!p28d%#X7~q~-p7Dbk&yvnA%$i<*HcJ6ZeRB0@ zKs>+1RpF6hSz7?(bIjxy(!!&{!x%xN`7X zexV23R5E^v69ST8>>7`4sk+*lm`&}Hjixl_y2i1wyitwm?J*k=_kBHl!Ucri;Ost0 zY2_&DE^#p)_pN(#xUQiKvLRbLlFhp1}cmd-BLaIZ@cFee~4HT{~n zc^T%5{Kq0mbH??O_Uu>5p(<9eWk(}qdHxdj3E9keJT~Y_Tpjk#bLbSyFN;8vy#bn+ z)0VclBF?zIl^-DxxJ&n-Ek|&fXXQ8&5iCtL1%Uh}XwXTfP8>72`#@!;LT#8^(!1b@ zF{Ekv!QG1T?}^pyH|j@&(ryV}-}Q**4KruT+tPgX4Ra z3-LP%&r7JuU$dH1uBepTf%Hdw-WN7DLp!nJLEzTiO|A;)3olYB-P5n9`&IVnu6X_i zL~MtgsE|Z|p+nd1nhjo@_{Q$7GY+LEM5qv}?;Sd-a}B8Rei1Yy8rjLHF7)cRiQW3% zMv@i3q@%*+F5$q*8O0}4FUSu-9zh1hR;0!>RBB8vk+6RmGFiik_q+jwGz8q;m*B>X z2~kM)DlProY?C{PY;m|@qS_x5$moG!$s-%sjhpxz29PWAl#|j28 zfL6hui&*f<);$jWLP{i=1;fR?2^`g)-|J=ksFmHI%lwi}P%`OQY>P;gLh4D@zedO^ zC6|Zq>i$um$i?2-*~z#Yjqg~^eXO-pQ!9B(aP0*OJLC#}&V1CIw3^kwx3;Se){{4l z+Y~{Ka}I|5bC#8!szG&vL$mneOl#OJDJju=Oh&jbZUwbakF0-$0H|q;_=&+lhY4G| zj3;R5rbgDhTdND^>p}B|dYOa#dc$78()_LAlv!sZN0!dO~jlZ+fL zUT+JF9B*5XJ~ixpZ4THo2J7)kU9_Px*@xUS`WOCg2Gd8kL(&!=Y8b1esfnvWZko3| z7`vP)vM|!EE}rDH@+dMHe(lqCN7*fDtwN~riFYljR*?PK zH#5cP%tLBB)W`zCf)q0OU3IAKEAC`67zDCy7zp)ZsdOUKlLM&3$3yggSNo-GZE;tnM_WEK<6j{;C)lc{du$J3 z@{!ho6#e;y0dnR;E|OpvJ_v{Mq`|^VVS19_oQ%3NO5u#qa2Rp<)d-5EsJHAd{QSiA zsP^#Su){>X!#~#o84u%?D;J!dks7H5fKdE~jloQV$9sz8oi<}5G&xAqNN?4vS6(v$sp z{g~~a-}b2`#`V;Po&UX~gHl=@SJ^qZx(-htY=Xwtdqa;g7F<5EGr$lbBGyR^!i{^Y zZSXq<{T;dkD6*;~!4~b4m8U2 z03g93Itqo;%J*(jCN5!3(2LO6hJk@IaK_IKPt?qFi@E;(f0%pgsH(d4e{>5d2#AD) zB8`ZEl!AnS(kY;nbOneX|YbH4X?#~pW! zyZ$(5@b2y2Yp*reoX`B!g9W!ZO8Q}?oe*SysHArkR_}jhuKHRGYys1Nzb0#w{}sW8 zH9h&g0=_&tK)_oXDpx(~){=Za&cBJ_abCDu{p~9zi~8CX*vxH(R{6G;fxH!V|F|L| zB9#Z-I9w&Ci50(&3$u_<5=rcT|3&k#0Xoi@%^LPSsk}P%USn~>(Ms0 zHXL&y-jI2tP;BA3Tz(26L|~ixv}dcTG(DxWpnm{xRHS(dH0!17N|Fl_E)o8i>??yoEYdX9Vsjqr*-&C4wR4O*ns7~t44J zVr)P7mmuKHp&bYpv>2%j1VaSEy8w`p;#nhTWo7kl{)#bkpZ3W>(T86sJ3cQ?g)}!a zMmhIYo!;wqfSI(cLa#>!*<;_d#P!X{xODF7eJ|i}9)afnQ>Am9O&QRLSJ_PyRpSi5 z=ch=)rd0Bl)T4w16RJg5%P#TZf_~Lo7ObEnMg!L}Yy*m13Sb|RUwE&zgrdRRnYw$fJni|*&Uj76Hkl5Xzt0(V zR1#180CL9=sSdOlT)$N5>NyiSm>rM;5dv8e7Z2n&7{JyJKTs4|doeJI8ko_wAA(Z| zN{zMk_V4pco&aq2_Fi2Y_C!J6+1jN@vXJ1Q#)#B5jt7(cOsTBWPnR*Z%uV=>XWe*D z=V*M>`~2=0D$(9kruiP{!TNwU=q)2TXMaaSv&&-li!(VW{Yu^^6nAlN)lvTHX zDG#6Q6W%q8vAQVc0qFA<3X<21U=>wIS^M}3q{tLGQ5Cu}T7iZFG^%d$LR*2}sHF>) zOrn$MIxvJ!4R1*=j%HzgVbcV17>$gK4BXD7MyJ|)8!tvl7l2v%1zhYtEu1af< zJyb4p^#Q48f9`NVr%-$#vufU@Xk|ys3|#xB^5iDV?)Q#X;3O})KXI%;E!cHuB`Y|) z-(Fxbd2#3FpvUrX8YyggiQ1l>iLUK-IL&XrP}De1YCgPYT2)=t<*+ipN5KUI%v^UF z9jjJZ5ZGsv^h8W$-7N{>Cxi(d9gqVcO}4i2*vb4ea}?hf801lk@Sr$4y?Dh+01~pW z9Y0zc8EkJ9+1Zb{Rk6bc_bQB>-pKWsj30q`8ziJBgNKi(NM7Pf*lOSepEA)N7!?*3 zRazx(#qJVhgyvuk_GaJt-AS~{?S#E$RV)T&qH_~ zO~Ms9QRDsDc6NPjg06V@ad41$S|~-n$;@rHuPwOeg{-k@48;RhzD-$ft@NRrs^r|* z|LU#w#sM9b_?xx}GXQf67Kr*GA9%5<1_)QsmkNVw7))m{g@MJxLT|&BN*C}6gwg}M z!^{Tk=Ne(_!}z4-X|dBW-G3z>QpzN*px%9@sF~Ux(5@ZoV@7Opsx@1ypumuPb!|`|zQi?|b z0Ts~BORelHQ6!-vP<0n2FyjwvCQG@1P&|iJMpTtT#Ep&e($4pcXFoS3d;!B59j}x- z@4Ldo#4I_jS+fUwOO{acK?D%&zcV?t5*E|#O+81diM)aDL%oA?ZUPEQndm$e3ZPp! zd2~=ac8#Q*1&h8F&@#vwf|lZ<{GmTIN{V;S{_xA~h*Zlu?wb^QTk{za>A8XKjO(jO zbWTa0I@ua;(BQ6xiaqueyQfstZ=f#RQ0Wbv}fJ#x~PgZDV< zNgQ)Sg6DEWfZ`Op^@bfNSkr6u$bMSL<2x7q782r|0Xtg%nfw6cY@zx~AYsA~^4ccjxudY1^6?{=w}m7$>fOL-#5%l}~W8KNMsf z&4WUO7^X_4h>4*Uy&(@BrJZeM;e>HT&!Z|TbZwVorYe{HtK5m&4{a#H9#`-=nu-Hl zuV%y5GR_~6lHLz-tYKl~bGUDue)hIq)6H?3DY?gXZ8V%&vaLh)?pQ^EVMqgQBNDv_Z?YWoD3OdogiU}}i=c7<0f{S9Tjj2> z4rtu#K7K^S^V^HU(1rsnEywTNxuaRG^x|U~tSqRTkJ@gYAtnZGdSYTC3fLzwrc6{g z+phPV9;fBwJ5k{=o}$SOI~j3tznO?DvB1Ba0&9#CWp_rFj;7WN1hef43%dy(#xo=& znM9mS(n1V3+vg zDxb+uAFZLMuTG6Obiadi_N<14JT3pQ(rZ8u0?ol;xPbvTmDJ58j+1(PU z;{fPKwl*reWY3Pk1>5`fMY(}i_*8UU++*i)LldB0fEd2cE6DdR1N?A*+pn~g6ak!R81VsAL$U#&oc>rKg8yEhLw{;A^9&{o zQ?cGS8)iR!0>PCTOpP~_p6#!urza}tRG-AC9wb!0TDtD$I*o0#ZGOr&WzzG^y_q&$ zYLcYH{4df;0b;^u1X<@N^-M!m-Qp+unh7^(k3Xbh&=;B0Vx;`;Gcs0(VTtLKSQdXA zSGw=Z{z@~kZ&PZmBuiaB_sZPT7?$%Rn-|pyURu$VH*ysc|C2D@-dzm=G?)MndLq4v zZzFg{?%XvM!RNP0nN4Mi?Mo0#HW1vQP)|^Y!{fpXYb^Lao1#ny@QI0iwzlj(4VfI( zJ4+SHfyvq1`&haCVlWtA2w-UkKXeK_p2V5_mjPE65I_%ETKY?g-Fc=rH(tYbbhyLr zHFLy))a8HYR}{9 z+uN7cx!)t=0e?8Dbb@;8{aYB2>pQWKx%VO_0(19x0uB-Z_c2A{nxW3T2^|Lq0c{g00JdSn@LMqq5>C)$1PY znURrnujS)_QKh?jxPuQ|bztCXsA{f82~xAO$jxR~?~@Z)le)WXD-DZqd9S)#N=#63SBLt=fQsc9_6VJh6LN71d{=1JC+FrWx-z1eA@Ks~+i%g*#i)Y6p zVyN~90{M7JiTqIZiu*@<+^ZCK%x801iFqA=@`U*0q^h}=z&8SG`<{FfgzP+EYY2#5W9Lo_x}hmDdj^*tPk} zb`}pVzq;Lgw^ZH?G4}D~6oXy{;=kk;DXargJ&4@y(2bjRWwbsEUAr-C+|%b)Jl|Y# z6`?@6y%@~Vpd|onzIxJaW|Y+*Co|ElP(0YveTGec>o=(O*sn4jUHe!tbrErZf$l^9 zy+(b$sw-$L$t3?$rphX|kw>*8MvvCi^9-4@6Fg z7!so2=OBNq-Vf^jPx&j?qQVm&lOBN(3M7O@8>WFkC7O;nLgAlG+t*bNma679*V{vl zZ^UfL1XlLJX!;1z_xkwDhsq6ld`#;ok*O<{uOl`Txsaa-2oTFZ%#9sqQ#PoEccg)_<;05Vw z;_w8WqhSKB;?oPE-sQFxSLJ4NVPt3LE2NGFoo^$A&G7x<5-x}Z(6$)+$xq@#70I;B zyw5H~h31Wq__?o3Ei4!yc4B>UpuUQ>wrg`K;)?r?@`4MS)W@eTJ`2z@d$srdCRA5o z(+yquy`nPF7Z|=fmKRTZ1Jf_jNjZx6jT~uLCdD&upWn-W^F|Dy zOR#MdmvD2NP_3lj0zD0a)*V#up9XO@p`YIgkSfj%15 z4+{R1lUF4pp>G>U;f2#YY`eJ4hfWyK(M2gvkh1zlF|QZ=s|y#Q<@%RHDfYP^cE+n- za!<}VWo&#AvYoLXN3%!(fx5AYV|Doekv8;|2$}S1ytUuQ>(3sB7^O3?^8|#22&yU2 z6kL=)^3SXJMhT*#CoT670S}aNUCJV~{eXy#mOC^;p!3zQNS)&PVc|^;uyCH#1w}=@ zpE}EPQ&c=I6A)AQ;E>jRo>kR+n53NbjK}ITJ}lW$De?c!6MSjlOP}e)bFNua5OG5f zCiJvh@qP&jd-rst0)zhAqIC@R6==7F$v_2@DR5%p!*XG7d%HwR^^Av(&eBbbH|JKrRIL1%7?(>DB$LI5vj{aR+;6_VKvkPD&4i(H&-h0dGoFgQ(8=HETT~R zhXZy%)$w+6HsxrAK5XmQzbuFIMGF8I3N^|JnEl|#>wO76tt4KKN6v|b_+Nv=gnR&Q zTh$i$m}hCZbl`rPP-K!*KF$IfRu)F5vjtz;`OZ(?=3x_Je(jP=!024I*c19|Uf;7%hV z?|>i?2idGbu()1##Z*_T{AP0p(44x6LKC)*)q>hHdr5cF@c4$an|ZU~GG5`-)zwX8 z=|~C9U2N{{#h~nnrJY>S0!kJeg-DTsrB1X=&}NzqOqTSHNoaqlsT}~KU=G_xOp`Y5 zT_@LCSQp$59;_Eau!i18a&+Zqm&))&X$heG`S`k62r>_}%w7A6PhZH+VObx}v=0al zzRi0g_Jg_mhf%%y6ME~DIvgwUQfq7X{jT|+uB{Vm+v0on54stI5q=>00+s-GvETf* zU~tcH8xf#L!ZB(qBN=oMU$DMixeB8jZ=XR90Op;~%ge`6yL0vPYQ;trudBWj06@;} z_6Z*&0q3UHNBit;0S{1W?@qo6E3i0ZKxQ=9BGIrXQiz9=;Uy`kO)QojIKMjF@xDQ= zFt3V(g9APXk#9>UZq)22mXDK`=jQ#SE9DomsQ4YwppFMv%5w4g&Qdv`7UwS%2QO+V9&iu3^mWgwB`)LKL-kqcL;YK4XH zvoqiK-z}N$;AFI zX5-)jm&}I7M!h}ED}W<$Hs3qm*((qX0W-j*{#OIn>yg&4GCMSEOgbW~#5%_9RAjF> zZsMW7j|Jtf?*`M;Ugfw}8|Z2sAo)S`3c0OQ)Y;mjhtYH@$HyB#<)p1mB+#^C@cvf^ zA5wZM9_!FGHo`3v_I~kDSm|?_HcUUF#lo!7B1}>GV4DETzGMtFX77O6Eh#w>b#*>U zK{pK0N(1PONc`*Sgx{9)EEabx+)v@2&(zW)0B;sN(U2i@12P)3#&j>ckQk_{c9N%$ z8K`UhS30=MAhk2qc$rchWUED)*F=u3&>QKFO|+4Xy}mR;&g~_cE+CVQdh+ygL(Me< zt2q_fkfvyuLbal)bZsuOow;-dD@?v=d)F??P?++<#9^)=yVHT_uI62uI)k44jit-e z#H|HOTXMKx9qVV^5A^h@>wKP?Aq7j49!ikIU|he7M~ccPsn>;t|MM zkSmKaj?al9FxoN;EW>2I)Rv(ZLpC`aJ*00gi6JZ#!gMk77Q$J}UwNkS(|G4noGUe{GkzY)K^fO(R9@OU5?)z@5{tpk?3gK$ z$h}$8NsHSIUnr`BeOVr8`AZ;o|HrWT2ws6&j{;O74_HVNx?1KYdk|SsF=h!BR zoc{BNsFEdz6ylKWWC4f0v!bGw|1Wc7Cp$w6EU0rahGzG?+S|`Fv?44#u+-hVpKrk_ zx){a8#8UHLB)yao!>FH(h~?p6^8P`%yBM9sxPg9!=qme_j75c?FU_58uwnl9FaGsk z{_|P=uP_eus)aMI__kse0{+<>?q&HD}sEF68R5xK(g^rn9yY;AiYiQ`?y5H=l6kk z>?y|Xh@fs37eONj>gcvb7sp;~JiOZYfC}geKr?n;@KtWTv&nrvqtwom_U@`wAI|=` zhba63d(@AQe&ca45ocn$xVs+t)@q&5=)-Peu4C4V^f|Jt_KyCK9)VkPLrKxGz4axL z;eKxQiz`2VI7%Xd2tgCElh(fN8mp=}IB5ANDtc$5XuCGMI{Ty&3{)cBcdjP<^WBO9 z&`9ma1cxLNwMLl&Xw=k9Q2#N*0JFGFCe`^x9@T z76&YaSxAzAa)rd%GZ<*KQ`@OX1kZ;T0+upgy0xNDFpZ~a>qak zGzFGBn*T-to!hw!R*@HFh+7g+oP}LG@>@` z8}4Y#;=(N=P?XdADcui2<`7@UMno2G^<{~dMg4&J#fuk+lFDK*wI=t5g9Bdmkqrs# z&hs5cPq=-rXM4WWp$W1dQE{;pxa>}l*8=togB8{Bx~3*_$RG^YC1)&8P9P zE9xRm@Z=yZE3>SJo)TwQ;Zrh-?nc~~my>Gn{~m{j+MSPnq#l-3Qr%swr8Vm>SVWO# zSaam(>x%)o5}x@bg^e+9Y9Sw~H>;+tq{5>9wM~<(>UdP@<(ES|#y@5T6T+G>X&CUG zSG{k1j{(5~I9w420U|<#jES4~=N{Q)iAgklKKO?7oZSQW99N+aK;8^;ZXgI0o7{-X zLU{p}35Eka_#ROt;0gHR3dhxBsQ=(RpbTSFkUX%g1ffojab=xW)xB~f{iDd zRqGfEx)~99pCduB+eZCez@m(P_x1xvb4056m$pkn8TT);MJ3$ zMpGY#ll7~JW4@Ijh=W3m5^yOEE6+`JPm!4{GMf+gm6~f^HRlHe9jr0z){{|y!uZ8C-D9pjBs~GbP_H__A3|HlWrhJkDHC!lA z;LLm({PgJt{3$4SGv;libSkKI{lON{JY(aw_s#6>Tb`-DV{Fg~_9S>O6>}{?+kL~j zuoBuns80>_J!2jEDiTRKT0ZGX<&O4Kw2cTBT$*UTP}qzwx0HVXPHGh{yx{$`vl#yO zJUPB$1ku_i=cZ%gI|r%;-7gYUb0eX-2I;5ehWpZxn8>?&Nv+o1s(-@!d<!WnH!}TJ{$NgDYYag{C`?azakQM{S2Tz%Wu&?1Cv< z9PBgyF~}?+G*G!>`QMYo;>|(QfH16a+~z(7;RFzJY`2o~=UQR9x&ou)3tT?PIiO9F z#_H76e^WG19H}_AGfDsRc~AspgEZ-4@Ai+pBIWPv^Legn3dHXFxA}w3$In|9*(mRC z7KRaJ%Y|57W+f&jTNBwD*~_ny*Ydw1`YI|gkfX9nzFfa{megqnkO4Y3T>N3jn+8mQ;s|BRs0CYQ2Firo=Lp*1`? z9%ka=UmCm=8g|4Nw&ZrV%=u1*3mxZWO!N42wD5nA;ciw2#Moqx|CZNgHvEg|weB$8 z7f@f>_#+x5fxy%>Yu2OUM?uxe^g{-FvFx#Q6-h?SYd8Zj57Z&i2Gi=IVOi9Y+u6e2 zgXYI)oSw%%y8GR?R*kl9D_0PLXq3lwPZgB1*FFm$)q9|MUD{$yW|_UH*j3A44E8_0 znUY#_X@i@ERD0mH02m$76@cO~vslz#%RKt$B1c6vk|7Q_ztH)zpst&gPWyDjDU{yqZ|80U9eICWJu; zU#U#hAS+Vi5cCN$w8-40a3deCUtxZn6L(g6Z8OWeuRH`&l7OS5705%~WGzvajv$>U z3p6)k%$R4*dYZ|{wLFO!9HR{lJ=oOqPlIRWYUe7vZ2Z6n642{>-$$tgiA(~ ze6FXgR&!kmEi7tD9vC**iSp;MX9L&GPyr3(+0^k5%cG-$FX#uG4V+nqG{AVBfRE|) zQ6;lmrXe_rEuTE;u&0JgZ|oS!QRANx%HA5O7iIaj>NjS6YiauABQ!PVug`3T(!Gq> zuRQ%c&~r_XoJ)3^!Ke;l=v8s8I)2l1j?^w|DqCmvPRagc0c|^9^OGyO__4a(^!>_C*E*Dzj zp2sD|N1hTKbv$Mr(?FLkKlZq(P4I(qkklQyjkxvs<`yicMzgv5aOTl|<{yj%w!TcKlMH~(kS)P ze|>u<{^4cPRoW}^D$M&xt@`uWR;{`fG4MswhWrlQTP`!Nj0&Id*tpXu$Xv>Y5n$Lt z98-x!*cJK8eZE<`zkq{FSooPzO$ZZ;t0JuwB8t}Qy-;K_?-9Ocz@HTzb=zhG8GY7G zXQu@ziFIg9CWCHu))vA*r6n-XzbGGk(kv7ZGu0hE=p^^+xUC4cshBt7dUFHGPUMxS z77G+3<5Rlpti(6NWO5;DtE<23gfS)kyZ&A*AN8_J(IFjACqZ>0USjG8iA^FbXY_St zNOOi|J31uD)BNDcNKBVhobYwH?LbNS>fS_7iCp-LNdLL5NyR0_dEU2#kq=wqtMcdT zv|dT!eAY7vwR=#leR`OMJo<6*SxEe{S~J4s+`xWvM*+(qFkO8gF#-HMB;#G;={;TB zGSr7vAnc1(^g?tT<++&P(jo4Ka$Cpz#SL`GZF@xQ!#`nR!{DG<9kx>r@f@&G1c6x7 z^TaQ^hPwEiua19mofaWtK>kh$r*^DbJmE)}P1b$V z(wtARM~^_D`1d09^BT;Zg+61S{U;?TN&Y>5aG)Z5`sCf)@Xs@Z+mN0A&!cNIW9&kU z@qs<1A91>4ybk*5^?$%bX(ClJ5pBfS(Sq%$4!7=^{{A=A*!nur)I5~HgmJn;gbjGiN;&r{K8H9BGvY>R?21(hVbgDj{ zkTY*zr@nxrb6{Hlv0E;=vN+nokN66NX zA@cIhcHu~nDE<5C5889Xb;lS^=<{gA97COFkpmP|Tu5F*7S^)xCnp7xGqL3>P6tZj z9wVAox`~EGO%O=%(xE0-;f;shY;m%14o91%ywI%anY%R)H>5d1zCQeCb^98^RP*N)YR@;*FhUM9;HqB@4+`ObI}$%8}}6+T8% zGe#NyfOmIcT!xDz3`yXeh`f#VSlcpeRF!fPrVrE&(+A1b)TV62?2?&CE}ywhl3t%9S)%3~$x+Bl(kBBjoj2 z;m3_uPCcp-5&5^IPJ4GGq3G9) zhvS2qaSds?2Hm-Dc=oA}aq^m`{h3p`HdDqz=)+{&{Hh)W zaw@KX7;7_JKBAYB`%CK%HWZBACZisZt=am@E;k#0KTksM`*t&S@%n1cea*)*=qL;! z+4m6W%8q|hk(#ZWZyZDh=J96i)&pm!NzS05K+*V~x%$J8WKOG}QI;swt3u)Fw&^vq zdx4+YHD9=0FOUCyMFQrW6;~ss6ILp`VD@Ao?5q&!U_?A{)xZtmc2xjL@^X*&)UeqrC)n1;wt@M?W zTijgdn*25iuxv^msnW^J6up(bG{iUb4ytCL(M}ZGWR)z7{g;GCljxoQ$q->>UV--4 zzYnFR1;YrpjJo5wawd8uA22sqlGHT&eELd$ZvF4|qdV^1*jTk)@_%F|S@|ZA z2OKghq4e0jXL(=C_r_MQ%U1n6rW~rZfeLmKa+UiO5_dFYx6TIK;AbPly6`r_h7>1v z4!2Ja6ZD%H$1xvYXX8AB>$5{#>SH zbhv;5MP15%+Oaz&e|tsmfYh!jihI; zg@p$r>nBB}g<{E$tB)vqm^RS4R%L2T+;rGXBci!>K3{-%XS>w)L_vtDJhDTExMH+0eO>_ot~6G_*6e$t8o=jot{VIqoN@ujUC+ z9r#nRT6(AIp>NsTf1HOtR-0^EvDV~srP#!1_Bf32tE3DUv9srN@9k3^pOOn3`?NoY8?ni0lA6#e-Pu{U zvVT#R`plHT_<%Lz!gU+$%>{arUxh@n@kyv6wtCqK$de4{bPm{@GWus!9OAvlSE-tC zJ}h?)z2+U`+aT`7E@U5^<*NKVLfKt+{d@p!Z7aFw&0NNZdByzVT&rQ+B^`=t^i30U z3M_BS(IK~8@PPmtx+dREMtthsVqzXb_hWB_RqNHv(hI5zyKxGwuQK4Sy3_yh_0 zW@l7&UDg)mA0~OeHO_|edWmPw&Sr(?NBPpHc?kpcZj7|LDVnr@N;qO=mVU|Eig5K? zwRb;vhc(|3lk9tO7=QSq%ZD^1LDsAje9tt8LwXNn4Rwyl?J1YZzfm`TzzKAo-`%KF zce-1que5pe1gqiGIcqZV)$Q!!57YPL7m*mKW8S!a3nQzV(5>@In_zh!rd4Bf~=#Xn9H6Qbf zFSy`G@zRH*sm3R)m|WX-5gT}sLU7-;on(Izawj#O3@!0HuEs=l6Q;{}28!0BU#a;mN2J1p z{Tr)MsDTq9dJG)3QBNyew)Ltt{iZAJu5uQfK($-&pOfoY=2-9TBy-*}B@4n`v-NMm z$9_3-Byn~1vAJW|=SQAKpA}WEdn|9zXttj-*bQe}@0rPQ8rWAhbRF8e9a*YT=~YAd zvJq!rsIK4>yIZe`T1bM-@406W2{(Fq?qO3Yy?3jvPv7v)982!25wU8`6L3yl#Zt^} z?$&fL44=G+_6~8xLw*eEoz7|bDJwh1R~v34lCA;yulUzW9C|*L#P*f^(Dv>b zzdw7`3^ht2G`qiVET>4KIbtTcI0qAzn3HpndyF>#&p{C7bsnqM0Rg>nodWT(lT5;+v0q7m)ndk_AXDdJDugsM#XBvyVPm^=F9E%>(ANmkxWm{ zG7mqw+}63AahP=@uL-9=7h3Jf39S5WVY6GD-5Xb|xdqQB6j2@% z3CB3*@RBFmNo!T`Dmha$btq=DbvO6h?rS$3KLr1VCrmI-jduMc1lzhIn9FeU1XK)#I^kW!S?8RbA4z#FuN@f@ukW@YUT4i^4t}pucmF^@1iaj~46{f_Gjjwqb_A5^? z*PnpZVUl>@gWA{(U5KIDiecktGdG?uCFD4;V2}mke*Dl+lzZwN6z~|vZ^S>raICvS z^{%OvBF8*7D?8z7G@hfas~Xw!I$8bfVgW5YiATwak|)@hNvkH&y`H>}voigNp>=K_ zMnFeXkA-f4tNipCY5P!JOq5Lh39_N0i;d3JzQQd(^(5$DH)-n*-DbvfxN>Xhkz*$@{};`TrpVx&_c!0VodF@eRz)68-F5Wr(kX9 z7GvVaw__tXrX02V#4O7jLeHgwt(lhzJgSZ`DO)ECHX3!F4_n|)2hnOumOsLgDpl?u z{mw`hA6Ha2epu5q=xgP6uJE>jYdK@$ySl83{LEolOjTk%eWV1}U!H;=_@;>m$-(?}Ke?Xz3^XY>r@tozPlUEyx^h!p9NAl6vbM z_AvUU_Ul(cf^e7l_9K1`rMbIw|BLo0m)|!)NnlS;H>&^i?xGgi<5P<+*XGVL{90%E zaDOd4-9A}yaa5?|Udgxf#7tga+Ip@MPL=Mgv!^`e`deOsy!HRR<^R_g{@;fMB%ixL zh?V4(oLir0{1gp40OLUUH)g=)AA?DosaUPD@}vbPhA|EM&W&R+mH6 z@9im21OnqEj?evI8wh(hDjP8nV8Grm{05c|vm?t2d(J<0b7ZjcDy7K2_fs1+grMa= zA=a-*{uzOwSk>?&3HRrh5cFxt{tyxT{PRD&7|r>v=`3N_o4yO1+9v)dZSaNvtz#(<8LhMMyWym9;qf2R@|D zjD}KEla^3lyz|-Irx)E?sXGHk$}bW#PxF1>DYp zd<}(y#P$xej)J*fd(^w-_qYd6TTDh@H_)lyK^2@;xxC*J10BfTRuNOC8qr;mfBiy*PmvWLg=wjBSuyv<`h6SECcjpCXHyANvoa_7 zvfz}|OAo2&_2)gWP27JF)VW_&7qV;jBFrt~9VNhJQO1^f6D=%fnjg2COC)d~P-GXd z(xcV9tPZ?Es!=)6K;AV^3JCa}+^YY8KG!ARS%a1iUnsFe!0Uh(Z%!R)8w{3(Ux$DO zNT`Tq4A2o5g{gH0(@7h!!S2jF@3mRHF5MxnDb5NUMX%!(b*%ttab{OjAge(>=N(l* zliT?E(kvC^(GU6uHnTmQ(zao+c%PP%A%`BteQPYF32tBUNgMZJ9vglVRf9VqPM@3} z^7L;Y^>wWbDy;oo1p=L0{EMlLoCvgHvAUgG1+f3~P6y-_^QY?Oh6-9x<3e=jnk zqJ6K%g6ZX3Kh#&&tFAdGs61(ToWI{H04E(VEi_1xC`ZhxPZG_5MB z2=p8G)VV<<_ONrW=phR2z+>i^CW!JBP}e0}ZnGNG6HH8Ld%)SRZLV8Ii8?4Z6Ju)z z-SP(P;%e~&FWb}wug4f@Zrj4-qNy{w-?t@^mI43-GxvHJ4#BUnDq?Tw*xj<({0Cs& zirv-`_CL|;-0a1_?H9IK(m~NCE&AGUYPeqYW5pwZa&B{6PoM?@<;3PqhVJmW5?^c% z)50^JB-3B1?ua96a#(1 zEB)f*+%OI5nJL)vsKrhyns8V1+Z-SK)?j!W5OF=HxYr0Vv3~b93$#M;IUHSs-Su;_ zfHmq1lg$M?JAJXhUbcMejp8tt2|9=Dg5}mQ9 z@Dlt#5BXWxl6`vjQtkhv_44m*`yUq5|K&xlD0)>T8_M5T8Neo;;+Gn^E#cF98124D zcKy#o@lYkL?B2hYG+7K0)8{GvK1rfnNkf~RTmlgEvB#A0*1L`*Z;5ccn)JcyDTa5pYiW?-RAHd{=F zEnv+tnLTu5qh$UtmbsYw58#S5h%@iwTG#|X`1Hxiq!$}RS%AH58172OVS=*Yjk zLOB#_5x!4}^o>q2w@|0do_(Tf2fEAtKsr9?vG?v-CpQf(AUDsV}=`YLsr~E%aQo^JtYtjTBtmbHowqf>OSrC0358)l@0-V zueg3YhVtNd#ko}WO|rEmD=WYC?7yzM9SI=3zedEF|5=nLcIdf*x~ipH;VN3U&E|IO zcMA!5tVd|=yUb@$?5U4Q5mYj1m}92I(&C<1DY^jr&J8js5eVkY&|@k;bgKTj$eBVW z7icc^a;}!O28_u{HbH-onOTj%vshs=K9zGV7D2vfyW9m2)8Zf5sKdGo7b!c6O#aWh zTA+Xh-p3b}r2fE~=QgF$a9yLa=s&d;yQ-`A5CBY|*-5%zeUps*GPSxUiBbM3bF^AX zCBQA_{L2s)8hjLVouT8XSdkd{^SE@~kx!b~eh`rHA~Tqtji0abmc(OKOpQBq?Mw5R z;eeC3FU??PWaHm)Oz=@sQA-(SgO!qRRbPqI#pRR1VuR~iK9|21y1{knwtmy|Dl#0i16S?dBshsb7!FTA&qe^9O9?`^Gs}A&-n1FOt{Io zs?SJnBaJD;!oURV*_lNsAOUnW_rJ700G#UCfn!P|(e&P1!nh!CI(#gCVDRe87lWA? z4arN&tm4B5PhVVz)l_!G`b3$_ip+m(29#Uj{d<-Scq!EPY%4dtP->%GT618}L1?ij zbEK%EC1&@_M?)U<*u;c1Iuu~lu0Xu1cYQH^kE0!`M(jqeYH+*nCC$(mi9b+q-D@1j zaE8lX{vo{GsozYf8s}k=JNAK_$LY$6v(Cq%bnq<@RT|K`@#h!Imj(rsSm&+RjO?`hvS3r7uR4t=#x(tDW zm+JYCn~a&d0?#@SrciI3q~H9od1&ODao|6-_No8S+EczZdJs=_E=Fmf;znosr{f|E zd>FsSx{Eju56^BgPm9*8DdnE4_6fy@H#%I+q18`{*~3b1Mkbzkg%@ijSGj$Gz6Qps z9rwchsZaR&N%-Lp2Ki?Hj8K9;LU-{nAui|1A=XmiD7%Xs=$`%(`oc{l`P>|$4jwcs z42JsPe~@gBfW(tbd%-`?zT#zt`!QdY@Sf&o(L2u%P1|dy9o#MWrPJT~N9CrTyvk_v z=;dPX^Pp!M&P-Ph|J*3iEv3xIiqphH&#I+1TKhv4Z`2HN%2xk9IRUDo3ysWtL6x-5 zUxM22WC96p&y}mNSw>6o59NO+rIxd4F&V5U> zxLQ{IZ9=_+Llx%r1Qc`Z;om)7vax?IMp?;wXG5EI<=aFsKi7)>UAek8(_Rxz_vL0v zZDe8`D%qj_Tmk~+tf5;^R44p5l#L5ah+j9(oa-1R@Afgj^8|GCwbD_ z&oI+8?(Z9v9M60dx|{p4uv)l{QyGdy!`2+eOm?|}(7#up>y6AELQRdy^XHyp||gp=d%(z)f<6@{-lr>3n@vG<{ee@C+q(^2KBf?V5`yZ?C8Qjf|l zv4Pp35}nJweIL_vX*NafW>VB){JlMjhl!TAE0dS|o=1dF$BGAtbbecS)>DE3rJr z-o$7w?sH$U+59)dagh$=x#!vrXWdO&v98MYY-L&q*eA3#W)}D`2ejD#{>0?Hq;d%{ z)mLX6p!sH{iEEza!}IF>K7O5hUsc8S?ik)Jc*#5`J+=^Mm%DPWOwf8Eu6^Jdx7wbEJ$GgC$mqjt^K*7? zD$$7p#^cN*O$!mzLxk;n3emnQ1GmH?W}>8?E)tar(v9^ss5rPh=!+WplKwYw^LAPx zT{<(rx<-O;S#?6vtg&2yYI%0h>jzQr`Q1;-6+aEN#?Rex5Yy1t958mD%#r3Lv!_st zr2EH4VkM?`<$Qqpgf|xk1JDi+6k6}R)CJ(K!r%{zou zw8PzL2aj_JnadZ#vo!wk5!`bc(UnU@t)a*%G`rewUNx_v-OpMEt?YhUxkN;?nPlQD zbE4n_?Ll)DBWHJ+Y^yVWpE`k4CN5W<=XdrPn)>9jnkCnR7-q9V;F+-xX2_HNM(i$A zIF~|O+$`g0C${)MzF6rMD~$R+Wf!s0>+b!>nVym5JyV>%(E%mWU}CQCx9`IhJ|mKP z)YyrXmv)3UvnBBhYn4m9UyTpjm2&7_iS3ZjNDt|II5#&e{pPX?t@3n^e`e5m6dZ5s zH8NW?61o(sUIdHnoVOv42vdqoINn!0C9td}PAU8<=pehemE1KPXZ9Gp{qtGNB^H7V z#v1JAopEFTFU6hxF0TE9{dO7ZZkFlOky_p$}2Q;4VKG3Svty<6oxhoz%zT-qq^8r69ayhKNw8Kx8= zF`TOL)lpN5Fn;lvR*I<801C^mNCA%0nJ=X~l8#pugeW&vNg>S8il~0en+?2m>ybRjBt>lZ2 zymhrVGVX&}RrjU6GrJVE&Zd_8+sIo{*Dv%uNHKO3 zGj#sqY7{owNz)TqSsGi(O>(kTW877EeX2mxi}KLN``yo5s=}6GvtEtvac@l33NZa~3F&PuA@v*PcFm7=N;M6}!1+)FQE2%HaB zYBUU((>XDbHx)iBCOb))K>So9bUQcIQKzFU#(}x%aR*jwSdD4zx3Bw4pR9diybdy= zAEF`eH&Y?`_5rKa)PU~TZq8+uuO>9#ve`?oF|wtyofkU#;5#9YFjRreez-F@zZC@Xo+D)H=jsb7d z>YLuZGaTZsKc$@`inh}9?aB@Ok1m4`_2Psp@p=@_qL}ucd8bgyJ`wDhQaRY zt9=j3w=`x-@t)ky+7lADp6|eUxd~l`AjyXGrdPXH-&&p4^5nLY8}oCIh`4G7|BxQ; zo1j9b!MtLT^|7^k4~NI5hk5LazaX4g`MPlp-^Q(}AZZ4>gW9Y1smCv$Vq~szU%$Z3 z!Zu2^ZqKoaS_L5426^qSru6vYUsB;MPky(a<64x?@+|#&kH9*x)APTTu=m^-*j67c zI_bT*ops2$J$s6u{)HHP^VOM>mtVu%pZw-s!?k%n-&u(8$=KE-+wVN@oA77H!sT(x zG;P4y;>Gun@ZRY6zvUNv$!neCx(FnwTH+c}l9E`GYL#4+3Zxi}3=GY54UBaSOhODT utc*>qOboOQjI0a{US?18L(!0%pOTqYiCe=AGrdnB|9QIlxvX Date: Mon, 7 Mar 2016 11:47:01 +0200 Subject: [PATCH 130/183] Clarify where 403 and 404 pages should exist --- doc/pages/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 080570cdaff..d9d38c1aa92 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -354,7 +354,9 @@ private key when adding a new domain. You can provide your own 403 and 404 error pages by creating the `403.html` and `404.html` files respectively in the root directory of the `public/` directory -that will be included in the artifacts. +that will be included in the artifacts. Usually this is the root directory of +your project, but that may differ depending on your static generator +configuration. If the case of `404.html`, there are different scenarios. For example: From 0255a85810f09fe6290c8e44a375d89eddd60ac7 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 7 Mar 2016 12:00:40 +0200 Subject: [PATCH 131/183] Use the default ruby:2.1 image --- doc/pages/README.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index d9d38c1aa92..cf4e45e17dd 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -224,15 +224,17 @@ specific to your static generator. The example below, uses [Jekyll] to build the static site: ```yaml -pages: - images: jekyll/jekyll:latest +image: ruby:2.1 # the script will run in Ruby 2.1 using the Docker image ruby:2.1 + +pages: # the build job must be named pages script: - - jekyll build -d public/ + - gem install jekyll # we install jekyll + - jekyll build -d public/ # we tell jekyll to build the site for us artifacts: paths: - - public + - public # this is where the site will live and the Runner uploads it in GitLab only: - - master + - master # this script is only affecting the master branch ``` Here, we used the Docker executor and in the first line we specified the base @@ -241,7 +243,11 @@ image against which our builds will run. You have to make sure that the generated static files are ultimately placed under the `public` directory, that's why in the `script` section we run the `jekyll` command that builds the website and puts all content in the `public/` -directory. +directory. Depending on the static generator of your choice, this command will +differ. Search in the documentation of the static generator you will use if +there is an option to explicitly set the output directory. If there is not +such an option, you can always add one more line under `script` to rename the +resulting directory in `public/`. We then tell the Runner to treat the `public/` directory as `artifacts` and upload it to GitLab. @@ -251,8 +257,8 @@ upload it to GitLab. See the [jekyll example project][pages-jekyll] to better understand how this works. -For a list of Pages projects, see [example projects](#example-projects) to get -you started. +For a list of Pages projects, see the [example projects](#example-projects) to +get you started. #### How to set up GitLab Pages in a repository where there's also actual code @@ -279,9 +285,11 @@ Below is a copy of `.gitlab-ci.yml` where the most significant line is the last one, specifying to execute everything in the `pages` branch: ``` +image: ruby:2.1 + pages: - images: jekyll/jekyll:latest script: + - gem install jekyll - jekyll build -d public/ artifacts: paths: From a77a101d312910175dff3d8ebe8ecd1d8da357ee Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 7 Mar 2016 12:35:10 +0200 Subject: [PATCH 132/183] Add separate section for GitLab.com --- doc/pages/README.md | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index cf4e45e17dd..8f4c0bc8081 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -12,6 +12,9 @@ With GitLab Pages you can host for free your static websites on GitLab. Combined with the power of [GitLab CI] and the help of [GitLab Runner] you can deploy static pages for your individual projects, your user or your group. +Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlab-com) if you are +using GitLab.com to host your website. + --- @@ -25,18 +28,19 @@ deploy static pages for your individual projects, your user or your group. - [Explore the contents of `.gitlab-ci.yml`](#explore-the-contents-of-gitlab-ciyml) - [How `.gitlab-ci.yml` looks like when the static content is in your repository](#how-gitlab-ciyml-looks-like-when-the-static-content-is-in-your-repository) - [How `.gitlab-ci.yml` looks like when using a static generator](#how-gitlab-ciyml-looks-like-when-using-a-static-generator) - - [How to set up GitLab Pages in a repository where there's also actual code](#how-to-set-up-gitlab-pages-in-a-repository-where-there-s-also-actual-code) + - [How to set up GitLab Pages in a repository where there's also actual code](#how-to-set-up-gitlab-pages-in-a-repository-where-theres-also-actual-code) - [Next steps](#next-steps) - [Example projects](#example-projects) - [Add a custom domain to your Pages website](#add-a-custom-domain-to-your-pages-website) - [Secure your custom domain website with TLS](#secure-your-custom-domain-website-with-tls) - [Custom error codes pages](#custom-error-codes-pages) - [Remove the contents of your pages](#remove-the-contents-of-your-pages) +- [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) - [Limitations](#limitations) - [Frequently Asked Questions](#frequently-asked-questions) - [Can I download my generated pages?](#can-i-download-my-generated-pages) - [Can I use GitLab Pages if my project is private?](#can-i-use-gitlab-pages-if-my-project-is-private) - - [Q: Do I have to create a project named `username.example.io` in order to host a project website?](#q-do-i-have-to-create-a-project-named-username-example-io-in-order-to-host-a-project-website) + - [Do I have to create a project named `username.example.io` in order to host a project website?](#do-i-have-to-create-a-project-named-usernameexampleio-in-order-to-host-a-project-website) @@ -44,8 +48,7 @@ deploy static pages for your individual projects, your user or your group. > **Note:** > In the rest of this document we will assume that the general domain name that -> is used for GitLab Pages is `example.io`. If you are using GitLab.com to -> host your website, replace `example.io` with `gitlab.io`. +> is used for GitLab Pages is `example.io`. In general there are two types of pages one might create: @@ -78,7 +81,7 @@ In brief, this is what you need to upload your website in GitLab Pages: sure you get that right. 1. Create a project 1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory - of your repository with a specific job named [`pages`][pages]. + of your repository with a specific job named [`pages`][pages] 1. Set up a GitLab Runner to build your website > **Note:** @@ -383,6 +386,17 @@ to your project's **Settings > Pages** and hit **Remove pages**. Simple as that. ![Remove pages](img/pages_remove.png) +## GitLab Pages on GitLab.com + +If you are using GitLab.com to host your website, then: + +- The general domain name for GitLab Pages on GitLab.com is `gitlab.io` +- Shared runners are provided for free and can be used to build your website + if you cannot or don't want to set up your own Runner +- Custom domains and TLS support are enabled + +The rest of the guide still applies. + ## Limitations When using Pages under the general domain of a GitLab instance (`*.example.io`), @@ -405,7 +419,7 @@ Sure. All you need to do is download the artifacts archive from the build page. Yes. GitLab Pages don't care whether you set your project's visibility level to private, internal or public. -### Q: Do I have to create a project named `username.example.io` in order to host a project website? +### Do I have to create a project named `username.example.io` in order to host a project website? No. You can create a new project named `foo` and have it served under `http(s)://username.example.io/foo` without having previously created a From 552e8993496fc57d15c5b712806aa87ed974fc6e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 7 Mar 2016 12:36:08 +0200 Subject: [PATCH 133/183] Clarification --- doc/pages/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 8f4c0bc8081..682359ad266 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -218,7 +218,7 @@ pages: #### How `.gitlab-ci.yml` looks like when using a static generator In general, GitLab Pages support any kind of [static site generator][staticgen], -since the Runner can be configured to run any possible command. +since `.gitlab-ci.yml` can be configured to run any possible command. In the root directory of your Git repository, place the source files of your favorite static generator. Then provide a `.gitlab-ci.yml` file which is From 1fe13d63cded87f06b7ab3edf77945b15d191b91 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 7 Mar 2016 12:46:35 +0200 Subject: [PATCH 134/183] Remove confusing FAQ question [ci skip] --- doc/pages/README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 682359ad266..e9234e14a17 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -40,7 +40,6 @@ using GitLab.com to host your website. - [Frequently Asked Questions](#frequently-asked-questions) - [Can I download my generated pages?](#can-i-download-my-generated-pages) - [Can I use GitLab Pages if my project is private?](#can-i-use-gitlab-pages-if-my-project-is-private) - - [Do I have to create a project named `username.example.io` in order to host a project website?](#do-i-have-to-create-a-project-named-usernameexampleio-in-order-to-host-a-project-website) @@ -419,12 +418,6 @@ Sure. All you need to do is download the artifacts archive from the build page. Yes. GitLab Pages don't care whether you set your project's visibility level to private, internal or public. -### Do I have to create a project named `username.example.io` in order to host a project website? - -No. You can create a new project named `foo` and have it served under -`http(s)://username.example.io/foo` without having previously created a -user page. - --- [jekyll]: http://jekyllrb.com/ From d557b3fec51eaa560d4ccbafe344a4ab4b7bb5dd Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 7 Mar 2016 13:00:40 +0200 Subject: [PATCH 135/183] Fix markdown anchor link [ci skip] --- doc/pages/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index e9234e14a17..fa31a3ec560 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -12,8 +12,8 @@ With GitLab Pages you can host for free your static websites on GitLab. Combined with the power of [GitLab CI] and the help of [GitLab Runner] you can deploy static pages for your individual projects, your user or your group. -Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlab-com) if you are -using GitLab.com to host your website. +Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) for specific +information, if you are using GitLab.com to host your website. --- From 9a8818f87f8bfc34e900b8fa2903a313980cd0fc Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 7 Mar 2016 19:09:42 +0200 Subject: [PATCH 136/183] Add FAQ on Pages website type precedence --- doc/pages/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/pages/README.md b/doc/pages/README.md index fa31a3ec560..0f37cce2c8f 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -40,6 +40,7 @@ information, if you are using GitLab.com to host your website. - [Frequently Asked Questions](#frequently-asked-questions) - [Can I download my generated pages?](#can-i-download-my-generated-pages) - [Can I use GitLab Pages if my project is private?](#can-i-use-gitlab-pages-if-my-project-is-private) + - [Do I need to create a user/group website before creating a project website?](#do-i-need-to-create-a-usergroup-website-before-creating-a-project-website) @@ -418,6 +419,11 @@ Sure. All you need to do is download the artifacts archive from the build page. Yes. GitLab Pages don't care whether you set your project's visibility level to private, internal or public. +### Do I need to create a user/group website before creating a project website? + +No, you don't. You can create your project first and it will be accessed under +`http(s)://namespace.example.io/projectname`. + --- [jekyll]: http://jekyllrb.com/ From 873eacaf8c7e4612a3800a65038e24508558d06c Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 8 Mar 2016 16:20:11 +0200 Subject: [PATCH 137/183] Mention that shared runners on GitLab.com are enabled by default [ci skip] --- doc/pages/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 0f37cce2c8f..9377135be7d 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -390,10 +390,10 @@ to your project's **Settings > Pages** and hit **Remove pages**. Simple as that. If you are using GitLab.com to host your website, then: -- The general domain name for GitLab Pages on GitLab.com is `gitlab.io` -- Shared runners are provided for free and can be used to build your website - if you cannot or don't want to set up your own Runner -- Custom domains and TLS support are enabled +- The general domain name for GitLab Pages on GitLab.com is `gitlab.io`. +- Custom domains and TLS support are enabled. +- Shared runners are enabled by default, provided for free and can be used to + build your website. If you want you can still bring your own Runner. The rest of the guide still applies. From 2b18f02023496bec4c661a5134f6c6aa5753cae6 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 9 Mar 2016 10:51:41 +0200 Subject: [PATCH 138/183] Add section on redirects [ci skip] --- doc/pages/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/pages/README.md b/doc/pages/README.md index 9377135be7d..7e9184b54d3 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -37,6 +37,7 @@ information, if you are using GitLab.com to host your website. - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) - [Limitations](#limitations) +- [Redirects in GitLab Pages](#redirects-in-gitlab-pages) - [Frequently Asked Questions](#frequently-asked-questions) - [Can I download my generated pages?](#can-i-download-my-generated-pages) - [Can I use GitLab Pages if my project is private?](#can-i-use-gitlab-pages-if-my-project-is-private) @@ -408,6 +409,16 @@ don't redirect HTTP to HTTPS. [rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC" +## Redirects in GitLab Pages + +Since you cannot use any custom server configuration files, like `.htaccess` or +any `.conf` file for that matter, if you want to redirect a web page to another +location, you can use the [HTTP meta refresh tag][metarefresh]. + +Some static site generators provide plugins for that functionality so that you +don't have to create and edit HTML files manually. For example, Jekyll has the +[redirect-from plugin](https://github.com/jekyll/jekyll-redirect-from). + ## Frequently Asked Questions ### Can I download my generated pages? @@ -435,3 +446,4 @@ No, you don't. You can create your project first and it will be accessed under [pages]: ../ci/yaml/README.md#pages [staticgen]: https://www.staticgen.com/ [pages-jekyll]: https://gitlab.com/gitlab-examples/pages-jekyll +[metarefresh]: https://en.wikipedia.org/wiki/Meta_refresh From 1bdfdd87d81c9415def30ac17518a991b900d095 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 9 Mar 2016 11:00:56 +0200 Subject: [PATCH 139/183] Add known issues section --- doc/pages/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/pages/README.md b/doc/pages/README.md index 7e9184b54d3..53462f81edb 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -42,6 +42,7 @@ information, if you are using GitLab.com to host your website. - [Can I download my generated pages?](#can-i-download-my-generated-pages) - [Can I use GitLab Pages if my project is private?](#can-i-use-gitlab-pages-if-my-project-is-private) - [Do I need to create a user/group website before creating a project website?](#do-i-need-to-create-a-usergroup-website-before-creating-a-project-website) +- [Known issues](#known-issues) @@ -435,6 +436,10 @@ to private, internal or public. No, you don't. You can create your project first and it will be accessed under `http(s)://namespace.example.io/projectname`. +## Known issues + +For a list of known issues, visit GitLab's [public issue tracker]. + --- [jekyll]: http://jekyllrb.com/ @@ -447,3 +452,4 @@ No, you don't. You can create your project first and it will be accessed under [staticgen]: https://www.staticgen.com/ [pages-jekyll]: https://gitlab.com/gitlab-examples/pages-jekyll [metarefresh]: https://en.wikipedia.org/wiki/Meta_refresh +[public issue tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Pages From 8fc3030ab64ca41d506fcc40eefcde64fa59f83e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 9 Mar 2016 11:07:52 +0200 Subject: [PATCH 140/183] Add note on custom domains limitation [ci skip] --- doc/pages/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/pages/README.md b/doc/pages/README.md index 53462f81edb..335c37b4814 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -354,6 +354,14 @@ instructions. ![Pages DNS details](img/pages_dns_details.png) +--- + +>**Note:** +Currently there is support only for custom domains on per-project basis. That +means that if you add a custom domain (`example.com`) for your user website +(`username.example.io`), a project that is served under `username.example.io/foo`, +will not be accessible under `example.com/foo`. + ### Secure your custom domain website with TLS When you add a new custom domain, you also have the chance to add a TLS From 8f5e7c36e4e8f60ab34a29da18e499705216c261 Mon Sep 17 00:00:00 2001 From: Heather McNamee Date: Wed, 23 Mar 2016 10:58:28 +0000 Subject: [PATCH 141/183] Update links to new pages group. --- doc/pages/README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 335c37b4814..938311ebf95 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -307,8 +307,8 @@ See an example that has different files in the [`master` branch][jekyll-master] and the source files for Jekyll are in a [`pages` branch][jekyll-pages] which also includes `.gitlab-ci.yml`. -[jekyll-master]: https://gitlab.com/gitlab-examples/pages-jekyll-branched/tree/master -[jekyll-pages]: https://gitlab.com/gitlab-examples/pages-jekyll-branched/tree/pages +[jekyll-master]: https://gitlab.com/pages/jekyll-branched/tree/master +[jekyll-pages]: https://gitlab.com/pages/jekyll-branched/tree/pages ## Next steps @@ -320,17 +320,17 @@ what more you can do with GitLab Pages. Below is a list of example projects for GitLab Pages with a plain HTML website or various static site generators. Contributions are very welcome. -- [Plain HTML](https://gitlab.com/gitlab-examples/pages-plain-html) -- [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) -- [Hugo](https://gitlab.com/gitlab-examples/pages-hugo) -- [Middleman](https://gitlab.com/gitlab-examples/pages-middleman) -- [Hexo](https://gitlab.com/gitlab-examples/pages-hexo) -- [Brunch](https://gitlab.com/gitlab-examples/pages-brunch) -- [Metalsmith](https://gitlab.com/gitlab-examples/pages-metalsmith) -- [Harp](https://gitlab.com/gitlab-examples/pages-harp) +- [Plain HTML](https://gitlab.com/pages/plain-html) +- [Jekyll](https://gitlab.com/pages/jekyll) +- [Hugo](https://gitlab.com/pages/hugo) +- [Middleman](https://gitlab.com/pages/middleman) +- [Hexo](https://gitlab.com/pages/hexo) +- [Brunch](https://gitlab.com/pages/brunch) +- [Metalsmith](https://gitlab.com/pages/metalsmith) +- [Harp](https://gitlab.com/pages/harp) -Visit the gitlab-examples group for a full list of projects: -. +Visit the GitLab Pages group for a full list of example projects: +. ### Add a custom domain to your Pages website @@ -458,6 +458,6 @@ For a list of known issues, visit GitLab's [public issue tracker]. [gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner [pages]: ../ci/yaml/README.md#pages [staticgen]: https://www.staticgen.com/ -[pages-jekyll]: https://gitlab.com/gitlab-examples/pages-jekyll +[pages-jekyll]: https://gitlab.com/pages/jekyll [metarefresh]: https://en.wikipedia.org/wiki/Meta_refresh [public issue tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Pages From 39f9056dbb45f8aec3b97ecf85ca4b0c00b49534 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 31 Mar 2016 11:54:33 +0200 Subject: [PATCH 142/183] Update GitLab Pages to 0.2.1 --- GITLAB_PAGES_VERSION | 2 +- doc/pages/administration.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index 0ea3a944b39..0c62199f16a 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -0.2.0 +0.2.1 diff --git a/doc/pages/administration.md b/doc/pages/administration.md index d0fdbeafa5b..68b9003a420 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -97,7 +97,7 @@ installing the pages daemon. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages -sudo -u git -H git checkout v0.2.0 +sudo -u git -H git checkout v0.2.1 sudo -u git -H make ``` @@ -505,7 +505,7 @@ latest previous version. - Documentation was moved to one place [8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md -[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.0 +[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.1 [NGINX configs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/8-5-stable-ee/lib/support/nginx --- From 2c1eeb5c73d625b0f4481e8a7985d95071e7640c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 5 Apr 2016 18:06:11 +0200 Subject: [PATCH 143/183] Update GitLab Pages to 0.2.2 --- GITLAB_PAGES_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index 0c62199f16a..ee1372d33a2 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -0.2.1 +0.2.2 From fd61cd08ceb6d05b6e1bf103c60add02bdd3edbe Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 21 Apr 2016 17:33:08 +0000 Subject: [PATCH 144/183] pages: Fix "undefined local variable or method `total_size'" when maximum page size is exceeded --- app/services/projects/update_pages_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index ceabd29fd52..45a28b46d06 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -85,7 +85,7 @@ module Projects public_entry = build.artifacts_metadata_entry(SITE_PATH, recursive: true) if public_entry.total_size > max_size - raise "artifacts for pages are too large: #{total_size}" + raise "artifacts for pages are too large: #{public_entry.total_size}" end # Requires UnZip at least 6.00 Info-ZIP. From b22c06d311525cbb9edc95ab99da26fa9b921395 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 12 May 2016 09:38:27 -0500 Subject: [PATCH 145/183] Bump GitLab Pages to 0.2.4 --- GITLAB_PAGES_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index ee1372d33a2..abd410582de 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -0.2.2 +0.2.4 From caedc996bdb37718b8397988662835b3c8163e46 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Fri, 3 Jun 2016 13:01:54 -0600 Subject: [PATCH 146/183] Adds algorithm to the pages domain key and remote mirror credentials encrypted attributes for forward compatibility with attr_encrypted 3.0.0. aes-256-cbc is the default algorithm for attr_encrypted 1.x, but the default is changed in 3.0 and thus must be declared explicitly. See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4216/ for more information. This will prevent OpenSSL errors once the code from that MR is merged into EE. --- app/models/pages_domain.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index 9155e57331d..2f4cded15c8 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -10,7 +10,10 @@ class PagesDomain < ActiveRecord::Base validate :validate_matching_key, if: ->(domain) { domain.certificate.present? || domain.key.present? } validate :validate_intermediates, if: ->(domain) { domain.certificate.present? } - attr_encrypted :key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base + attr_encrypted :key, + mode: :per_attribute_iv_and_salt, + key: Gitlab::Application.secrets.db_key_base, + algorithm: 'aes-256-cbc' after_create :update after_save :update From 0168a24064a5748ab0de902a12c0fe9851668fb1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 8 Aug 2016 20:53:31 +0800 Subject: [PATCH 147/183] Only show the message if user is not the owner Closes #323 --- app/views/projects/pages/_destroy.haml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index 6a7b6baf767..df5add89545 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -1,11 +1,12 @@ -- if can?(current_user, :remove_pages, @project) && @project.pages_deployed? - .panel.panel-default.panel.panel-danger - .panel-heading Remove pages - .errors-holder - .panel-body - %p - Removing the pages will prevent from exposing them to outside world. - .form-actions - = link_to 'Remove pages', namespace_project_pages_path(@project.namespace, @project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" +- if can?(current_user, :remove_pages, @project) + - if @project.pages_deployed? + .panel.panel-default.panel.panel-danger + .panel-heading Remove pages + .errors-holder + .panel-body + %p + Removing the pages will prevent from exposing them to outside world. + .form-actions + = link_to 'Remove pages', namespace_project_pages_path(@project.namespace, @project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" - else .nothing-here-block Only the project owner can remove pages From e6a7eb43b2dda4d47d25b4d7ecc068ba01f5b54d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 18 Aug 2016 23:36:31 +0800 Subject: [PATCH 148/183] If no pages were deployed, show nothing. Feedback: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/628#note_14000640 --- app/views/projects/pages/_destroy.haml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index df5add89545..42d9ef5ccba 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -1,5 +1,5 @@ -- if can?(current_user, :remove_pages, @project) - - if @project.pages_deployed? +- if @project.pages_deployed? + - if can?(current_user, :remove_pages, @project) .panel.panel-default.panel.panel-danger .panel-heading Remove pages .errors-holder @@ -8,5 +8,5 @@ Removing the pages will prevent from exposing them to outside world. .form-actions = link_to 'Remove pages', namespace_project_pages_path(@project.namespace, @project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" -- else - .nothing-here-block Only the project owner can remove pages + - else + .nothing-here-block Only the project owner can remove pages From 0763b5ea4a5e463b6cc0e94ae05ba2e58262cf74 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 19 Aug 2016 00:24:27 +0800 Subject: [PATCH 149/183] Add a test for checking pages setting --- spec/features/projects/pages_spec.rb | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 spec/features/projects/pages_spec.rb diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb new file mode 100644 index 00000000000..acd7a1abb9a --- /dev/null +++ b/spec/features/projects/pages_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +feature 'Pages', feature: true do + given(:project) { create(:empty_project) } + given(:user) { create(:user) } + given(:role) { :master } + + background do + project.team << [user, role] + + login_as(user) + end + + shared_examples 'no pages deployed' do + scenario 'does not see anything to destroy' do + visit namespace_project_pages_path(project.namespace, project) + + expect(page).not_to have_link('Remove pages') + expect(page).not_to have_text('Only the project owner can remove pages') + end + end + + context 'when user is the owner' do + background do + project.namespace.update(owner: user) + end + + context 'when pages deployed' do + background do + allow_any_instance_of(Project).to receive(:pages_deployed?) { true } + end + + scenario 'sees "Remove pages" link' do + visit namespace_project_pages_path(project.namespace, project) + + expect(page).to have_link('Remove pages') + end + end + + it_behaves_like 'no pages deployed' + end + + context 'when the user is not the owner' do + context 'when pages deployed' do + background do + allow_any_instance_of(Project).to receive(:pages_deployed?) { true } + end + + scenario 'sees "Only the project owner can remove pages" text' do + visit namespace_project_pages_path(project.namespace, project) + + expect(page).to have_text('Only the project owner can remove pages') + end + end + + it_behaves_like 'no pages deployed' + end +end From c3e483b05b11e862dfe14104c8ba63cbdb11d953 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 23 Aug 2016 21:47:22 +0800 Subject: [PATCH 150/183] Stub to enable it so that we could test this --- spec/features/projects/pages_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index acd7a1abb9a..11793c0f303 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -6,6 +6,8 @@ feature 'Pages', feature: true do given(:role) { :master } background do + allow(Gitlab.config.pages).to receive(:enabled).and_return(true) + project.team << [user, role] login_as(user) From 109553afd0ba80d47a282eb5e68ce3b5eda1d818 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 7 Jun 2016 16:40:15 +0200 Subject: [PATCH 151/183] Fix EE specs after ci_commit rename to pipeline --- app/services/projects/update_pages_service.rb | 2 +- features/steps/project/pages.rb | 12 ++++++------ spec/services/projects/update_pages_service_spec.rb | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index 45a28b46d06..f588f6feb8c 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -52,7 +52,7 @@ module Projects def create_status GenericCommitStatus.new( project: project, - commit: build.commit, + pipeline: build.pipeline, user: build.user, ref: build.ref, stage: 'deploy', diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb index 34f97f1ea8b..a76672168fb 100644 --- a/features/steps/project/pages.rb +++ b/features/steps/project/pages.rb @@ -27,13 +27,13 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps end step 'pages are deployed' do - commit = @project.ensure_ci_commit(@project.commit('HEAD').sha) + pipeline = @project.ensure_pipeline(@project.commit('HEAD').sha, 'HEAD') build = build(:ci_build, - project: @project, - commit: commit, - ref: 'HEAD', - artifacts_file: fixture_file_upload(Rails.root + 'spec/fixtures/pages.zip'), - artifacts_metadata: fixture_file_upload(Rails.root + 'spec/fixtures/pages.zip.meta') + project: @project, + pipeline: pipeline, + ref: 'HEAD', + artifacts_file: fixture_file_upload(Rails.root + 'spec/fixtures/pages.zip'), + artifacts_metadata: fixture_file_upload(Rails.root + 'spec/fixtures/pages.zip.meta') ) result = ::Projects::UpdatePagesService.new(@project, build).execute expect(result[:status]).to eq(:success) diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index 51da582c497..4eac7875864 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -2,8 +2,8 @@ require "spec_helper" describe Projects::UpdatePagesService do let(:project) { create :project } - let(:commit) { create :ci_commit, project: project, sha: project.commit('HEAD').sha } - let(:build) { create :ci_build, commit: commit, ref: 'HEAD' } + let(:pipeline) { create :ci_pipeline, project: project, sha: project.commit('HEAD').sha } + let(:build) { create :ci_build, pipeline: pipeline, ref: 'HEAD' } let(:invalid_file) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png') } subject { described_class.new(project, build) } @@ -47,7 +47,7 @@ describe Projects::UpdatePagesService do end it 'fails if sha on branch is not latest' do - commit.update_attributes(sha: 'old_sha') + pipeline.update_attributes(sha: 'old_sha') build.update_attributes(artifacts_file: file) expect(execute).to_not eq(:success) end From 8fd926fe862fdaa5f44c11c2184c7e2734e5e173 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 2 Sep 2016 22:33:39 +0800 Subject: [PATCH 152/183] Project#ensure_pipeline changed the args order --- features/steps/project/pages.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb index a76672168fb..c80c6273807 100644 --- a/features/steps/project/pages.rb +++ b/features/steps/project/pages.rb @@ -27,7 +27,7 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps end step 'pages are deployed' do - pipeline = @project.ensure_pipeline(@project.commit('HEAD').sha, 'HEAD') + pipeline = @project.ensure_pipeline('HEAD', @project.commit('HEAD').sha) build = build(:ci_build, project: @project, pipeline: pipeline, From d8ae09229a7798450263534a486c9d11b087d816 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 19 Nov 2016 19:20:05 +0100 Subject: [PATCH 153/183] Remove Pages ToC from docs [ci skip] --- doc/pages/administration.md | 37 +++---------------------------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 68b9003a420..2ef46932c13 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -14,39 +14,6 @@ configuration. If you are looking for ways to upload your static content in GitLab Pages, you probably want to read the [user documentation](README.md). -[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 -[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 - ---- - - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [The GitLab Pages daemon](#the-gitlab-pages-daemon) - - [The GitLab Pages daemon and the case of custom domains](#the-gitlab-pages-daemon-and-the-case-of-custom-domains) - - [Install the Pages daemon](#install-the-pages-daemon) -- [Configuration](#configuration) - - [Configuration prerequisites](#configuration-prerequisites) - - [Configuration scenarios](#configuration-scenarios) - - [DNS configuration](#dns-configuration) -- [Setting up GitLab Pages](#setting-up-gitlab-pages) - - [Custom domains with HTTPS support](#custom-domains-with-https-support) - - [Custom domains without HTTPS support](#custom-domains-without-https-support) - - [Wildcard HTTP domain without custom domains](#wildcard-http-domain-without-custom-domains) - - [Wildcard HTTPS domain without custom domains](#wildcard-https-domain-without-custom-domains) -- [NGINX configuration](#nginx-configuration) - - [NGINX configuration files](#nginx-configuration-files) - - [NGINX configuration for custom domains](#nginx-configuration-for-custom-domains) - - [NGINX caveats](#nginx-caveats) -- [Set maximum pages size](#set-maximum-pages-size) -- [Change storage path](#change-storage-path) -- [Backup](#backup) -- [Security](#security) -- [Changelog](#changelog) - - - ## The GitLab Pages daemon Starting from GitLab EE 8.5, GitLab Pages make use of the [GitLab Pages daemon], @@ -522,6 +489,8 @@ No new changes. [8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md [8-3-omnidocs]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/8-3-stable-ee/doc/settings/pages.md +[backup]: ../../raketasks/backup_restore.md +[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 +[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 [reconfigure]: ../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure [restart]: ../../administration/restart_gitlab.md#installations-from-source -[backup]: ../../raketasks/backup_restore.md From baac53652c258241b45dbe77ba99b02f22ef020f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 29 Nov 2016 10:59:40 +0100 Subject: [PATCH 154/183] Clarify where the settings are in Pages docs Fixes https://gitlab.com/gitlab-org/gitlab-ee/issues/1333 [ci skip] --- doc/pages/README.md | 40 ++++++---------------------------------- 1 file changed, 6 insertions(+), 34 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 938311ebf95..e70ca7b48bd 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -12,40 +12,9 @@ With GitLab Pages you can host for free your static websites on GitLab. Combined with the power of [GitLab CI] and the help of [GitLab Runner] you can deploy static pages for your individual projects, your user or your group. -Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) for specific +Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlab-com) for specific information, if you are using GitLab.com to host your website. ---- - - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [Getting started with GitLab Pages](#getting-started-with-gitlab-pages) - - [GitLab Pages requirements](#gitlab-pages-requirements) - - [User or group Pages](#user-or-group-pages) - - [Project Pages](#project-pages) - - [Explore the contents of `.gitlab-ci.yml`](#explore-the-contents-of-gitlab-ciyml) - - [How `.gitlab-ci.yml` looks like when the static content is in your repository](#how-gitlab-ciyml-looks-like-when-the-static-content-is-in-your-repository) - - [How `.gitlab-ci.yml` looks like when using a static generator](#how-gitlab-ciyml-looks-like-when-using-a-static-generator) - - [How to set up GitLab Pages in a repository where there's also actual code](#how-to-set-up-gitlab-pages-in-a-repository-where-theres-also-actual-code) -- [Next steps](#next-steps) - - [Example projects](#example-projects) - - [Add a custom domain to your Pages website](#add-a-custom-domain-to-your-pages-website) - - [Secure your custom domain website with TLS](#secure-your-custom-domain-website-with-tls) - - [Custom error codes pages](#custom-error-codes-pages) - - [Remove the contents of your pages](#remove-the-contents-of-your-pages) -- [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) -- [Limitations](#limitations) -- [Redirects in GitLab Pages](#redirects-in-gitlab-pages) -- [Frequently Asked Questions](#frequently-asked-questions) - - [Can I download my generated pages?](#can-i-download-my-generated-pages) - - [Can I use GitLab Pages if my project is private?](#can-i-use-gitlab-pages-if-my-project-is-private) - - [Do I need to create a user/group website before creating a project website?](#do-i-need-to-create-a-usergroup-website-before-creating-a-project-website) -- [Known issues](#known-issues) - - - ## Getting started with GitLab Pages > **Note:** @@ -335,7 +304,8 @@ Visit the GitLab Pages group for a full list of example projects: ### Add a custom domain to your Pages website If this setting is enabled by your GitLab administrator, you should be able to -see the **New Domain** button when visiting your project's **Settings > Pages**. +see the **New Domain** button when visiting your project's settings through the +gear icon in the top right and then navigating to **Pages**. ![New domain button](img/pages_new_domain_button.png) @@ -392,7 +362,9 @@ If the case of `404.html`, there are different scenarios. For example: ### Remove the contents of your pages If you ever feel the need to purge your Pages content, you can do so by going -to your project's **Settings > Pages** and hit **Remove pages**. Simple as that. +to your project's settings through the gear icon in the top right, and then +navigating to **Pages**. Hit the **Remove pages** button and your Pages website +will be deleted. Simple as that. ![Remove pages](img/pages_remove.png) From f6c66f4567f6818ec4678dcfb3e543d478f9dc36 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Thu, 15 Dec 2016 20:01:55 +0000 Subject: [PATCH 155/183] Fix reconfigure link on doc/pages/administration.md --- doc/pages/administration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 2ef46932c13..00100466ce2 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -492,5 +492,5 @@ No new changes. [backup]: ../../raketasks/backup_restore.md [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 [ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 -[reconfigure]: ../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure +[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure [restart]: ../../administration/restart_gitlab.md#installations-from-source From 80f9794c3e05362c2b06978d8a664ebf4e0ae0a6 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Sat, 17 Dec 2016 00:52:06 +0000 Subject: [PATCH 156/183] Fix restart link on doc/pages/administration.md --- doc/pages/administration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 00100466ce2..07b603f78c7 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -489,8 +489,8 @@ No new changes. [8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md [8-3-omnidocs]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/8-3-stable-ee/doc/settings/pages.md -[backup]: ../../raketasks/backup_restore.md +[backup]: ../raketasks/backup_restore.md [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 [ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 [reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure -[restart]: ../../administration/restart_gitlab.md#installations-from-source +[restart]: ../administration/restart_gitlab.md#installations-from-source From 86f4767dc1afea9f0744e4fb0c5ce663bf7e3de8 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 23 Dec 2016 00:26:33 +0530 Subject: [PATCH 157/183] Fix 500 error while navigating to the `pages_domains` 'show' page. ================== = Implementation = ================== 1. The path of the page is of the form 'group/project/pages/domains/' 2. Rails looks at `params[:id]` (which should be the domain name), and finds the relevant model record. 3. Given a domain like `foo.bar`, Rails sets `params[:id]` to `foo` (should be `foo.bar`), and sets `params[:format]` to `bar` 4. This commit fixes the issue by adding a route constraint, so that `params[:id]` is set to the entire `foo.bar` domain name. ========= = Tests = ========= 1. Add controller specs for the `PagesDomainController`. These are slightly orthogonal to this bug fix (they don't fail when this bug is present), but should be present nonetheless. 2. Add routing specs that catch this bug (by asserting that the `id` param is passed as expected when it contains a domain name). 3. Modify the 'RESTful project resources' routing spec shared example to accomodate controllers where the controller path (such as `pages/domains`) is different from the controller name (such as `pages_domains`). --- config/routes/project.rb | 2 +- .../projects/pages_domains_controller_spec.rb | 64 +++++++++++++++++++ spec/routing/project_routing_spec.rb | 37 +++++++++-- 3 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 spec/controllers/projects/pages_domains_controller_spec.rb diff --git a/config/routes/project.rb b/config/routes/project.rb index ea3bfdd45e6..b6b432256df 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -40,7 +40,7 @@ constraints(ProjectUrlConstrainer.new) do end resource :pages, only: [:show, :destroy] do - resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains' + resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains', constraints: { id: /[^\/]+/ } end resources :compare, only: [:index, :create] do diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb new file mode 100644 index 00000000000..2362df895a8 --- /dev/null +++ b/spec/controllers/projects/pages_domains_controller_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +describe Projects::PagesDomainsController do + let(:user) { create(:user) } + let(:project) { create(:project) } + + let(:request_params) do + { + namespace_id: project.namespace, + project_id: project + } + end + + before do + sign_in(user) + project.team << [user, :master] + end + + describe 'GET show' do + let!(:pages_domain) { create(:pages_domain, project: project) } + + it "displays the 'show' page" do + get(:show, request_params.merge(id: pages_domain.domain)) + + expect(response).to have_http_status(200) + expect(response).to render_template('show') + end + end + + describe 'GET new' do + it "displays the 'new' page" do + get(:new, request_params) + + expect(response).to have_http_status(200) + expect(response).to render_template('new') + end + end + + describe 'POST create' do + let(:pages_domain_params) do + build(:pages_domain, :with_certificate, :with_key).slice(:key, :certificate, :domain) + end + + it "creates a new pages domain" do + expect do + post(:create, request_params.merge(pages_domain: pages_domain_params)) + end.to change { PagesDomain.count }.by(1) + + expect(response).to redirect_to(namespace_project_pages_path(project.namespace, project)) + end + end + + describe 'DELETE destroy' do + let!(:pages_domain) { create(:pages_domain, project: project) } + + it "deletes the pages domain" do + expect do + delete(:destroy, request_params.merge(id: pages_domain.domain)) + end.to change { PagesDomain.count }.by(-1) + + expect(response).to redirect_to(namespace_project_pages_path(project.namespace, project)) + end + end +end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 77549db2927..43be785ad9d 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -27,35 +27,42 @@ describe 'project routing' do # let(:actions) { [:index] } # let(:controller) { 'issues' } # end + # + # # Different controller name and path + # it_behaves_like 'RESTful project resources' do + # let(:controller) { 'pages_domains' } + # let(:controller_path) { 'pages/domains' } + # end shared_examples 'RESTful project resources' do let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] } + let(:controller_path) { controller } it 'to #index' do - expect(get("/gitlab/gitlabhq/#{controller}")).to route_to("projects/#{controller}#index", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:index) + expect(get("/gitlab/gitlabhq/#{controller_path}")).to route_to("projects/#{controller}#index", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:index) end it 'to #create' do - expect(post("/gitlab/gitlabhq/#{controller}")).to route_to("projects/#{controller}#create", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:create) + expect(post("/gitlab/gitlabhq/#{controller_path}")).to route_to("projects/#{controller}#create", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:create) end it 'to #new' do - expect(get("/gitlab/gitlabhq/#{controller}/new")).to route_to("projects/#{controller}#new", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:new) + expect(get("/gitlab/gitlabhq/#{controller_path}/new")).to route_to("projects/#{controller}#new", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:new) end it 'to #edit' do - expect(get("/gitlab/gitlabhq/#{controller}/1/edit")).to route_to("projects/#{controller}#edit", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:edit) + expect(get("/gitlab/gitlabhq/#{controller_path}/1/edit")).to route_to("projects/#{controller}#edit", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:edit) end it 'to #show' do - expect(get("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#show", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:show) + expect(get("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#show", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:show) end it 'to #update' do - expect(put("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#update", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:update) + expect(put("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#update", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:update) end it 'to #destroy' do - expect(delete("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#destroy", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:destroy) + expect(delete("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#destroy", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:destroy) end end @@ -539,4 +546,20 @@ describe 'project routing' do 'projects/avatars#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq') end end + + describe Projects::PagesDomainsController, 'routing' do + it_behaves_like 'RESTful project resources' do + let(:actions) { [:show, :new, :create, :destroy] } + let(:controller) { 'pages_domains' } + let(:controller_path) { 'pages/domains' } + end + + it 'to #destroy with a valid domain name' do + expect(delete('/gitlab/gitlabhq/pages/domains/my.domain.com')).to route_to('projects/pages_domains#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'my.domain.com') + end + + it 'to #show with a valid domain' do + expect(get('/gitlab/gitlabhq/pages/domains/my.domain.com')).to route_to('projects/pages_domains#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'my.domain.com') + end + end end From 66bfc9e9e78257d9e6e232b004f6152440ebe27b Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 17 Aug 2016 13:27:19 +0300 Subject: [PATCH 158/183] [CE->EE] Fix specs --- spec/services/pages_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/pages_service_spec.rb b/spec/services/pages_service_spec.rb index e6ad93358a0..254b4f688cf 100644 --- a/spec/services/pages_service_spec.rb +++ b/spec/services/pages_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe PagesService, services: true do let(:build) { create(:ci_build) } - let(:data) { Gitlab::BuildDataBuilder.build(build) } + let(:data) { Gitlab::DataBuilder::Build.build(build) } let(:service) { PagesService.new(data) } before do From 12d44272ec68e38760d5886b27546e8c13f7942a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 30 May 2016 10:11:46 +0200 Subject: [PATCH 159/183] Fix Rubocop offenses --- spec/models/pages_domain_spec.rb | 14 +++++++------- spec/services/pages_service_spec.rb | 4 ++-- .../services/projects/update_pages_service_spec.rb | 12 ++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index 0b95bf594c5..0cbea5be106 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -23,13 +23,13 @@ describe PagesDomain, models: true do context 'no domain' do let(:domain) { nil } - it { is_expected.to_not be_valid } + it { is_expected.not_to be_valid } end context 'invalid domain' do let(:domain) { '0123123' } - it { is_expected.to_not be_valid } + it { is_expected.not_to be_valid } end context 'domain from .example.com' do @@ -37,7 +37,7 @@ describe PagesDomain, models: true do before { allow(Settings.pages).to receive(:host).and_return('domain.com') } - it { is_expected.to_not be_valid } + it { is_expected.not_to be_valid } end end @@ -47,13 +47,13 @@ describe PagesDomain, models: true do context 'when only certificate is specified' do let(:domain) { build(:pages_domain, :with_certificate) } - it { is_expected.to_not be_valid } + it { is_expected.not_to be_valid } end context 'when only key is specified' do let(:domain) { build(:pages_domain, :with_key) } - it { is_expected.to_not be_valid } + it { is_expected.not_to be_valid } end context 'with matching key' do @@ -65,7 +65,7 @@ describe PagesDomain, models: true do context 'for not matching key' do let(:domain) { build(:pages_domain, :with_missing_chain, :with_key) } - it { is_expected.to_not be_valid } + it { is_expected.not_to be_valid } end end @@ -157,6 +157,6 @@ describe PagesDomain, models: true do subject { domain.certificate_text } # We test only existence of output, since the output is long - it { is_expected.to_not be_empty } + it { is_expected.not_to be_empty } end end diff --git a/spec/services/pages_service_spec.rb b/spec/services/pages_service_spec.rb index 254b4f688cf..b4215b2cd02 100644 --- a/spec/services/pages_service_spec.rb +++ b/spec/services/pages_service_spec.rb @@ -26,7 +26,7 @@ describe PagesService, services: true do before { build.status = status } it 'should not execute worker' do - expect(PagesWorker).to_not receive(:perform_async) + expect(PagesWorker).not_to receive(:perform_async) service.execute end end @@ -40,7 +40,7 @@ describe PagesService, services: true do end it 'should not execute worker' do - expect(PagesWorker).to_not receive(:perform_async) + expect(PagesWorker).not_to receive(:perform_async) service.execute end end diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index 4eac7875864..af1c6a5e7b5 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -34,7 +34,7 @@ describe Projects::UpdatePagesService do it 'limits pages size' do stub_application_setting(max_pages_size: 1) - expect(execute).to_not eq(:success) + expect(execute).not_to eq(:success) end it 'removes pages after destroy' do @@ -49,29 +49,29 @@ describe Projects::UpdatePagesService do it 'fails if sha on branch is not latest' do pipeline.update_attributes(sha: 'old_sha') build.update_attributes(artifacts_file: file) - expect(execute).to_not eq(:success) + expect(execute).not_to eq(:success) end it 'fails for empty file fails' do build.update_attributes(artifacts_file: empty_file) - expect(execute).to_not eq(:success) + expect(execute).not_to eq(:success) end end end it 'fails to remove project pages when no pages is deployed' do - expect(PagesWorker).to_not receive(:perform_in) + expect(PagesWorker).not_to receive(:perform_in) expect(project.pages_deployed?).to be_falsey project.destroy end it 'fails if no artifacts' do - expect(execute).to_not eq(:success) + expect(execute).not_to eq(:success) end it 'fails for invalid archive' do build.update_attributes(artifacts_file: invalid_file) - expect(execute).to_not eq(:success) + expect(execute).not_to eq(:success) end def execute From 91c07d16cd371a545a9ad089ccc0313c7a13bb5b Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 13 May 2016 12:51:52 +0200 Subject: [PATCH 160/183] Fixed Rubocop deprecation warnings --- app/services/projects/update_pages_service.rb | 2 +- spec/services/projects/update_pages_service_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index f588f6feb8c..90fff91dd9c 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -25,7 +25,7 @@ module Projects # Check if we did extract public directory archive_public_path = File.join(archive_path, 'public') - raise 'pages miss the public folder' unless Dir.exists?(archive_public_path) + raise 'pages miss the public folder' unless Dir.exist?(archive_public_path) raise 'pages are outdated' unless latest? deploy_page!(archive_public_path) diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index af1c6a5e7b5..411b22a0fb8 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -5,7 +5,7 @@ describe Projects::UpdatePagesService do let(:pipeline) { create :ci_pipeline, project: project, sha: project.commit('HEAD').sha } let(:build) { create :ci_build, pipeline: pipeline, ref: 'HEAD' } let(:invalid_file) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png') } - + subject { described_class.new(project, build) } before do @@ -18,7 +18,7 @@ describe Projects::UpdatePagesService do let(:empty_file) { fixture_file_upload(Rails.root + "spec/fixtures/pages_empty.#{format}") } let(:metadata) do filename = Rails.root + "spec/fixtures/pages.#{format}.meta" - fixture_file_upload(filename) if File.exists?(filename) + fixture_file_upload(filename) if File.exist?(filename) end before do @@ -73,7 +73,7 @@ describe Projects::UpdatePagesService do build.update_attributes(artifacts_file: invalid_file) expect(execute).not_to eq(:success) end - + def execute subject.execute[:status] end From 7163da6046c2b57f9e9cf3b83959a57763e2f460 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 13 Aug 2016 11:15:31 +0200 Subject: [PATCH 161/183] Fix GitLab Pages test failures --- app/services/projects/update_pages_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index 90fff91dd9c..c52f3d3e230 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -13,6 +13,7 @@ module Projects def execute # Create status notifying the deployment of pages @status = create_status + @status.enqueue! @status.run! raise 'missing pages artifacts' unless build.artifacts_file? From 6ba149279445bd376e145dab2d7fa58808031692 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 20 Dec 2016 11:24:44 +0000 Subject: [PATCH 162/183] Update validates_hostname to 1.0.6 to fix a bug in parsing hexadecimal-looking domain names --- Gemfile | 2 +- Gemfile.lock | 4 ++-- spec/models/pages_domain_spec.rb | 8 +++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index bc1b13c7331..49851aabe19 100644 --- a/Gemfile +++ b/Gemfile @@ -49,7 +49,7 @@ gem 'attr_encrypted', '~> 3.0.0' gem 'u2f', '~> 0.2.1' # GitLab Pages -gem 'validates_hostname', '~> 1.0.0' +gem 'validates_hostname', '~> 1.0.6' # Browser detection gem 'browser', '~> 2.2' diff --git a/Gemfile.lock b/Gemfile.lock index 6263b02b041..5736862e5ab 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -799,7 +799,7 @@ GEM get_process_mem (~> 0) unicorn (>= 4, < 6) uniform_notifier (1.10.0) - validates_hostname (1.0.5) + validates_hostname (1.0.6) activerecord (>= 3.0) activesupport (>= 3.0) version_sorter (2.1.0) @@ -1017,7 +1017,7 @@ DEPENDENCIES unf (~> 0.1.4) unicorn (~> 5.1.0) unicorn-worker-killer (~> 0.4.4) - validates_hostname (~> 1.0.0) + validates_hostname (~> 1.0.6) version_sorter (~> 2.1.0) virtus (~> 1.0.1) vmstat (~> 2.3.0) diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index 0cbea5be106..e6a4583a8fb 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -4,7 +4,7 @@ describe PagesDomain, models: true do describe 'associations' do it { is_expected.to belong_to(:project) } end - + describe :validate_domain do subject { build(:pages_domain, domain: domain) } @@ -20,6 +20,12 @@ describe PagesDomain, models: true do it { is_expected.to be_valid } end + context 'valid hexadecimal-looking domain' do + let(:domain) { '0x12345.com'} + + it { is_expected.to be_valid } + end + context 'no domain' do let(:domain) { nil } From 8a09a25185bb05a9360370f40a8f7ff50279e60b Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Nov 2016 11:51:00 +0000 Subject: [PATCH 163/183] small but mighty confusing typo --- doc/pages/administration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 07b603f78c7..0e1665fa832 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -246,7 +246,7 @@ Below are the four scenarios that are described in 1. Edit `/etc/gitlab/gitlab.rb`: ```ruby - pages_external_url "https://example.io" + pages_external_url "http://example.io" nginx['listen_addresses'] = ['1.1.1.1'] pages_nginx['enable'] = false gitlab_pages['external_http'] = '1.1.1.2:80' From 877c121cfcca1f9bb116ff0a8831c5a9096e2853 Mon Sep 17 00:00:00 2001 From: Rebecca Skinner Date: Mon, 28 Mar 2016 14:15:48 +0000 Subject: [PATCH 164/183] Fixed typo ("server" -> "serve") --- doc/pages/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index e70ca7b48bd..e427d7f283d 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -357,7 +357,7 @@ If the case of `404.html`, there are different scenarios. For example: - If you use user/group Pages (served under `/`) and try to access `/non/existing_file` GitLab Pages will try to serve `/404.html`. - If you use a custom domain and try to access `/non/existing_file`, GitLab - Pages will try to server only `/404.html`. + Pages will try to serve only `/404.html`. ### Remove the contents of your pages From 5075fb3bb7d0d91cec697095dc7c7803333a7ffb Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 28 Jun 2016 10:14:24 +0200 Subject: [PATCH 165/183] fix attr_encrypted in EE --- app/models/pages_domain.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index 2f4cded15c8..0b9ebf1ffe2 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -12,6 +12,7 @@ class PagesDomain < ActiveRecord::Base attr_encrypted :key, mode: :per_attribute_iv_and_salt, + insecure_mode: true, key: Gitlab::Application.secrets.db_key_base, algorithm: 'aes-256-cbc' From 94545e58f3be29f40fd4e20bb58324246a1f6a60 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 11 Aug 2016 18:30:18 +0300 Subject: [PATCH 166/183] Active tense test coverage in pages spec Ports change from https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/642 --- spec/services/pages_service_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/services/pages_service_spec.rb b/spec/services/pages_service_spec.rb index b4215b2cd02..aa63fe3a5c1 100644 --- a/spec/services/pages_service_spec.rb +++ b/spec/services/pages_service_spec.rb @@ -15,7 +15,7 @@ describe PagesService, services: true do context 'on success' do before { build.success } - it 'should execute worker' do + it 'executes worker' do expect(PagesWorker).to receive(:perform_async) service.execute end @@ -25,7 +25,7 @@ describe PagesService, services: true do context "on #{status}" do before { build.status = status } - it 'should not execute worker' do + it 'does not execute worker' do expect(PagesWorker).not_to receive(:perform_async) service.execute end @@ -39,7 +39,7 @@ describe PagesService, services: true do build.success end - it 'should not execute worker' do + it 'does not execute worker' do expect(PagesWorker).not_to receive(:perform_async) service.execute end From 9677b5387201b2530b5cd18fe2b0bc897eae581e Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Wed, 1 Feb 2017 22:43:36 +0000 Subject: [PATCH 167/183] Excluded pages_domains from import/export spec --- spec/lib/gitlab/import_export/all_models.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 7fb6829f582..6aa10a6509a 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -191,6 +191,7 @@ project: - environments - deployments - project_feature +- pages_domains - authorized_users - project_authorizations - route From 239743345a23c53b2c41509cefb288450d3bb563 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 26 Jan 2017 17:58:42 -0800 Subject: [PATCH 168/183] Fix GitLab Pages not refreshing upon new content Due to autoloading and Ruby scoping, the .update file was never being updated due to this error: ``` NoMethodError: undefined method `pages' for Projects::Settings:Module from /opt/gitlab/embedded/service/gitlab-rails/app/services/projects/update_pages_configuration_service.rb:50:in `pages_update_file' from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/instrumentation.rb:157:in `pages_update_file' from (irb):6 from /opt/gitlab/embedded/lib/ruby/gems/2.3.0/gems/railties-4.2.7.1/lib/rails/commands/console.rb:110:in `start' from /opt/gitlab/embedded/lib/ruby/gems/2.3.0/gems/railties-4.2.7.1/lib/rails/commands/console.rb:9:in `start' from /opt/gitlab/embedded/lib/ruby/gems/2.3.0/gems/railties-4.2.7.1/lib/rails/commands/commands_tasks.rb:68:in `console' from /opt/gitlab/embedded/lib/ruby/gems/2.3.0/gems/railties-4.2.7.1/lib/rails/commands/commands_tasks.rb:39:in `run_command!' from /opt/gitlab/embedded/lib/ruby/gems/2.3.0/gems/railties-4.2.7.1/lib/rails/commands.rb:17:in `' from bin/rails:9:in `require' ``` This error was caught and discarded quietly. This fix exercises this code and fixes the scope problem. Closes gitlab-com/infrastructure#1058 --- .../update_pages_configuration_service.rb | 2 +- ...update_pages_configuration_service_spec.rb | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 spec/services/projects/update_pages_configuration_service_spec.rb diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index 188847b5ad6..eb4809afa85 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -47,7 +47,7 @@ module Projects end def pages_update_file - File.join(Settings.pages.path, '.update') + File.join(::Settings.pages.path, '.update') end def update_file(file, data) diff --git a/spec/services/projects/update_pages_configuration_service_spec.rb b/spec/services/projects/update_pages_configuration_service_spec.rb new file mode 100644 index 00000000000..8b329bc21c3 --- /dev/null +++ b/spec/services/projects/update_pages_configuration_service_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Projects::UpdatePagesConfigurationService, services: true do + let(:project) { create(:empty_project) } + subject { described_class.new(project) } + + describe "#update" do + let(:file) { Tempfile.new('pages-test') } + + after do + file.close + file.unlink + end + + it 'updates the .update file' do + # Access this reference to ensure scoping works + Projects::Settings # rubocop:disable Lint/Void + expect(subject).to receive(:pages_config_file).and_return(file.path) + expect(subject).to receive(:reload_daemon).and_call_original + + expect(subject.execute).to eq({ status: :success }) + end + end +end From 7cacaf18de37987f15a3c41d1d022621c810d699 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 10 Jan 2017 15:46:47 +0000 Subject: [PATCH 169/183] Fix constant resolution in UpdatePagesService There is now a `Projects::Settings` module, for the members controller. Ensure that we get the actual settings, not that module. --- app/services/projects/update_pages_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index c52f3d3e230..f5f9ee88912 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -130,7 +130,7 @@ module Projects end def tmp_path - @tmp_path ||= File.join(Settings.pages.path, 'tmp') + @tmp_path ||= File.join(::Settings.pages.path, 'tmp') end def pages_path From e7d4b8a03068419794162ffcfa13703c09dbcd02 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 17 Jan 2017 19:47:42 -0500 Subject: [PATCH 170/183] First iteration on Pages refactoring --- doc/pages/administration.md | 364 ++++++++++++++++++------------------ 1 file changed, 182 insertions(+), 182 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 0e1665fa832..9a94282a229 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -1,8 +1,9 @@ # GitLab Pages Administration -> **Note:** -> This feature was first [introduced][ee-80] in GitLab EE 8.3. -> Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. +> **Notes:** +> - [Introduced][ee-80] in GitLab EE 8.3. +> - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. +> - GitLab Pages were ported to Community Edition in GitLab 8.16. --- @@ -14,33 +15,20 @@ configuration. If you are looking for ways to upload your static content in GitLab Pages, you probably want to read the [user documentation](README.md). -## The GitLab Pages daemon - -Starting from GitLab EE 8.5, GitLab Pages make use of the [GitLab Pages daemon], -a simple HTTP server written in Go that can listen on an external IP address -and provide support for custom domains and custom certificates. The GitLab -Pages Daemon supports dynamic certificates through SNI and exposes pages using -HTTP2 by default. - -Here is a brief list with what it is supported when using the pages daemon: - -- Multiple domains per-project -- One TLS certificate per-domain - - Validation of certificate - - Validation of certificate chain - - Validation of private key against certificate +## Overview +GitLab Pages makes use of the [GitLab Pages daemon], a simple HTTP server +written in Go that can listen on an external IP address and provide support for +custom domains and custom certificates. It supports dynamic certificates through +SNI and exposes pages using HTTP2 by default. You are encouraged to read its [README][pages-readme] to fully understand how it works. -[gitlab pages daemon]: https://gitlab.com/gitlab-org/gitlab-pages -[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md - -### The GitLab Pages daemon and the case of custom domains +--- In the case of custom domains, the Pages daemon needs to listen on ports `80` and/or `443`. For that reason, there is some flexibility in the way which you -can set it up, so you basically have three choices: +can set it up: 1. Run the pages daemon in the same server as GitLab, listening on a secondary IP 1. Run the pages daemon in a separate server. In that case, the @@ -53,68 +41,18 @@ can set it up, so you basically have three choices: pages will not be able to be served with user provided certificates. For HTTP it's OK to use HTTP or TCP load balancing. -In this document, we will proceed assuming the first option. Let's begin by -installing the pages daemon. +In this document, we will proceed assuming the first option. -### Install the Pages daemon +## Prerequisites -**Source installations** +Before proceeding with the Pages configuration, you will need to: -``` -cd /home/git -sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git -cd gitlab-pages -sudo -u git -H git checkout v0.2.1 -sudo -u git -H make -``` - -**Omnibus installations** - -The `gitlab-pages` daemon is included in the Omnibus package. - - -## Configuration - -There are multiple ways to set up GitLab Pages according to what URL scheme you -are willing to support. - -### Configuration prerequisites - -In the next section you will find all possible scenarios to choose from. - -In either scenario, you will need: - -1. To use the [GitLab Pages daemon](#the-gitlab-pages-daemon) -1. A separate domain -1. A separate Nginx configuration file which needs to be explicitly added in - the server under which GitLab EE runs (Omnibus does that automatically) -1. (Optional) A wildcard certificate for that domain if you decide to serve - pages under HTTPS -1. (Optional but recommended) [Shared runners](../ci/runners/README.md) so that - your users don't have to bring their own - -### Configuration scenarios - -Before proceeding with setting up GitLab Pages, you have to decide which route -you want to take. - -The possible scenarios are depicted in the table below. - -| URL scheme | Option | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | -| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| `http://page.example.io` | 1 | no | no | no | no | -| `https://page.example.io` | 1 | yes | no | no | no | -| `http://page.example.io` and `http://page.com` | 2 | no | yes | no | yes | -| `https://page.example.io` and `https://page.com` | 2 | yes | redirects to HTTPS | yes | yes | - -As you see from the table above, each URL scheme comes with an option: - -1. Pages enabled, daemon is enabled and NGINX will proxy all requests to the - daemon. Pages daemon doesn't listen to the outside world. -1. Pages enabled, daemon is enabled AND pages has external IP support enabled. - In that case, the pages daemon is running, NGINX still proxies requests to - the daemon but the daemon is also able to receive requests from the outside - world. Custom domains and TLS are supported. +1. Have a separate domain under which the GitLab Pages will be served +1. (Optional) Have a wildcard certificate for that domain if you decide to serve + Pages under HTTPS +1. Configure a wildcard DNS record +1. (Optional but recommended) Enable [Shared runners](../ci/runners/README.md) + so that your users don't have to bring their own ### DNS configuration @@ -129,21 +67,39 @@ host that GitLab runs. For example, an entry would look like this: where `example.io` is the domain under which GitLab Pages will be served and `1.2.3.4` is the IP address of your GitLab instance. +> **Note:** You should not use the GitLab domain to serve user pages. For more information see the [security section](#security). [wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record -## Setting up GitLab Pages +## Configuration -Below are the four scenarios that are described in -[#configuration-scenarios](#configuration-scenarios). +Depending on your needs, you can install GitLab Pages in four different ways. -### Custom domains with HTTPS support +### Option 1. Custom domains with HTTPS support + +| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `https://page.example.io` and `https://page.com` | yes | redirects to HTTPS | yes | yes | + +Pages enabled, daemon is enabled AND pages has external IP support enabled. +In that case, the pages daemon is running, NGINX still proxies requests to +the daemon but the daemon is also able to receive requests from the outside +world. Custom domains and TLS are supported. **Source installations:** -1. [Install the pages daemon](#install-the-pages-daemon) +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` + 1. Edit `gitlab.yml` to look like the example below. You need to change the `host` to the FQDN under which GitLab Pages will be served. Set `external_http` and `external_https` to the secondary IP on which the pages @@ -176,7 +132,19 @@ Below are the four scenarios that are described in gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80 -listen-https 1.1.1.1:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key ``` -1. Make sure to [configure NGINX](#nginx-configuration) properly. +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace + `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + listens to. +1. Restart NGINX 1. [Restart GitLab][restart] --- @@ -197,17 +165,32 @@ Below are the four scenarios that are described in where `1.1.1.1` is the primary IP address that GitLab is listening to and `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to. - Read more at the - [NGINX configuration for custom domains](#nginx-configuration-for-custom-domains) - section. 1. [Reconfigure GitLab][reconfigure] -### Custom domains without HTTPS support +### Option 2. Custom domains without HTTPS support + +| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `http://page.example.io` and `http://page.com` | no | yes | no | yes | + +Pages enabled, daemon is enabled AND pages has external IP support enabled. +In that case, the pages daemon is running, NGINX still proxies requests to +the daemon but the daemon is also able to receive requests from the outside +world. Custom domains and TLS are supported. **Source installations:** -1. [Install the pages daemon](#install-the-pages-daemon) +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` + 1. Edit `gitlab.yml` to look like the example below. You need to change the `host` to the FQDN under which GitLab Pages will be served. Set `external_http` to the secondary IP on which the pages daemon will listen @@ -236,7 +219,19 @@ Below are the four scenarios that are described in gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" ``` -1. Make sure to [configure NGINX](#nginx-configuration) properly. +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace + `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + listens to. +1. Restart NGINX 1. [Restart GitLab][restart] --- @@ -254,58 +249,29 @@ Below are the four scenarios that are described in where `1.1.1.1` is the primary IP address that GitLab is listening to and `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to. - Read more at the - [NGINX configuration for custom domains](#nginx-configuration-for-custom-domains) - section. 1. [Reconfigure GitLab][reconfigure] -### Wildcard HTTP domain without custom domains +### Option 3. Wildcard HTTPS domain without custom domains + +| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `https://page.example.io` | yes | no | no | no | + +Pages enabled, daemon is enabled and NGINX will proxy all requests to the +daemon. Pages daemon doesn't listen to the outside world. **Source installations:** -1. [Install the pages daemon](#install-the-pages-daemon) -1. Go to the GitLab installation directory: +1. Install the Pages daemon: - ```bash - cd /home/git/gitlab - ``` - -1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and - the `host` to the FQDN under which GitLab Pages will be served: - - ```yaml - ## GitLab Pages - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 80 - https: false - ``` - -1. Make sure to [configure NGINX](#nginx-configuration) properly. -1. [Restart GitLab][restart] - ---- - -**Omnibus installations:** - -1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: - - ```ruby - pages_external_url 'http://example.io' ``` - -1. [Reconfigure GitLab][reconfigure] - -### Wildcard HTTPS domain without custom domains - -**Source installations:** - -1. [Install the pages daemon](#install-the-pages-daemon) + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` 1. In `gitlab.yml`, set the port to `443` and https to `true`: ```bash @@ -320,7 +286,14 @@ Below are the four scenarios that are described in https: true ``` -1. Make sure to [configure NGINX](#nginx-configuration) properly. +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. --- @@ -342,49 +315,76 @@ Below are the four scenarios that are described in 1. [Reconfigure GitLab][reconfigure] -## NGINX configuration +### Option 4. Wildcard HTTP domain without custom domains -Depending on your setup, you will need to make some changes to NGINX. -Specifically you must change the domain name and the IP address where NGINX -listens to. Read the following sections for more details. +| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `http://page.example.io` | no | no | no | no | -### NGINX configuration files - -Copy the `gitlab-pages-ssl` Nginx configuration file: - -```bash -sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf -sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf -``` - -Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - -### NGINX configuration for custom domains - -> If you are not using custom domains ignore this section. - -[In the case of custom domains](#the-gitlab-pages-daemon-and-the-case-of-custom-domains), -if you have the secondary IP address configured on the same server as GitLab, -you need to change **all** NGINX configs to listen on the first IP address. +Pages enabled, daemon is enabled and NGINX will proxy all requests to the +daemon. Pages daemon doesn't listen to the outside world. **Source installations:** -1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace - `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab - listens to. +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` + +1. Go to the GitLab installation directory: + + ```bash + cd /home/git/gitlab + ``` + +1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and + the `host` to the FQDN under which GitLab Pages will be served: + + ```yaml + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 80 + https: false + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + 1. Restart NGINX +1. [Restart GitLab][restart] + +--- **Omnibus installations:** -1. Edit `/etc/gitlab/gilab.rb`: +1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: - ``` - nginx['listen_addresses'] = ['1.1.1.1'] + ```ruby + pages_external_url 'http://example.io' ``` 1. [Reconfigure GitLab][reconfigure] -### NGINX caveats +## NGINX caveats + +>**Note:** +The following information applies only for installations from source. Be extra careful when setting up the domain name in the NGINX config. You must not remove the backslashes. @@ -462,35 +462,35 @@ latest previous version. --- +**GitLab 8.16 ([documentation][8-16-docs])** + +- GitLab Pages were ported to Community Edition in GitLab 8.16. +- Documentation was refactored to be more modular and easy to follow. + **GitLab 8.5 ([documentation][8-5-docs])** - In GitLab 8.5 we introduced the [gitlab-pages][] daemon which is now the recommended way to set up GitLab Pages. - The [NGINX configs][] have changed to reflect this change. So make sure to update them. -- Custom CNAME and TLS certificates support -- Documentation was moved to one place - -[8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md -[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.1 -[NGINX configs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/8-5-stable-ee/lib/support/nginx +- Custom CNAME and TLS certificates support. +- Documentation was moved to one place. --- -**GitLab 8.4** - -No new changes. - ---- - -**GitLab 8.3 ([source docs][8-3-docs], [Omnibus docs][8-3-omnidocs])** +**GitLab 8.3 ([documentation][8-3-docs])** - GitLab Pages feature was introduced. [8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md -[8-3-omnidocs]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/8-3-stable-ee/doc/settings/pages.md +[8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md +[8-16-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-16-stable-ce/doc/pages/administration.md [backup]: ../raketasks/backup_restore.md [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 [ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 +[gitlab pages daemon]: https://gitlab.com/gitlab-org/gitlab-pages +[NGINX configs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/8-5-stable-ee/lib/support/nginx +[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md [reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure [restart]: ../administration/restart_gitlab.md#installations-from-source +[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.1 From b14ee42ffad4b5f47a9c440d8467677b1f41ce06 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 18 Jan 2017 12:46:52 -0500 Subject: [PATCH 171/183] Move Pages docs to new location --- doc/README.md | 4 +- .../high_availability/load_balancer.md | 2 +- doc/administration/pages/index.md | 497 ++++++++++++++++++ doc/install/installation.md | 2 +- doc/pages/README.md | 436 +-------------- doc/pages/administration.md | 497 +----------------- doc/university/README.md | 2 +- doc/university/support/README.md | 2 +- .../pages/img/pages_create_project.png | Bin .../pages/img/pages_create_user_page.png | Bin .../project}/pages/img/pages_dns_details.png | Bin .../pages/img/pages_multiple_domains.png | Bin .../pages/img/pages_new_domain_button.png | Bin .../project}/pages/img/pages_remove.png | Bin .../project}/pages/img/pages_upload_cert.png | Bin doc/user/project/pages/index.md | 435 +++++++++++++++ 16 files changed, 940 insertions(+), 937 deletions(-) create mode 100644 doc/administration/pages/index.md rename doc/{ => user/project}/pages/img/pages_create_project.png (100%) rename doc/{ => user/project}/pages/img/pages_create_user_page.png (100%) rename doc/{ => user/project}/pages/img/pages_dns_details.png (100%) rename doc/{ => user/project}/pages/img/pages_multiple_domains.png (100%) rename doc/{ => user/project}/pages/img/pages_new_domain_button.png (100%) rename doc/{ => user/project}/pages/img/pages_remove.png (100%) rename doc/{ => user/project}/pages/img/pages_upload_cert.png (100%) create mode 100644 doc/user/project/pages/index.md diff --git a/doc/README.md b/doc/README.md index 951a302f8ba..6e94f1e8e87 100644 --- a/doc/README.md +++ b/doc/README.md @@ -12,7 +12,7 @@ - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. - [Container Registry](user/project/container_registry.md) Learn how to use GitLab Container Registry. - [GitLab basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. -- [GitLab Pages](pages/README.md) Using GitLab Pages. +- [GitLab Pages](user/project/pages/index.md) Using GitLab Pages. - [Importing to GitLab](workflow/importing/README.md) Import your projects from GitHub, Bitbucket, GitLab.com, FogBugz and SVN into GitLab. - [Importing and exporting projects between instances](user/project/settings/import_export.md). - [Markdown](user/markdown.md) GitLab's advanced formatting system. @@ -54,7 +54,7 @@ - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE. - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. -- [GitLab Pages configuration](pages/administration.md) +- [GitLab Pages configuration](administration/pages/index.md) Configure GitLab Pages. - [GitLab performance monitoring with InfluxDB](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. - [GitLab performance monitoring with Prometheus](administration/monitoring/performance/prometheus.md) Configure GitLab and Prometheus for measuring performance metrics. - [Request Profiling](administration/monitoring/performance/request_profiling.md) Get a detailed profile on slow requests. diff --git a/doc/administration/high_availability/load_balancer.md b/doc/administration/high_availability/load_balancer.md index 1824829903c..dad8e956c0e 100644 --- a/doc/administration/high_availability/load_balancer.md +++ b/doc/administration/high_availability/load_balancer.md @@ -66,4 +66,4 @@ Read more on high-availability configuration: configure custom domains with custom SSL, which would not be possible if SSL was terminated at the load balancer. -[gitlab-pages]: http://docs.gitlab.com/ee/pages/administration.html +[gitlab-pages]: ../pages/index.md diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md new file mode 100644 index 00000000000..da148a0f2bb --- /dev/null +++ b/doc/administration/pages/index.md @@ -0,0 +1,497 @@ +# GitLab Pages Administration + +> **Notes:** +> - [Introduced][ee-80] in GitLab EE 8.3. +> - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. +> - GitLab Pages were ported to Community Edition in GitLab 8.16. + +--- + +This document describes how to set up the _latest_ GitLab Pages feature. Make +sure to read the [changelog](#changelog) if you are upgrading to a new GitLab +version as it may include new features and changes needed to be made in your +configuration. + +If you are looking for ways to upload your static content in GitLab Pages, you +probably want to read the [user documentation][pages-userguide]. + +## Overview + +GitLab Pages makes use of the [GitLab Pages daemon], a simple HTTP server +written in Go that can listen on an external IP address and provide support for +custom domains and custom certificates. It supports dynamic certificates through +SNI and exposes pages using HTTP2 by default. +You are encouraged to read its [README][pages-readme] to fully understand how +it works. + +--- + +In the case of custom domains, the Pages daemon needs to listen on ports `80` +and/or `443`. For that reason, there is some flexibility in the way which you +can set it up: + +1. Run the pages daemon in the same server as GitLab, listening on a secondary IP +1. Run the pages daemon in a separate server. In that case, the + [Pages path](#change-storage-path) must also be present in the server that + the pages daemon is installed, so you will have to share it via network. +1. Run the pages daemon in the same server as GitLab, listening on the same IP + but on different ports. In that case, you will have to proxy the traffic with + a loadbalancer. If you choose that route note that you should use TCP load + balancing for HTTPS. If you use TLS-termination (HTTPS-load balancing) the + pages will not be able to be served with user provided certificates. For + HTTP it's OK to use HTTP or TCP load balancing. + +In this document, we will proceed assuming the first option. + +## Prerequisites + +Before proceeding with the Pages configuration, you will need to: + +1. Have a separate domain under which the GitLab Pages will be served +1. (Optional) Have a wildcard certificate for that domain if you decide to serve + Pages under HTTPS +1. Configure a wildcard DNS record +1. (Optional but recommended) Enable [Shared runners](../ci/runners/README.md) + so that your users don't have to bring their own + +### DNS configuration + +GitLab Pages expect to run on their own virtual host. In your DNS server/provider +you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the +host that GitLab runs. For example, an entry would look like this: + +``` +*.example.io. 1800 IN A 1.2.3.4 +``` + +where `example.io` is the domain under which GitLab Pages will be served +and `1.2.3.4` is the IP address of your GitLab instance. + +> **Note:** +You should not use the GitLab domain to serve user pages. For more information +see the [security section](#security). + +[wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record + +## Configuration + +Depending on your needs, you can install GitLab Pages in four different ways. + +### Option 1. Custom domains with HTTPS support + +| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `https://page.example.io` and `https://page.com` | yes | redirects to HTTPS | yes | yes | + +Pages enabled, daemon is enabled AND pages has external IP support enabled. +In that case, the pages daemon is running, NGINX still proxies requests to +the daemon but the daemon is also able to receive requests from the outside +world. Custom domains and TLS are supported. + +**Source installations:** + +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` + +1. Edit `gitlab.yml` to look like the example below. You need to change the + `host` to the FQDN under which GitLab Pages will be served. Set + `external_http` and `external_https` to the secondary IP on which the pages + daemon will listen for connections: + + ```yaml + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 443 + https: true + + external_http: 1.1.1.1:80 + external_https: 1.1.1.1:443 + ``` + +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain`, `-listen-http` and `-listen-https` must match the `host`, + `external_http` and `external_https` settings that you set above respectively. + The `-root-cert` and `-root-key` settings are the wildcard TLS certificates + of the `example.io` domain: + + ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80 -listen-https 1.1.1.1:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace + `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + listens to. +1. Restart NGINX +1. [Restart GitLab][restart] + +--- + +**Omnibus installations:** + +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + pages_external_url "https://example.io" + nginx['listen_addresses'] = ['1.1.1.1'] + pages_nginx['enable'] = false + gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt" + gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key" + gitlab_pages['external_http'] = '1.1.1.2:80' + gitlab_pages['external_https'] = '1.1.1.2:443' + ``` + + where `1.1.1.1` is the primary IP address that GitLab is listening to and + `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to. + +1. [Reconfigure GitLab][reconfigure] + +### Option 2. Custom domains without HTTPS support + +| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `http://page.example.io` and `http://page.com` | no | yes | no | yes | + +Pages enabled, daemon is enabled AND pages has external IP support enabled. +In that case, the pages daemon is running, NGINX still proxies requests to +the daemon but the daemon is also able to receive requests from the outside +world. Custom domains and TLS are supported. + +**Source installations:** + +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` + +1. Edit `gitlab.yml` to look like the example below. You need to change the + `host` to the FQDN under which GitLab Pages will be served. Set + `external_http` to the secondary IP on which the pages daemon will listen + for connections: + + ```yaml + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 80 + https: false + + external_http: 1.1.1.1:80 + ``` + +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain` and `-listen-http` must match the `host` and `external_http` + settings that you set above respectively: + + ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace + `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + listens to. +1. Restart NGINX +1. [Restart GitLab][restart] + +--- + +**Omnibus installations:** + +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + pages_external_url "http://example.io" + nginx['listen_addresses'] = ['1.1.1.1'] + pages_nginx['enable'] = false + gitlab_pages['external_http'] = '1.1.1.2:80' + ``` + + where `1.1.1.1` is the primary IP address that GitLab is listening to and + `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to. + +1. [Reconfigure GitLab][reconfigure] + +### Option 3. Wildcard HTTPS domain without custom domains + +| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `https://page.example.io` | yes | no | no | no | + +Pages enabled, daemon is enabled and NGINX will proxy all requests to the +daemon. Pages daemon doesn't listen to the outside world. + +**Source installations:** + +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` +1. In `gitlab.yml`, set the port to `443` and https to `true`: + + ```bash + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 443 + https: true + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +--- + +**Omnibus installations:** + +1. Place the certificate and key inside `/etc/gitlab/ssl` +1. In `/etc/gitlab/gitlab.rb` specify the following configuration: + + ```ruby + pages_external_url 'https://example.io' + + pages_nginx['redirect_http_to_https'] = true + pages_nginx['ssl_certificate'] = "/etc/gitlab/ssl/pages-nginx.crt" + pages_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/pages-nginx.key" + ``` + + where `pages-nginx.crt` and `pages-nginx.key` are the SSL cert and key, + respectively. + +1. [Reconfigure GitLab][reconfigure] + +### Option 4. Wildcard HTTP domain without custom domains + +| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `http://page.example.io` | no | no | no | no | + +Pages enabled, daemon is enabled and NGINX will proxy all requests to the +daemon. Pages daemon doesn't listen to the outside world. + +**Source installations:** + +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` + +1. Go to the GitLab installation directory: + + ```bash + cd /home/git/gitlab + ``` + +1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and + the `host` to the FQDN under which GitLab Pages will be served: + + ```yaml + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 80 + https: false + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Restart NGINX +1. [Restart GitLab][restart] + +--- + +**Omnibus installations:** + +1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: + + ```ruby + pages_external_url 'http://example.io' + ``` + +1. [Reconfigure GitLab][reconfigure] + +## NGINX caveats + +>**Note:** +The following information applies only for installations from source. + +Be extra careful when setting up the domain name in the NGINX config. You must +not remove the backslashes. + +If your GitLab pages domain is `example.io`, replace: + +```bash +server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$; +``` + +with: + +``` +server_name ~^.*\.example\.io$; +``` + +If you are using a subdomain, make sure to escape all dots (`.`) except from +the first one with a backslash (\). For example `pages.example.io` would be: + +``` +server_name ~^.*\.pages\.example\.io$; +``` + +## Set maximum pages size + +The maximum size of the unpacked archive per project can be configured in the +Admin area under the Application settings in the **Maximum size of pages (MB)**. +The default is 100MB. + +## Change storage path + +**Source installations:** + +1. Pages are stored by default in `/home/git/gitlab/shared/pages`. + If you wish to store them in another location you must set it up in + `gitlab.yml` under the `pages` section: + + ```yaml + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + path: /mnt/storage/pages + ``` + +1. [Restart GitLab][restart] + +**Omnibus installations:** + +1. Pages are stored by default in `/var/opt/gitlab/gitlab-rails/shared/pages`. + If you wish to store them in another location you must set it up in + `/etc/gitlab/gitlab.rb`: + + ```ruby + gitlab_rails['pages_path'] = "/mnt/storage/pages" + ``` + +1. [Reconfigure GitLab][reconfigure] + +## Backup + +Pages are part of the [regular backup][backup] so there is nothing to configure. + +## Security + +You should strongly consider running GitLab pages under a different hostname +than GitLab to prevent XSS attacks. + +## Changelog + +GitLab Pages were first introduced in GitLab EE 8.3. Since then, many features +where added, like custom CNAME and TLS support, and many more are likely to +come. Below is a brief changelog. If no changes were introduced or a version is +missing from the changelog, assume that the documentation is the same as the +latest previous version. + +--- + +**GitLab 8.16 ([documentation][8-16-docs])** + +- GitLab Pages were ported to Community Edition in GitLab 8.16. +- Documentation was refactored to be more modular and easy to follow. + +**GitLab 8.5 ([documentation][8-5-docs])** + +- In GitLab 8.5 we introduced the [gitlab-pages][] daemon which is now the + recommended way to set up GitLab Pages. +- The [NGINX configs][] have changed to reflect this change. So make sure to + update them. +- Custom CNAME and TLS certificates support. +- Documentation was moved to one place. + +--- + +**GitLab 8.3 ([documentation][8-3-docs])** + +- GitLab Pages feature was introduced. + +[8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md +[8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md +[8-16-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-16-stable-ce/doc/administration/pages/index.md +[backup]: ../raketasks/backup_restore.md +[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 +[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 +[gitlab pages daemon]: https://gitlab.com/gitlab-org/gitlab-pages +[NGINX configs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/8-5-stable-ee/lib/support/nginx +[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md +[pages-userguide]: ../../user/project/pages/index.md +[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure +[restart]: ../administration/restart_gitlab.md#installations-from-source +[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.1 diff --git a/doc/install/installation.md b/doc/install/installation.md index 4496243da25..276e7f6916e 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -489,7 +489,7 @@ Make sure to edit the config file to match your setup. Also, ensure that you mat If you intend to enable GitLab pages, there is a separate Nginx config you need to use. Read all about the needed configuration at the -[GitLab Pages administration guide](../pages/administration.md). +[GitLab Pages administration guide](../administration/pages/index.md). **Note:** If you want to use HTTPS, replace the `gitlab` Nginx config with `gitlab-ssl`. See [Using HTTPS](#using-https) for HTTPS configuration details. diff --git a/doc/pages/README.md b/doc/pages/README.md index e427d7f283d..44b74513fd9 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -1,435 +1 @@ -# GitLab Pages - -> **Note:** -> This feature was [introduced][ee-80] in GitLab EE 8.3. -> Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. - -> **Note:** -> This document is about the user guide. To learn how to enable GitLab Pages -> across your GitLab instance, visit the [administrator documentation](administration.md). - -With GitLab Pages you can host for free your static websites on GitLab. -Combined with the power of [GitLab CI] and the help of [GitLab Runner] you can -deploy static pages for your individual projects, your user or your group. - -Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlab-com) for specific -information, if you are using GitLab.com to host your website. - -## Getting started with GitLab Pages - -> **Note:** -> In the rest of this document we will assume that the general domain name that -> is used for GitLab Pages is `example.io`. - -In general there are two types of pages one might create: - -- Pages per user (`username.example.io`) or per group (`groupname.example.io`) -- Pages per project (`username.example.io/projectname` or `groupname.example.io/projectname`) - -In GitLab, usernames and groupnames are unique and we often refer to them -as namespaces. There can be only one namespace in a GitLab instance. Below you -can see the connection between the type of GitLab Pages, what the project name -that is created on GitLab looks like and the website URL it will be ultimately -be served on. - -| Type of GitLab Pages | The name of the project created in GitLab | Website URL | -| -------------------- | ------------ | ----------- | -| User pages | `username.example.io` | `http(s)://username.example.io` | -| Group pages | `groupname.example.io` | `http(s)://groupname.example.io` | -| Project pages owned by a user | `projectname` | `http(s)://username.example.io/projectname` | -| Project pages owned by a group | `projectname` | `http(s)://groupname.example.io/projectname`| - -> **Warning:** -> There are some known [limitations](#limitations) regarding namespaces served -> under the general domain name and HTTPS. Make sure to read that section. - -### GitLab Pages requirements - -In brief, this is what you need to upload your website in GitLab Pages: - -1. Find out the general domain name that is used for GitLab Pages - (ask your administrator). This is very important, so you should first make - sure you get that right. -1. Create a project -1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory - of your repository with a specific job named [`pages`][pages] -1. Set up a GitLab Runner to build your website - -> **Note:** -> If [shared runners](../ci/runners/README.md) are enabled by your GitLab -> administrator, you should be able to use them instead of bringing your own. - -### User or group Pages - -For user and group pages, the name of the project should be specific to the -username or groupname and the general domain name that is used for GitLab Pages. -Head over your GitLab instance that supports GitLab Pages and create a -repository named `username.example.io`, where `username` is your username on -GitLab. If the first part of the project name doesn't match exactly your -username, it won’t work, so make sure to get it right. - -To create a group page, the steps are the same like when creating a website for -users. Just make sure that you are creating the project within the group's -namespace. - -![Create a user-based pages project](img/pages_create_user_page.png) - ---- - -After you push some static content to your repository and GitLab Runner uploads -the artifacts to GitLab CI, you will be able to access your website under -`http(s)://username.example.io`. Keep reading to find out how. - ->**Note:** -If your username/groupname contains a dot, for example `foo.bar`, you will not -be able to use the wildcard domain HTTPS, read more at [limitations](#limitations). - -### Project Pages - -GitLab Pages for projects can be created by both user and group accounts. -The steps to create a project page for a user or a group are identical: - -1. Create a new project -1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory - of your repository with a specific job named [`pages`][pages]. -1. Set up a GitLab Runner to build your website - -A user's project will be served under `http(s)://username.example.io/projectname` -whereas a group's project under `http(s)://groupname.example.io/projectname`. - -### Explore the contents of `.gitlab-ci.yml` - -The key thing about GitLab Pages is the `.gitlab-ci.yml` file, something that -gives you absolute control over the build process. You can actually watch your -website being built live by following the CI build traces. - -> **Note:** -> Before reading this section, make sure you familiarize yourself with GitLab CI -> and the specific syntax of[`.gitlab-ci.yml`](../ci/yaml/README.md) by -> following our [quick start guide](../ci/quick_start/README.md). - -To make use of GitLab Pages, the contents of `.gitlab-ci.yml` must follow the -rules below: - -1. A special job named [`pages`][pages] must be defined -1. Any static content which will be served by GitLab Pages must be placed under - a `public/` directory -1. `artifacts` with a path to the `public/` directory must be defined - -In its simplest form, `.gitlab-ci.yml` looks like: - -```yaml -pages: - script: - - my_commands - artifacts: - paths: - - public -``` - -When the Runner reaches to build the `pages` job, it executes whatever is -defined in the `script` parameter and if the build completes with a non-zero -exit status, it then uploads the `public/` directory to GitLab Pages. - -The `public/` directory should contain all the static content of your website. -Depending on how you plan to publish your website, the steps defined in the -[`script` parameter](../ci/yaml/README.md#script) may differ. - -Be aware that Pages are by default branch/tag agnostic and their deployment -relies solely on what you specify in `.gitlab-ci.yml`. If you don't limit the -`pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), -whenever a new commit is pushed to whatever branch or tag, the Pages will be -overwritten. In the example below, we limit the Pages to be deployed whenever -a commit is pushed only on the `master` branch: - -```yaml -pages: - script: - - my_commands - artifacts: - paths: - - public - only: - - master -``` - -We then tell the Runner to treat the `public/` directory as `artifacts` and -upload it to GitLab. And since all these parameters were all under a `pages` -job, the contents of the `public` directory will be served by GitLab Pages. - -#### How `.gitlab-ci.yml` looks like when the static content is in your repository - -Supposedly your repository contained the following files: - -``` -├── index.html -├── css -│   └── main.css -└── js - └── main.js -``` - -Then the `.gitlab-ci.yml` example below simply moves all files from the root -directory of the project to the `public/` directory. The `.public` workaround -is so `cp` doesn't also copy `public/` to itself in an infinite loop: - -```yaml -pages: - script: - - mkdir .public - - cp -r * .public - - mv .public public - artifacts: - paths: - - public - only: - - master -``` - -#### How `.gitlab-ci.yml` looks like when using a static generator - -In general, GitLab Pages support any kind of [static site generator][staticgen], -since `.gitlab-ci.yml` can be configured to run any possible command. - -In the root directory of your Git repository, place the source files of your -favorite static generator. Then provide a `.gitlab-ci.yml` file which is -specific to your static generator. - -The example below, uses [Jekyll] to build the static site: - -```yaml -image: ruby:2.1 # the script will run in Ruby 2.1 using the Docker image ruby:2.1 - -pages: # the build job must be named pages - script: - - gem install jekyll # we install jekyll - - jekyll build -d public/ # we tell jekyll to build the site for us - artifacts: - paths: - - public # this is where the site will live and the Runner uploads it in GitLab - only: - - master # this script is only affecting the master branch -``` - -Here, we used the Docker executor and in the first line we specified the base -image against which our builds will run. - -You have to make sure that the generated static files are ultimately placed -under the `public` directory, that's why in the `script` section we run the -`jekyll` command that builds the website and puts all content in the `public/` -directory. Depending on the static generator of your choice, this command will -differ. Search in the documentation of the static generator you will use if -there is an option to explicitly set the output directory. If there is not -such an option, you can always add one more line under `script` to rename the -resulting directory in `public/`. - -We then tell the Runner to treat the `public/` directory as `artifacts` and -upload it to GitLab. - ---- - -See the [jekyll example project][pages-jekyll] to better understand how this -works. - -For a list of Pages projects, see the [example projects](#example-projects) to -get you started. - -#### How to set up GitLab Pages in a repository where there's also actual code - -Remember that GitLab Pages are by default branch/tag agnostic and their -deployment relies solely on what you specify in `.gitlab-ci.yml`. You can limit -the `pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), -whenever a new commit is pushed to a branch that will be used specifically for -your pages. - -That way, you can have your project's code in the `master` branch and use an -orphan branch (let's name it `pages`) that will host your static generator site. - -You can create a new empty branch like this: - -```bash -git checkout --orphan pages -``` - -The first commit made on this new branch will have no parents and it will be -the root of a new history totally disconnected from all the other branches and -commits. Push the source files of your static generator in the `pages` branch. - -Below is a copy of `.gitlab-ci.yml` where the most significant line is the last -one, specifying to execute everything in the `pages` branch: - -``` -image: ruby:2.1 - -pages: - script: - - gem install jekyll - - jekyll build -d public/ - artifacts: - paths: - - public - only: - - pages -``` - -See an example that has different files in the [`master` branch][jekyll-master] -and the source files for Jekyll are in a [`pages` branch][jekyll-pages] which -also includes `.gitlab-ci.yml`. - -[jekyll-master]: https://gitlab.com/pages/jekyll-branched/tree/master -[jekyll-pages]: https://gitlab.com/pages/jekyll-branched/tree/pages - -## Next steps - -So you have successfully deployed your website, congratulations! Let's check -what more you can do with GitLab Pages. - -### Example projects - -Below is a list of example projects for GitLab Pages with a plain HTML website -or various static site generators. Contributions are very welcome. - -- [Plain HTML](https://gitlab.com/pages/plain-html) -- [Jekyll](https://gitlab.com/pages/jekyll) -- [Hugo](https://gitlab.com/pages/hugo) -- [Middleman](https://gitlab.com/pages/middleman) -- [Hexo](https://gitlab.com/pages/hexo) -- [Brunch](https://gitlab.com/pages/brunch) -- [Metalsmith](https://gitlab.com/pages/metalsmith) -- [Harp](https://gitlab.com/pages/harp) - -Visit the GitLab Pages group for a full list of example projects: -. - -### Add a custom domain to your Pages website - -If this setting is enabled by your GitLab administrator, you should be able to -see the **New Domain** button when visiting your project's settings through the -gear icon in the top right and then navigating to **Pages**. - -![New domain button](img/pages_new_domain_button.png) - ---- - -You can add multiple domains pointing to your website hosted under GitLab. -Once the domain is added, you can see it listed under the **Domains** section. - -![Pages multiple domains](img/pages_multiple_domains.png) - ---- - -As a last step, you need to configure your DNS and add a CNAME pointing to your -user/group page. Click on the **Details** button of a domain for further -instructions. - -![Pages DNS details](img/pages_dns_details.png) - ---- - ->**Note:** -Currently there is support only for custom domains on per-project basis. That -means that if you add a custom domain (`example.com`) for your user website -(`username.example.io`), a project that is served under `username.example.io/foo`, -will not be accessible under `example.com/foo`. - -### Secure your custom domain website with TLS - -When you add a new custom domain, you also have the chance to add a TLS -certificate. If this setting is enabled by your GitLab administrator, you -should be able to see the option to upload the public certificate and the -private key when adding a new domain. - -![Pages upload cert](img/pages_upload_cert.png) - -### Custom error codes pages - -You can provide your own 403 and 404 error pages by creating the `403.html` and -`404.html` files respectively in the root directory of the `public/` directory -that will be included in the artifacts. Usually this is the root directory of -your project, but that may differ depending on your static generator -configuration. - -If the case of `404.html`, there are different scenarios. For example: - -- If you use project Pages (served under `/projectname/`) and try to access - `/projectname/non/exsiting_file`, GitLab Pages will try to serve first - `/projectname/404.html`, and then `/404.html`. -- If you use user/group Pages (served under `/`) and try to access - `/non/existing_file` GitLab Pages will try to serve `/404.html`. -- If you use a custom domain and try to access `/non/existing_file`, GitLab - Pages will try to serve only `/404.html`. - -### Remove the contents of your pages - -If you ever feel the need to purge your Pages content, you can do so by going -to your project's settings through the gear icon in the top right, and then -navigating to **Pages**. Hit the **Remove pages** button and your Pages website -will be deleted. Simple as that. - -![Remove pages](img/pages_remove.png) - -## GitLab Pages on GitLab.com - -If you are using GitLab.com to host your website, then: - -- The general domain name for GitLab Pages on GitLab.com is `gitlab.io`. -- Custom domains and TLS support are enabled. -- Shared runners are enabled by default, provided for free and can be used to - build your website. If you want you can still bring your own Runner. - -The rest of the guide still applies. - -## Limitations - -When using Pages under the general domain of a GitLab instance (`*.example.io`), -you _cannot_ use HTTPS with sub-subdomains. That means that if your -username/groupname contains a dot, for example `foo.bar`, the domain -`https://foo.bar.example.io` will _not_ work. This is a limitation of the -[HTTP Over TLS protocol][rfc]. HTTP pages will continue to work provided you -don't redirect HTTP to HTTPS. - -[rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC" - -## Redirects in GitLab Pages - -Since you cannot use any custom server configuration files, like `.htaccess` or -any `.conf` file for that matter, if you want to redirect a web page to another -location, you can use the [HTTP meta refresh tag][metarefresh]. - -Some static site generators provide plugins for that functionality so that you -don't have to create and edit HTML files manually. For example, Jekyll has the -[redirect-from plugin](https://github.com/jekyll/jekyll-redirect-from). - -## Frequently Asked Questions - -### Can I download my generated pages? - -Sure. All you need to do is download the artifacts archive from the build page. - -### Can I use GitLab Pages if my project is private? - -Yes. GitLab Pages don't care whether you set your project's visibility level -to private, internal or public. - -### Do I need to create a user/group website before creating a project website? - -No, you don't. You can create your project first and it will be accessed under -`http(s)://namespace.example.io/projectname`. - -## Known issues - -For a list of known issues, visit GitLab's [public issue tracker]. - ---- - -[jekyll]: http://jekyllrb.com/ -[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 -[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 -[pages-daemon]: https://gitlab.com/gitlab-org/gitlab-pages -[gitlab ci]: https://about.gitlab.com/gitlab-ci -[gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner -[pages]: ../ci/yaml/README.md#pages -[staticgen]: https://www.staticgen.com/ -[pages-jekyll]: https://gitlab.com/pages/jekyll -[metarefresh]: https://en.wikipedia.org/wiki/Meta_refresh -[public issue tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Pages +This document was moved to [user/project/pages](../user/project/pages). diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 9a94282a229..4eb3bb32c77 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -1,496 +1 @@ -# GitLab Pages Administration - -> **Notes:** -> - [Introduced][ee-80] in GitLab EE 8.3. -> - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. -> - GitLab Pages were ported to Community Edition in GitLab 8.16. - ---- - -This document describes how to set up the _latest_ GitLab Pages feature. Make -sure to read the [changelog](#changelog) if you are upgrading to a new GitLab -version as it may include new features and changes needed to be made in your -configuration. - -If you are looking for ways to upload your static content in GitLab Pages, you -probably want to read the [user documentation](README.md). - -## Overview - -GitLab Pages makes use of the [GitLab Pages daemon], a simple HTTP server -written in Go that can listen on an external IP address and provide support for -custom domains and custom certificates. It supports dynamic certificates through -SNI and exposes pages using HTTP2 by default. -You are encouraged to read its [README][pages-readme] to fully understand how -it works. - ---- - -In the case of custom domains, the Pages daemon needs to listen on ports `80` -and/or `443`. For that reason, there is some flexibility in the way which you -can set it up: - -1. Run the pages daemon in the same server as GitLab, listening on a secondary IP -1. Run the pages daemon in a separate server. In that case, the - [Pages path](#change-storage-path) must also be present in the server that - the pages daemon is installed, so you will have to share it via network. -1. Run the pages daemon in the same server as GitLab, listening on the same IP - but on different ports. In that case, you will have to proxy the traffic with - a loadbalancer. If you choose that route note that you should use TCP load - balancing for HTTPS. If you use TLS-termination (HTTPS-load balancing) the - pages will not be able to be served with user provided certificates. For - HTTP it's OK to use HTTP or TCP load balancing. - -In this document, we will proceed assuming the first option. - -## Prerequisites - -Before proceeding with the Pages configuration, you will need to: - -1. Have a separate domain under which the GitLab Pages will be served -1. (Optional) Have a wildcard certificate for that domain if you decide to serve - Pages under HTTPS -1. Configure a wildcard DNS record -1. (Optional but recommended) Enable [Shared runners](../ci/runners/README.md) - so that your users don't have to bring their own - -### DNS configuration - -GitLab Pages expect to run on their own virtual host. In your DNS server/provider -you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the -host that GitLab runs. For example, an entry would look like this: - -``` -*.example.io. 1800 IN A 1.2.3.4 -``` - -where `example.io` is the domain under which GitLab Pages will be served -and `1.2.3.4` is the IP address of your GitLab instance. - -> **Note:** -You should not use the GitLab domain to serve user pages. For more information -see the [security section](#security). - -[wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record - -## Configuration - -Depending on your needs, you can install GitLab Pages in four different ways. - -### Option 1. Custom domains with HTTPS support - -| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | -| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| `https://page.example.io` and `https://page.com` | yes | redirects to HTTPS | yes | yes | - -Pages enabled, daemon is enabled AND pages has external IP support enabled. -In that case, the pages daemon is running, NGINX still proxies requests to -the daemon but the daemon is also able to receive requests from the outside -world. Custom domains and TLS are supported. - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.1 - sudo -u git -H make - ``` - -1. Edit `gitlab.yml` to look like the example below. You need to change the - `host` to the FQDN under which GitLab Pages will be served. Set - `external_http` and `external_https` to the secondary IP on which the pages - daemon will listen for connections: - - ```yaml - ## GitLab Pages - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 443 - https: true - - external_http: 1.1.1.1:80 - external_https: 1.1.1.1:443 - ``` - -1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in - order to enable the pages daemon. In `gitlab_pages_options` the - `-pages-domain`, `-listen-http` and `-listen-https` must match the `host`, - `external_http` and `external_https` settings that you set above respectively. - The `-root-cert` and `-root-key` settings are the wildcard TLS certificates - of the `example.io` domain: - - ``` - gitlab_pages_enabled=true - gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80 -listen-https 1.1.1.1:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - -1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace - `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab - listens to. -1. Restart NGINX -1. [Restart GitLab][restart] - ---- - -**Omnibus installations:** - -1. Edit `/etc/gitlab/gitlab.rb`: - - ```ruby - pages_external_url "https://example.io" - nginx['listen_addresses'] = ['1.1.1.1'] - pages_nginx['enable'] = false - gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt" - gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key" - gitlab_pages['external_http'] = '1.1.1.2:80' - gitlab_pages['external_https'] = '1.1.1.2:443' - ``` - - where `1.1.1.1` is the primary IP address that GitLab is listening to and - `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to. - -1. [Reconfigure GitLab][reconfigure] - -### Option 2. Custom domains without HTTPS support - -| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | -| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| `http://page.example.io` and `http://page.com` | no | yes | no | yes | - -Pages enabled, daemon is enabled AND pages has external IP support enabled. -In that case, the pages daemon is running, NGINX still proxies requests to -the daemon but the daemon is also able to receive requests from the outside -world. Custom domains and TLS are supported. - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.1 - sudo -u git -H make - ``` - -1. Edit `gitlab.yml` to look like the example below. You need to change the - `host` to the FQDN under which GitLab Pages will be served. Set - `external_http` to the secondary IP on which the pages daemon will listen - for connections: - - ```yaml - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 80 - https: false - - external_http: 1.1.1.1:80 - ``` - -1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in - order to enable the pages daemon. In `gitlab_pages_options` the - `-pages-domain` and `-listen-http` must match the `host` and `external_http` - settings that you set above respectively: - - ``` - gitlab_pages_enabled=true - gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - -1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace - `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab - listens to. -1. Restart NGINX -1. [Restart GitLab][restart] - ---- - -**Omnibus installations:** - -1. Edit `/etc/gitlab/gitlab.rb`: - - ```ruby - pages_external_url "http://example.io" - nginx['listen_addresses'] = ['1.1.1.1'] - pages_nginx['enable'] = false - gitlab_pages['external_http'] = '1.1.1.2:80' - ``` - - where `1.1.1.1` is the primary IP address that GitLab is listening to and - `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to. - -1. [Reconfigure GitLab][reconfigure] - -### Option 3. Wildcard HTTPS domain without custom domains - -| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | -| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| `https://page.example.io` | yes | no | no | no | - -Pages enabled, daemon is enabled and NGINX will proxy all requests to the -daemon. Pages daemon doesn't listen to the outside world. - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.1 - sudo -u git -H make - ``` -1. In `gitlab.yml`, set the port to `443` and https to `true`: - - ```bash - ## GitLab Pages - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 443 - https: true - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - ---- - -**Omnibus installations:** - -1. Place the certificate and key inside `/etc/gitlab/ssl` -1. In `/etc/gitlab/gitlab.rb` specify the following configuration: - - ```ruby - pages_external_url 'https://example.io' - - pages_nginx['redirect_http_to_https'] = true - pages_nginx['ssl_certificate'] = "/etc/gitlab/ssl/pages-nginx.crt" - pages_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/pages-nginx.key" - ``` - - where `pages-nginx.crt` and `pages-nginx.key` are the SSL cert and key, - respectively. - -1. [Reconfigure GitLab][reconfigure] - -### Option 4. Wildcard HTTP domain without custom domains - -| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | -| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| `http://page.example.io` | no | no | no | no | - -Pages enabled, daemon is enabled and NGINX will proxy all requests to the -daemon. Pages daemon doesn't listen to the outside world. - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.1 - sudo -u git -H make - ``` - -1. Go to the GitLab installation directory: - - ```bash - cd /home/git/gitlab - ``` - -1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and - the `host` to the FQDN under which GitLab Pages will be served: - - ```yaml - ## GitLab Pages - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 80 - https: false - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - -1. Restart NGINX -1. [Restart GitLab][restart] - ---- - -**Omnibus installations:** - -1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: - - ```ruby - pages_external_url 'http://example.io' - ``` - -1. [Reconfigure GitLab][reconfigure] - -## NGINX caveats - ->**Note:** -The following information applies only for installations from source. - -Be extra careful when setting up the domain name in the NGINX config. You must -not remove the backslashes. - -If your GitLab pages domain is `example.io`, replace: - -```bash -server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$; -``` - -with: - -``` -server_name ~^.*\.example\.io$; -``` - -If you are using a subdomain, make sure to escape all dots (`.`) except from -the first one with a backslash (\). For example `pages.example.io` would be: - -``` -server_name ~^.*\.pages\.example\.io$; -``` - -## Set maximum pages size - -The maximum size of the unpacked archive per project can be configured in the -Admin area under the Application settings in the **Maximum size of pages (MB)**. -The default is 100MB. - -## Change storage path - -**Source installations:** - -1. Pages are stored by default in `/home/git/gitlab/shared/pages`. - If you wish to store them in another location you must set it up in - `gitlab.yml` under the `pages` section: - - ```yaml - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - path: /mnt/storage/pages - ``` - -1. [Restart GitLab][restart] - -**Omnibus installations:** - -1. Pages are stored by default in `/var/opt/gitlab/gitlab-rails/shared/pages`. - If you wish to store them in another location you must set it up in - `/etc/gitlab/gitlab.rb`: - - ```ruby - gitlab_rails['pages_path'] = "/mnt/storage/pages" - ``` - -1. [Reconfigure GitLab][reconfigure] - -## Backup - -Pages are part of the [regular backup][backup] so there is nothing to configure. - -## Security - -You should strongly consider running GitLab pages under a different hostname -than GitLab to prevent XSS attacks. - -## Changelog - -GitLab Pages were first introduced in GitLab EE 8.3. Since then, many features -where added, like custom CNAME and TLS support, and many more are likely to -come. Below is a brief changelog. If no changes were introduced or a version is -missing from the changelog, assume that the documentation is the same as the -latest previous version. - ---- - -**GitLab 8.16 ([documentation][8-16-docs])** - -- GitLab Pages were ported to Community Edition in GitLab 8.16. -- Documentation was refactored to be more modular and easy to follow. - -**GitLab 8.5 ([documentation][8-5-docs])** - -- In GitLab 8.5 we introduced the [gitlab-pages][] daemon which is now the - recommended way to set up GitLab Pages. -- The [NGINX configs][] have changed to reflect this change. So make sure to - update them. -- Custom CNAME and TLS certificates support. -- Documentation was moved to one place. - ---- - -**GitLab 8.3 ([documentation][8-3-docs])** - -- GitLab Pages feature was introduced. - -[8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md -[8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md -[8-16-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-16-stable-ce/doc/pages/administration.md -[backup]: ../raketasks/backup_restore.md -[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 -[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 -[gitlab pages daemon]: https://gitlab.com/gitlab-org/gitlab-pages -[NGINX configs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/8-5-stable-ee/lib/support/nginx -[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md -[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure -[restart]: ../administration/restart_gitlab.md#installations-from-source -[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.1 +This document was moved to [administration/pages](../administration/pages/index.md). diff --git a/doc/university/README.md b/doc/university/README.md index 12727e9d56f..379a7b4b40f 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -91,7 +91,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project 1. [Using any Static Site Generator with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) 1. [Securing GitLab Pages with SSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/) -1. [GitLab Pages Documentation](https://docs.gitlab.com/ee/pages/README.html) +1. [GitLab Pages Documentation](https://docs.gitlab.com/ce/user/project/pages/) #### 2.2. GitLab Issues diff --git a/doc/university/support/README.md b/doc/university/support/README.md index 6e415e4d219..ca538ef6dc3 100644 --- a/doc/university/support/README.md +++ b/doc/university/support/README.md @@ -172,7 +172,7 @@ Move on to understanding some of GitLab's more advanced features. You can make u - Get to know the [GitLab API](https://docs.gitlab.com/ee/api/README.html), its capabilities and shortcomings - Learn how to [migrate from SVN to Git](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html) - Set up [GitLab CI](https://docs.gitlab.com/ee/ci/quick_start/README.html) -- Create your first [GitLab Page](https://docs.gitlab.com/ee/pages/administration.html) +- Create your first [GitLab Page](https://docs.gitlab.com/ce/administration/pages/) - Get to know the GitLab Codebase by reading through the source code: - Find the differences between the [EE codebase](https://gitlab.com/gitlab-org/gitlab-ce) and the [CE codebase](https://gitlab.com/gitlab-org/gitlab-ce) diff --git a/doc/pages/img/pages_create_project.png b/doc/user/project/pages/img/pages_create_project.png similarity index 100% rename from doc/pages/img/pages_create_project.png rename to doc/user/project/pages/img/pages_create_project.png diff --git a/doc/pages/img/pages_create_user_page.png b/doc/user/project/pages/img/pages_create_user_page.png similarity index 100% rename from doc/pages/img/pages_create_user_page.png rename to doc/user/project/pages/img/pages_create_user_page.png diff --git a/doc/pages/img/pages_dns_details.png b/doc/user/project/pages/img/pages_dns_details.png similarity index 100% rename from doc/pages/img/pages_dns_details.png rename to doc/user/project/pages/img/pages_dns_details.png diff --git a/doc/pages/img/pages_multiple_domains.png b/doc/user/project/pages/img/pages_multiple_domains.png similarity index 100% rename from doc/pages/img/pages_multiple_domains.png rename to doc/user/project/pages/img/pages_multiple_domains.png diff --git a/doc/pages/img/pages_new_domain_button.png b/doc/user/project/pages/img/pages_new_domain_button.png similarity index 100% rename from doc/pages/img/pages_new_domain_button.png rename to doc/user/project/pages/img/pages_new_domain_button.png diff --git a/doc/pages/img/pages_remove.png b/doc/user/project/pages/img/pages_remove.png similarity index 100% rename from doc/pages/img/pages_remove.png rename to doc/user/project/pages/img/pages_remove.png diff --git a/doc/pages/img/pages_upload_cert.png b/doc/user/project/pages/img/pages_upload_cert.png similarity index 100% rename from doc/pages/img/pages_upload_cert.png rename to doc/user/project/pages/img/pages_upload_cert.png diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md new file mode 100644 index 00000000000..e427d7f283d --- /dev/null +++ b/doc/user/project/pages/index.md @@ -0,0 +1,435 @@ +# GitLab Pages + +> **Note:** +> This feature was [introduced][ee-80] in GitLab EE 8.3. +> Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. + +> **Note:** +> This document is about the user guide. To learn how to enable GitLab Pages +> across your GitLab instance, visit the [administrator documentation](administration.md). + +With GitLab Pages you can host for free your static websites on GitLab. +Combined with the power of [GitLab CI] and the help of [GitLab Runner] you can +deploy static pages for your individual projects, your user or your group. + +Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlab-com) for specific +information, if you are using GitLab.com to host your website. + +## Getting started with GitLab Pages + +> **Note:** +> In the rest of this document we will assume that the general domain name that +> is used for GitLab Pages is `example.io`. + +In general there are two types of pages one might create: + +- Pages per user (`username.example.io`) or per group (`groupname.example.io`) +- Pages per project (`username.example.io/projectname` or `groupname.example.io/projectname`) + +In GitLab, usernames and groupnames are unique and we often refer to them +as namespaces. There can be only one namespace in a GitLab instance. Below you +can see the connection between the type of GitLab Pages, what the project name +that is created on GitLab looks like and the website URL it will be ultimately +be served on. + +| Type of GitLab Pages | The name of the project created in GitLab | Website URL | +| -------------------- | ------------ | ----------- | +| User pages | `username.example.io` | `http(s)://username.example.io` | +| Group pages | `groupname.example.io` | `http(s)://groupname.example.io` | +| Project pages owned by a user | `projectname` | `http(s)://username.example.io/projectname` | +| Project pages owned by a group | `projectname` | `http(s)://groupname.example.io/projectname`| + +> **Warning:** +> There are some known [limitations](#limitations) regarding namespaces served +> under the general domain name and HTTPS. Make sure to read that section. + +### GitLab Pages requirements + +In brief, this is what you need to upload your website in GitLab Pages: + +1. Find out the general domain name that is used for GitLab Pages + (ask your administrator). This is very important, so you should first make + sure you get that right. +1. Create a project +1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory + of your repository with a specific job named [`pages`][pages] +1. Set up a GitLab Runner to build your website + +> **Note:** +> If [shared runners](../ci/runners/README.md) are enabled by your GitLab +> administrator, you should be able to use them instead of bringing your own. + +### User or group Pages + +For user and group pages, the name of the project should be specific to the +username or groupname and the general domain name that is used for GitLab Pages. +Head over your GitLab instance that supports GitLab Pages and create a +repository named `username.example.io`, where `username` is your username on +GitLab. If the first part of the project name doesn't match exactly your +username, it won’t work, so make sure to get it right. + +To create a group page, the steps are the same like when creating a website for +users. Just make sure that you are creating the project within the group's +namespace. + +![Create a user-based pages project](img/pages_create_user_page.png) + +--- + +After you push some static content to your repository and GitLab Runner uploads +the artifacts to GitLab CI, you will be able to access your website under +`http(s)://username.example.io`. Keep reading to find out how. + +>**Note:** +If your username/groupname contains a dot, for example `foo.bar`, you will not +be able to use the wildcard domain HTTPS, read more at [limitations](#limitations). + +### Project Pages + +GitLab Pages for projects can be created by both user and group accounts. +The steps to create a project page for a user or a group are identical: + +1. Create a new project +1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory + of your repository with a specific job named [`pages`][pages]. +1. Set up a GitLab Runner to build your website + +A user's project will be served under `http(s)://username.example.io/projectname` +whereas a group's project under `http(s)://groupname.example.io/projectname`. + +### Explore the contents of `.gitlab-ci.yml` + +The key thing about GitLab Pages is the `.gitlab-ci.yml` file, something that +gives you absolute control over the build process. You can actually watch your +website being built live by following the CI build traces. + +> **Note:** +> Before reading this section, make sure you familiarize yourself with GitLab CI +> and the specific syntax of[`.gitlab-ci.yml`](../ci/yaml/README.md) by +> following our [quick start guide](../ci/quick_start/README.md). + +To make use of GitLab Pages, the contents of `.gitlab-ci.yml` must follow the +rules below: + +1. A special job named [`pages`][pages] must be defined +1. Any static content which will be served by GitLab Pages must be placed under + a `public/` directory +1. `artifacts` with a path to the `public/` directory must be defined + +In its simplest form, `.gitlab-ci.yml` looks like: + +```yaml +pages: + script: + - my_commands + artifacts: + paths: + - public +``` + +When the Runner reaches to build the `pages` job, it executes whatever is +defined in the `script` parameter and if the build completes with a non-zero +exit status, it then uploads the `public/` directory to GitLab Pages. + +The `public/` directory should contain all the static content of your website. +Depending on how you plan to publish your website, the steps defined in the +[`script` parameter](../ci/yaml/README.md#script) may differ. + +Be aware that Pages are by default branch/tag agnostic and their deployment +relies solely on what you specify in `.gitlab-ci.yml`. If you don't limit the +`pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), +whenever a new commit is pushed to whatever branch or tag, the Pages will be +overwritten. In the example below, we limit the Pages to be deployed whenever +a commit is pushed only on the `master` branch: + +```yaml +pages: + script: + - my_commands + artifacts: + paths: + - public + only: + - master +``` + +We then tell the Runner to treat the `public/` directory as `artifacts` and +upload it to GitLab. And since all these parameters were all under a `pages` +job, the contents of the `public` directory will be served by GitLab Pages. + +#### How `.gitlab-ci.yml` looks like when the static content is in your repository + +Supposedly your repository contained the following files: + +``` +├── index.html +├── css +│   └── main.css +└── js + └── main.js +``` + +Then the `.gitlab-ci.yml` example below simply moves all files from the root +directory of the project to the `public/` directory. The `.public` workaround +is so `cp` doesn't also copy `public/` to itself in an infinite loop: + +```yaml +pages: + script: + - mkdir .public + - cp -r * .public + - mv .public public + artifacts: + paths: + - public + only: + - master +``` + +#### How `.gitlab-ci.yml` looks like when using a static generator + +In general, GitLab Pages support any kind of [static site generator][staticgen], +since `.gitlab-ci.yml` can be configured to run any possible command. + +In the root directory of your Git repository, place the source files of your +favorite static generator. Then provide a `.gitlab-ci.yml` file which is +specific to your static generator. + +The example below, uses [Jekyll] to build the static site: + +```yaml +image: ruby:2.1 # the script will run in Ruby 2.1 using the Docker image ruby:2.1 + +pages: # the build job must be named pages + script: + - gem install jekyll # we install jekyll + - jekyll build -d public/ # we tell jekyll to build the site for us + artifacts: + paths: + - public # this is where the site will live and the Runner uploads it in GitLab + only: + - master # this script is only affecting the master branch +``` + +Here, we used the Docker executor and in the first line we specified the base +image against which our builds will run. + +You have to make sure that the generated static files are ultimately placed +under the `public` directory, that's why in the `script` section we run the +`jekyll` command that builds the website and puts all content in the `public/` +directory. Depending on the static generator of your choice, this command will +differ. Search in the documentation of the static generator you will use if +there is an option to explicitly set the output directory. If there is not +such an option, you can always add one more line under `script` to rename the +resulting directory in `public/`. + +We then tell the Runner to treat the `public/` directory as `artifacts` and +upload it to GitLab. + +--- + +See the [jekyll example project][pages-jekyll] to better understand how this +works. + +For a list of Pages projects, see the [example projects](#example-projects) to +get you started. + +#### How to set up GitLab Pages in a repository where there's also actual code + +Remember that GitLab Pages are by default branch/tag agnostic and their +deployment relies solely on what you specify in `.gitlab-ci.yml`. You can limit +the `pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), +whenever a new commit is pushed to a branch that will be used specifically for +your pages. + +That way, you can have your project's code in the `master` branch and use an +orphan branch (let's name it `pages`) that will host your static generator site. + +You can create a new empty branch like this: + +```bash +git checkout --orphan pages +``` + +The first commit made on this new branch will have no parents and it will be +the root of a new history totally disconnected from all the other branches and +commits. Push the source files of your static generator in the `pages` branch. + +Below is a copy of `.gitlab-ci.yml` where the most significant line is the last +one, specifying to execute everything in the `pages` branch: + +``` +image: ruby:2.1 + +pages: + script: + - gem install jekyll + - jekyll build -d public/ + artifacts: + paths: + - public + only: + - pages +``` + +See an example that has different files in the [`master` branch][jekyll-master] +and the source files for Jekyll are in a [`pages` branch][jekyll-pages] which +also includes `.gitlab-ci.yml`. + +[jekyll-master]: https://gitlab.com/pages/jekyll-branched/tree/master +[jekyll-pages]: https://gitlab.com/pages/jekyll-branched/tree/pages + +## Next steps + +So you have successfully deployed your website, congratulations! Let's check +what more you can do with GitLab Pages. + +### Example projects + +Below is a list of example projects for GitLab Pages with a plain HTML website +or various static site generators. Contributions are very welcome. + +- [Plain HTML](https://gitlab.com/pages/plain-html) +- [Jekyll](https://gitlab.com/pages/jekyll) +- [Hugo](https://gitlab.com/pages/hugo) +- [Middleman](https://gitlab.com/pages/middleman) +- [Hexo](https://gitlab.com/pages/hexo) +- [Brunch](https://gitlab.com/pages/brunch) +- [Metalsmith](https://gitlab.com/pages/metalsmith) +- [Harp](https://gitlab.com/pages/harp) + +Visit the GitLab Pages group for a full list of example projects: +. + +### Add a custom domain to your Pages website + +If this setting is enabled by your GitLab administrator, you should be able to +see the **New Domain** button when visiting your project's settings through the +gear icon in the top right and then navigating to **Pages**. + +![New domain button](img/pages_new_domain_button.png) + +--- + +You can add multiple domains pointing to your website hosted under GitLab. +Once the domain is added, you can see it listed under the **Domains** section. + +![Pages multiple domains](img/pages_multiple_domains.png) + +--- + +As a last step, you need to configure your DNS and add a CNAME pointing to your +user/group page. Click on the **Details** button of a domain for further +instructions. + +![Pages DNS details](img/pages_dns_details.png) + +--- + +>**Note:** +Currently there is support only for custom domains on per-project basis. That +means that if you add a custom domain (`example.com`) for your user website +(`username.example.io`), a project that is served under `username.example.io/foo`, +will not be accessible under `example.com/foo`. + +### Secure your custom domain website with TLS + +When you add a new custom domain, you also have the chance to add a TLS +certificate. If this setting is enabled by your GitLab administrator, you +should be able to see the option to upload the public certificate and the +private key when adding a new domain. + +![Pages upload cert](img/pages_upload_cert.png) + +### Custom error codes pages + +You can provide your own 403 and 404 error pages by creating the `403.html` and +`404.html` files respectively in the root directory of the `public/` directory +that will be included in the artifacts. Usually this is the root directory of +your project, but that may differ depending on your static generator +configuration. + +If the case of `404.html`, there are different scenarios. For example: + +- If you use project Pages (served under `/projectname/`) and try to access + `/projectname/non/exsiting_file`, GitLab Pages will try to serve first + `/projectname/404.html`, and then `/404.html`. +- If you use user/group Pages (served under `/`) and try to access + `/non/existing_file` GitLab Pages will try to serve `/404.html`. +- If you use a custom domain and try to access `/non/existing_file`, GitLab + Pages will try to serve only `/404.html`. + +### Remove the contents of your pages + +If you ever feel the need to purge your Pages content, you can do so by going +to your project's settings through the gear icon in the top right, and then +navigating to **Pages**. Hit the **Remove pages** button and your Pages website +will be deleted. Simple as that. + +![Remove pages](img/pages_remove.png) + +## GitLab Pages on GitLab.com + +If you are using GitLab.com to host your website, then: + +- The general domain name for GitLab Pages on GitLab.com is `gitlab.io`. +- Custom domains and TLS support are enabled. +- Shared runners are enabled by default, provided for free and can be used to + build your website. If you want you can still bring your own Runner. + +The rest of the guide still applies. + +## Limitations + +When using Pages under the general domain of a GitLab instance (`*.example.io`), +you _cannot_ use HTTPS with sub-subdomains. That means that if your +username/groupname contains a dot, for example `foo.bar`, the domain +`https://foo.bar.example.io` will _not_ work. This is a limitation of the +[HTTP Over TLS protocol][rfc]. HTTP pages will continue to work provided you +don't redirect HTTP to HTTPS. + +[rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC" + +## Redirects in GitLab Pages + +Since you cannot use any custom server configuration files, like `.htaccess` or +any `.conf` file for that matter, if you want to redirect a web page to another +location, you can use the [HTTP meta refresh tag][metarefresh]. + +Some static site generators provide plugins for that functionality so that you +don't have to create and edit HTML files manually. For example, Jekyll has the +[redirect-from plugin](https://github.com/jekyll/jekyll-redirect-from). + +## Frequently Asked Questions + +### Can I download my generated pages? + +Sure. All you need to do is download the artifacts archive from the build page. + +### Can I use GitLab Pages if my project is private? + +Yes. GitLab Pages don't care whether you set your project's visibility level +to private, internal or public. + +### Do I need to create a user/group website before creating a project website? + +No, you don't. You can create your project first and it will be accessed under +`http(s)://namespace.example.io/projectname`. + +## Known issues + +For a list of known issues, visit GitLab's [public issue tracker]. + +--- + +[jekyll]: http://jekyllrb.com/ +[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 +[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 +[pages-daemon]: https://gitlab.com/gitlab-org/gitlab-pages +[gitlab ci]: https://about.gitlab.com/gitlab-ci +[gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner +[pages]: ../ci/yaml/README.md#pages +[staticgen]: https://www.staticgen.com/ +[pages-jekyll]: https://gitlab.com/pages/jekyll +[metarefresh]: https://en.wikipedia.org/wiki/Meta_refresh +[public issue tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Pages From 749d8051ed379f79cab9c378ee1ac564d2d09e9b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 23 Jan 2017 13:47:39 +0100 Subject: [PATCH 172/183] Bump GitLab version that Pages where ported to CE --- doc/administration/pages/index.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index da148a0f2bb..c460c8e1b86 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -462,9 +462,9 @@ latest previous version. --- -**GitLab 8.16 ([documentation][8-16-docs])** +**GitLab 8.17 ([documentation][8-17-docs])** -- GitLab Pages were ported to Community Edition in GitLab 8.16. +- GitLab Pages were ported to Community Edition in GitLab 8.17. - Documentation was refactored to be more modular and easy to follow. **GitLab 8.5 ([documentation][8-5-docs])** @@ -476,15 +476,13 @@ latest previous version. - Custom CNAME and TLS certificates support. - Documentation was moved to one place. ---- - **GitLab 8.3 ([documentation][8-3-docs])** - GitLab Pages feature was introduced. [8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md [8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md -[8-16-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-16-stable-ce/doc/administration/pages/index.md +[8-17-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-17-stable-ce/doc/administration/pages/index.md [backup]: ../raketasks/backup_restore.md [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 [ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 From 39e74b1cba3773fd0fb37bc78a7400ec7c6b7ab3 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 23 Jan 2017 13:48:27 +0100 Subject: [PATCH 173/183] Fix link to new pages location --- doc/pages/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 44b74513fd9..c9715eed598 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -1 +1 @@ -This document was moved to [user/project/pages](../user/project/pages). +This document was moved to [user/project/pages](../user/project/pages/index.md). From 452b9c8b955274f8892d3fa4a0fc335d28515272 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 23 Jan 2017 13:48:43 +0100 Subject: [PATCH 174/183] Merge multiple notes into one in Pages user docs --- doc/user/project/pages/index.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index e427d7f283d..a07c23a3274 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -1,12 +1,10 @@ # GitLab Pages -> **Note:** -> This feature was [introduced][ee-80] in GitLab EE 8.3. -> Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. - -> **Note:** -> This document is about the user guide. To learn how to enable GitLab Pages -> across your GitLab instance, visit the [administrator documentation](administration.md). +> **Notes:** +> - This feature was [introduced][ee-80] in GitLab EE 8.3. +> - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. +> - This document is about the user guide. To learn how to enable GitLab Pages +> across your GitLab instance, visit the [administrator documentation](../../../administration/pages/index.md). With GitLab Pages you can host for free your static websites on GitLab. Combined with the power of [GitLab CI] and the help of [GitLab Runner] you can From 2c787bca1f0566517a2ddae7d1f23a822285a44d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 23 Jan 2017 13:54:09 +0100 Subject: [PATCH 175/183] Better highlight prerequisites for Pages --- doc/administration/pages/index.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index c460c8e1b86..5e84eb85667 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -47,12 +47,13 @@ In this document, we will proceed assuming the first option. Before proceeding with the Pages configuration, you will need to: -1. Have a separate domain under which the GitLab Pages will be served -1. (Optional) Have a wildcard certificate for that domain if you decide to serve - Pages under HTTPS -1. Configure a wildcard DNS record +1. Have a separate domain under which the GitLab Pages will be served. In this + document we assume that to be `example.io`. +1. Configure a **wildcard DNS record**. +1. (Optional) Have a **wildcard certificate** for that domain if you decide to + serve Pages under HTTPS. 1. (Optional but recommended) Enable [Shared runners](../ci/runners/README.md) - so that your users don't have to bring their own + so that your users don't have to bring their own. ### DNS configuration From e9c08231d73d2f3c73eb2687bc887076c9b6ed83 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 23 Jan 2017 14:03:27 +0100 Subject: [PATCH 176/183] Bump pages daemon and place Omnibus settings on top --- doc/administration/pages/index.md | 232 +++++++++++++++--------------- 1 file changed, 120 insertions(+), 112 deletions(-) diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 5e84eb85667..5b1d6ee9998 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -89,6 +89,27 @@ In that case, the pages daemon is running, NGINX still proxies requests to the daemon but the daemon is also able to receive requests from the outside world. Custom domains and TLS are supported. +**Omnibus installations:** + +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + pages_external_url "https://example.io" + nginx['listen_addresses'] = ['1.1.1.1'] + pages_nginx['enable'] = false + gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt" + gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key" + gitlab_pages['external_http'] = '1.1.1.2:80' + gitlab_pages['external_https'] = '1.1.1.2:443' + ``` + + where `1.1.1.1` is the primary IP address that GitLab is listening to and + `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to. + +1. [Reconfigure GitLab][reconfigure] + +--- + **Source installations:** 1. Install the Pages daemon: @@ -97,7 +118,7 @@ world. Custom domains and TLS are supported. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.1 + sudo -u git -H git checkout v0.2.4 sudo -u git -H make ``` @@ -148,27 +169,6 @@ world. Custom domains and TLS are supported. 1. Restart NGINX 1. [Restart GitLab][restart] ---- - -**Omnibus installations:** - -1. Edit `/etc/gitlab/gitlab.rb`: - - ```ruby - pages_external_url "https://example.io" - nginx['listen_addresses'] = ['1.1.1.1'] - pages_nginx['enable'] = false - gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt" - gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key" - gitlab_pages['external_http'] = '1.1.1.2:80' - gitlab_pages['external_https'] = '1.1.1.2:443' - ``` - - where `1.1.1.1` is the primary IP address that GitLab is listening to and - `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to. - -1. [Reconfigure GitLab][reconfigure] - ### Option 2. Custom domains without HTTPS support | URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | @@ -180,6 +180,24 @@ In that case, the pages daemon is running, NGINX still proxies requests to the daemon but the daemon is also able to receive requests from the outside world. Custom domains and TLS are supported. +**Omnibus installations:** + +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + pages_external_url "http://example.io" + nginx['listen_addresses'] = ['1.1.1.1'] + pages_nginx['enable'] = false + gitlab_pages['external_http'] = '1.1.1.2:80' + ``` + + where `1.1.1.1` is the primary IP address that GitLab is listening to and + `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to. + +1. [Reconfigure GitLab][reconfigure] + +--- + **Source installations:** 1. Install the Pages daemon: @@ -188,7 +206,7 @@ world. Custom domains and TLS are supported. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.1 + sudo -u git -H git checkout v0.2.4 sudo -u git -H make ``` @@ -235,24 +253,6 @@ world. Custom domains and TLS are supported. 1. Restart NGINX 1. [Restart GitLab][restart] ---- - -**Omnibus installations:** - -1. Edit `/etc/gitlab/gitlab.rb`: - - ```ruby - pages_external_url "http://example.io" - nginx['listen_addresses'] = ['1.1.1.1'] - pages_nginx['enable'] = false - gitlab_pages['external_http'] = '1.1.1.2:80' - ``` - - where `1.1.1.1` is the primary IP address that GitLab is listening to and - `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to. - -1. [Reconfigure GitLab][reconfigure] - ### Option 3. Wildcard HTTPS domain without custom domains | URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | @@ -262,6 +262,26 @@ world. Custom domains and TLS are supported. Pages enabled, daemon is enabled and NGINX will proxy all requests to the daemon. Pages daemon doesn't listen to the outside world. +**Omnibus installations:** + +1. Place the certificate and key inside `/etc/gitlab/ssl` +1. In `/etc/gitlab/gitlab.rb` specify the following configuration: + + ```ruby + pages_external_url 'https://example.io' + + pages_nginx['redirect_http_to_https'] = true + pages_nginx['ssl_certificate'] = "/etc/gitlab/ssl/pages-nginx.crt" + pages_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/pages-nginx.key" + ``` + + where `pages-nginx.crt` and `pages-nginx.key` are the SSL cert and key, + respectively. + +1. [Reconfigure GitLab][reconfigure] + +--- + **Source installations:** 1. Install the Pages daemon: @@ -270,7 +290,7 @@ daemon. Pages daemon doesn't listen to the outside world. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.1 + sudo -u git -H git checkout v0.2.4 sudo -u git -H make ``` 1. In `gitlab.yml`, set the port to `443` and https to `true`: @@ -296,25 +316,8 @@ daemon. Pages daemon doesn't listen to the outside world. Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. ---- - -**Omnibus installations:** - -1. Place the certificate and key inside `/etc/gitlab/ssl` -1. In `/etc/gitlab/gitlab.rb` specify the following configuration: - - ```ruby - pages_external_url 'https://example.io' - - pages_nginx['redirect_http_to_https'] = true - pages_nginx['ssl_certificate'] = "/etc/gitlab/ssl/pages-nginx.crt" - pages_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/pages-nginx.key" - ``` - - where `pages-nginx.crt` and `pages-nginx.key` are the SSL cert and key, - respectively. - -1. [Reconfigure GitLab][reconfigure] +1. Restart NGINX +1. [Restart GitLab][restart] ### Option 4. Wildcard HTTP domain without custom domains @@ -325,6 +328,18 @@ daemon. Pages daemon doesn't listen to the outside world. Pages enabled, daemon is enabled and NGINX will proxy all requests to the daemon. Pages daemon doesn't listen to the outside world. +**Omnibus installations:** + +1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: + + ```ruby + pages_external_url 'http://example.io' + ``` + +1. [Reconfigure GitLab][reconfigure] + +--- + **Source installations:** 1. Install the Pages daemon: @@ -333,7 +348,7 @@ daemon. Pages daemon doesn't listen to the outside world. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.1 + sudo -u git -H git checkout v0.2.4 sudo -u git -H make ``` @@ -370,18 +385,55 @@ daemon. Pages daemon doesn't listen to the outside world. 1. Restart NGINX 1. [Restart GitLab][restart] ---- +## Set maximum pages size + +The maximum size of the unpacked archive per project can be configured in the +Admin area under the Application settings in the **Maximum size of pages (MB)**. +The default is 100MB. + +## Change storage path + +Follow the steps below to change the default path where GitLab Pages' contents +are stored. **Omnibus installations:** -1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: +1. Pages are stored by default in `/var/opt/gitlab/gitlab-rails/shared/pages`. + If you wish to store them in another location you must set it up in + `/etc/gitlab/gitlab.rb`: - ```ruby - pages_external_url 'http://example.io' - ``` + ```ruby + gitlab_rails['pages_path'] = "/mnt/storage/pages" + ``` 1. [Reconfigure GitLab][reconfigure] +--- + +**Source installations:** + +1. Pages are stored by default in `/home/git/gitlab/shared/pages`. + If you wish to store them in another location you must set it up in + `gitlab.yml` under the `pages` section: + + ```yaml + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + path: /mnt/storage/pages + ``` + +1. [Restart GitLab][restart] + +## Backup + +Pages are part of the [regular backup][backup] so there is nothing to configure. + +## Security + +You should strongly consider running GitLab pages under a different hostname +than GitLab to prevent XSS attacks. + ## NGINX caveats >**Note:** @@ -409,50 +461,6 @@ the first one with a backslash (\). For example `pages.example.io` would be: server_name ~^.*\.pages\.example\.io$; ``` -## Set maximum pages size - -The maximum size of the unpacked archive per project can be configured in the -Admin area under the Application settings in the **Maximum size of pages (MB)**. -The default is 100MB. - -## Change storage path - -**Source installations:** - -1. Pages are stored by default in `/home/git/gitlab/shared/pages`. - If you wish to store them in another location you must set it up in - `gitlab.yml` under the `pages` section: - - ```yaml - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - path: /mnt/storage/pages - ``` - -1. [Restart GitLab][restart] - -**Omnibus installations:** - -1. Pages are stored by default in `/var/opt/gitlab/gitlab-rails/shared/pages`. - If you wish to store them in another location you must set it up in - `/etc/gitlab/gitlab.rb`: - - ```ruby - gitlab_rails['pages_path'] = "/mnt/storage/pages" - ``` - -1. [Reconfigure GitLab][reconfigure] - -## Backup - -Pages are part of the [regular backup][backup] so there is nothing to configure. - -## Security - -You should strongly consider running GitLab pages under a different hostname -than GitLab to prevent XSS attacks. - ## Changelog GitLab Pages were first introduced in GitLab EE 8.3. Since then, many features @@ -493,4 +501,4 @@ latest previous version. [pages-userguide]: ../../user/project/pages/index.md [reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure [restart]: ../administration/restart_gitlab.md#installations-from-source -[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.1 +[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.4 From e2123de5fca072306f7247931863418864739035 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 23 Jan 2017 14:37:52 +0100 Subject: [PATCH 177/183] Split Omnibus and source installation Pages admin docs [ci skip] --- doc/administration/pages/index.md | 275 +----------------------- doc/administration/pages/source.md | 323 +++++++++++++++++++++++++++++ 2 files changed, 333 insertions(+), 265 deletions(-) create mode 100644 doc/administration/pages/source.md diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 5b1d6ee9998..c352caf1115 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -1,9 +1,11 @@ # GitLab Pages Administration > **Notes:** -> - [Introduced][ee-80] in GitLab EE 8.3. -> - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. -> - GitLab Pages were ported to Community Edition in GitLab 8.16. +- [Introduced][ee-80] in GitLab EE 8.3. +- Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. +- GitLab Pages [were ported][ce-14605] to Community Edition in GitLab 8.17. +- This guide is for Omnibus GitLab installations. If you have installed + GitLab from source, follow the [Pages source installation document](source.md). --- @@ -89,8 +91,6 @@ In that case, the pages daemon is running, NGINX still proxies requests to the daemon but the daemon is also able to receive requests from the outside world. Custom domains and TLS are supported. -**Omnibus installations:** - 1. Edit `/etc/gitlab/gitlab.rb`: ```ruby @@ -108,67 +108,6 @@ world. Custom domains and TLS are supported. 1. [Reconfigure GitLab][reconfigure] ---- - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.4 - sudo -u git -H make - ``` - -1. Edit `gitlab.yml` to look like the example below. You need to change the - `host` to the FQDN under which GitLab Pages will be served. Set - `external_http` and `external_https` to the secondary IP on which the pages - daemon will listen for connections: - - ```yaml - ## GitLab Pages - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 443 - https: true - - external_http: 1.1.1.1:80 - external_https: 1.1.1.1:443 - ``` - -1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in - order to enable the pages daemon. In `gitlab_pages_options` the - `-pages-domain`, `-listen-http` and `-listen-https` must match the `host`, - `external_http` and `external_https` settings that you set above respectively. - The `-root-cert` and `-root-key` settings are the wildcard TLS certificates - of the `example.io` domain: - - ``` - gitlab_pages_enabled=true - gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80 -listen-https 1.1.1.1:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - -1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace - `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab - listens to. -1. Restart NGINX -1. [Restart GitLab][restart] - ### Option 2. Custom domains without HTTPS support | URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | @@ -180,8 +119,6 @@ In that case, the pages daemon is running, NGINX still proxies requests to the daemon but the daemon is also able to receive requests from the outside world. Custom domains and TLS are supported. -**Omnibus installations:** - 1. Edit `/etc/gitlab/gitlab.rb`: ```ruby @@ -196,63 +133,6 @@ world. Custom domains and TLS are supported. 1. [Reconfigure GitLab][reconfigure] ---- - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.4 - sudo -u git -H make - ``` - -1. Edit `gitlab.yml` to look like the example below. You need to change the - `host` to the FQDN under which GitLab Pages will be served. Set - `external_http` to the secondary IP on which the pages daemon will listen - for connections: - - ```yaml - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 80 - https: false - - external_http: 1.1.1.1:80 - ``` - -1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in - order to enable the pages daemon. In `gitlab_pages_options` the - `-pages-domain` and `-listen-http` must match the `host` and `external_http` - settings that you set above respectively: - - ``` - gitlab_pages_enabled=true - gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - -1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace - `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab - listens to. -1. Restart NGINX -1. [Restart GitLab][restart] - ### Option 3. Wildcard HTTPS domain without custom domains | URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | @@ -262,8 +142,6 @@ world. Custom domains and TLS are supported. Pages enabled, daemon is enabled and NGINX will proxy all requests to the daemon. Pages daemon doesn't listen to the outside world. -**Omnibus installations:** - 1. Place the certificate and key inside `/etc/gitlab/ssl` 1. In `/etc/gitlab/gitlab.rb` specify the following configuration: @@ -280,45 +158,6 @@ daemon. Pages daemon doesn't listen to the outside world. 1. [Reconfigure GitLab][reconfigure] ---- - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.4 - sudo -u git -H make - ``` -1. In `gitlab.yml`, set the port to `443` and https to `true`: - - ```bash - ## GitLab Pages - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 443 - https: true - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - -1. Restart NGINX -1. [Restart GitLab][restart] - ### Option 4. Wildcard HTTP domain without custom domains | URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | @@ -328,8 +167,6 @@ daemon. Pages daemon doesn't listen to the outside world. Pages enabled, daemon is enabled and NGINX will proxy all requests to the daemon. Pages daemon doesn't listen to the outside world. -**Omnibus installations:** - 1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: ```ruby @@ -338,66 +175,11 @@ daemon. Pages daemon doesn't listen to the outside world. 1. [Reconfigure GitLab][reconfigure] ---- - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.4 - sudo -u git -H make - ``` - -1. Go to the GitLab installation directory: - - ```bash - cd /home/git/gitlab - ``` - -1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and - the `host` to the FQDN under which GitLab Pages will be served: - - ```yaml - ## GitLab Pages - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 80 - https: false - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - -1. Restart NGINX -1. [Restart GitLab][restart] - -## Set maximum pages size - -The maximum size of the unpacked archive per project can be configured in the -Admin area under the Application settings in the **Maximum size of pages (MB)**. -The default is 100MB. - ## Change storage path Follow the steps below to change the default path where GitLab Pages' contents are stored. -**Omnibus installations:** - 1. Pages are stored by default in `/var/opt/gitlab/gitlab-rails/shared/pages`. If you wish to store them in another location you must set it up in `/etc/gitlab/gitlab.rb`: @@ -408,22 +190,11 @@ are stored. 1. [Reconfigure GitLab][reconfigure] ---- +## Set maximum pages size -**Source installations:** - -1. Pages are stored by default in `/home/git/gitlab/shared/pages`. - If you wish to store them in another location you must set it up in - `gitlab.yml` under the `pages` section: - - ```yaml - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - path: /mnt/storage/pages - ``` - -1. [Restart GitLab][restart] +The maximum size of the unpacked archive per project can be configured in the +Admin area under the Application settings in the **Maximum size of pages (MB)**. +The default is 100MB. ## Backup @@ -434,33 +205,6 @@ Pages are part of the [regular backup][backup] so there is nothing to configure. You should strongly consider running GitLab pages under a different hostname than GitLab to prevent XSS attacks. -## NGINX caveats - ->**Note:** -The following information applies only for installations from source. - -Be extra careful when setting up the domain name in the NGINX config. You must -not remove the backslashes. - -If your GitLab pages domain is `example.io`, replace: - -```bash -server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$; -``` - -with: - -``` -server_name ~^.*\.example\.io$; -``` - -If you are using a subdomain, make sure to escape all dots (`.`) except from -the first one with a backslash (\). For example `pages.example.io` would be: - -``` -server_name ~^.*\.pages\.example\.io$; -``` - ## Changelog GitLab Pages were first introduced in GitLab EE 8.3. Since then, many features @@ -493,6 +237,7 @@ latest previous version. [8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md [8-17-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-17-stable-ce/doc/administration/pages/index.md [backup]: ../raketasks/backup_restore.md +[ce-14605]: https://gitlab.com/gitlab-org/gitlab-ce/issues/14605 [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 [ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 [gitlab pages daemon]: https://gitlab.com/gitlab-org/gitlab-pages diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md new file mode 100644 index 00000000000..d4468b99992 --- /dev/null +++ b/doc/administration/pages/source.md @@ -0,0 +1,323 @@ +# GitLab Pages administration for source installations + +This is the documentation for configuring a GitLab Pages when you have installed +GitLab from source and not using the Omnibus packages. + +You are encouraged to read the [Omnibus documentation](index.md) as it provides +some invaluable information to the configuration of GitLab Pages. Please proceed +to read it before going forward with this guide. + +We also highly recommend that you use the Omnibus GitLab packages, as we +optimize them specifically for GitLab, and we will take care of upgrading GitLab +Pages to the latest supported version. + +## Overview + +[Read the Omnibus overview section.](index.md#overview) + +## Prerequisites + +[Read the Omnibus prerequisites section.](index.md#prerequisites) + +## Configuration + +Depending on your needs, you can install GitLab Pages in four different ways. + +### Option 1. Custom domains with HTTPS support + +| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `https://page.example.io` and `https://page.com` | yes | redirects to HTTPS | yes | yes | + +Pages enabled, daemon is enabled AND pages has external IP support enabled. +In that case, the pages daemon is running, NGINX still proxies requests to +the daemon but the daemon is also able to receive requests from the outside +world. Custom domains and TLS are supported. + +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.4 + sudo -u git -H make + ``` + +1. Edit `gitlab.yml` to look like the example below. You need to change the + `host` to the FQDN under which GitLab Pages will be served. Set + `external_http` and `external_https` to the secondary IP on which the pages + daemon will listen for connections: + + ```yaml + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 443 + https: true + + external_http: 1.1.1.2:80 + external_https: 1.1.1.2:443 + ``` + +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain`, `-listen-http` and `-listen-https` must match the `host`, + `external_http` and `external_https` settings that you set above respectively. + The `-root-cert` and `-root-key` settings are the wildcard TLS certificates + of the `example.io` domain: + + ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80 -listen-https 1.1.1.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace + `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + listens to. +1. Restart NGINX +1. [Restart GitLab][restart] + +### Option 2. Custom domains without HTTPS support + +| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `http://page.example.io` and `http://page.com` | no | yes | no | yes | + +Pages enabled, daemon is enabled AND pages has external IP support enabled. +In that case, the pages daemon is running, NGINX still proxies requests to +the daemon but the daemon is also able to receive requests from the outside +world. Custom domains and TLS are supported. + +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.4 + sudo -u git -H make + ``` + +1. Edit `gitlab.yml` to look like the example below. You need to change the + `host` to the FQDN under which GitLab Pages will be served. Set + `external_http` to the secondary IP on which the pages daemon will listen + for connections: + + ```yaml + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 80 + https: false + + external_http: 1.1.1.2:80 + ``` + +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain` and `-listen-http` must match the `host` and `external_http` + settings that you set above respectively: + + ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80" + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace + `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + listens to. +1. Restart NGINX +1. [Restart GitLab][restart] + +### Option 3. Wildcard HTTPS domain without custom domains + +| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `https://page.example.io` | yes | no | no | no | + +Pages enabled, daemon is enabled and NGINX will proxy all requests to the +daemon. Pages daemon doesn't listen to the outside world. + +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.4 + sudo -u git -H make + ``` +1. In `gitlab.yml`, set the port to `443` and https to `true`: + + ```bash + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 443 + https: true + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Restart NGINX +1. [Restart GitLab][restart] + +### Option 4. Wildcard HTTP domain without custom domains + +| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `http://page.example.io` | no | no | no | no | + +Pages enabled, daemon is enabled and NGINX will proxy all requests to the +daemon. Pages daemon doesn't listen to the outside world. + +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.4 + sudo -u git -H make + ``` + +1. Go to the GitLab installation directory: + + ```bash + cd /home/git/gitlab + ``` + +1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and + the `host` to the FQDN under which GitLab Pages will be served: + + ```yaml + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 80 + https: false + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Restart NGINX +1. [Restart GitLab][restart] + +## NGINX caveats + +>**Note:** +The following information applies only for installations from source. + +Be extra careful when setting up the domain name in the NGINX config. You must +not remove the backslashes. + +If your GitLab pages domain is `example.io`, replace: + +```bash +server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$; +``` + +with: + +``` +server_name ~^.*\.example\.io$; +``` + +If you are using a subdomain, make sure to escape all dots (`.`) except from +the first one with a backslash (\). For example `pages.example.io` would be: + +``` +server_name ~^.*\.pages\.example\.io$; +``` + +## Change storage path + +Follow the steps below to change the default path where GitLab Pages' contents +are stored. + +1. Pages are stored by default in `/home/git/gitlab/shared/pages`. + If you wish to store them in another location you must set it up in + `gitlab.yml` under the `pages` section: + + ```yaml + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + path: /mnt/storage/pages + ``` + +1. [Restart GitLab][restart] + +## Set maximum Pages size + +The maximum size of the unpacked archive per project can be configured in the +Admin area under the Application settings in the **Maximum size of pages (MB)**. +The default is 100MB. + +## Backup + +Pages are part of the [regular backup][backup] so there is nothing to configure. + +## Security + +You should strongly consider running GitLab pages under a different hostname +than GitLab to prevent XSS attacks. + +[backup]: ../raketasks/backup_restore.md +[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 +[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 +[gitlab pages daemon]: https://gitlab.com/gitlab-org/gitlab-pages +[NGINX configs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/8-5-stable-ee/lib/support/nginx +[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md +[pages-userguide]: ../../user/project/pages/index.md +[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure +[restart]: ../administration/restart_gitlab.md#installations-from-source +[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.4 From 01b14b2d050ed32dd25907cda5a5ea49dc99d6a0 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Thu, 2 Feb 2017 15:56:29 +0000 Subject: [PATCH 178/183] Fix rubocop error from pages EE->CE port --- config/initializers/1_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 0015ddf902d..71046d7860e 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -268,8 +268,8 @@ Settings.registry['path'] = File.expand_path(Settings.registry['path' Settings['pages'] ||= Settingslogic.new({}) Settings.pages['enabled'] = false if Settings.pages['enabled'].nil? Settings.pages['path'] = File.expand_path(Settings.pages['path'] || File.join(Settings.shared['path'], "pages"), Rails.root) -Settings.pages['host'] ||= "example.com" Settings.pages['https'] = false if Settings.pages['https'].nil? +Settings.pages['host'] ||= "example.com" Settings.pages['port'] ||= Settings.pages.https ? 443 : 80 Settings.pages['protocol'] ||= Settings.pages.https ? "https" : "http" Settings.pages['url'] ||= Settings.send(:build_pages_url) From 3c571baf5d8e2a81f8722a3b5cd38ae1f51988d8 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Thu, 2 Feb 2017 17:28:53 +0000 Subject: [PATCH 179/183] =?UTF-8?q?Use=20non-downtime=20migration=20for=20?= =?UTF-8?q?ApplicationSetting=E2=80=99s=20max=5Fpages=5Fsize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...3_add_pages_size_to_application_settings.rb | 11 ++++++++++- .../20160210105555_create_pages_domain.rb | 2 ++ db/schema.rb | 18 +++++++++--------- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/db/migrate/20151215132013_add_pages_size_to_application_settings.rb b/db/migrate/20151215132013_add_pages_size_to_application_settings.rb index e7fb73190e8..f3a663f805b 100644 --- a/db/migrate/20151215132013_add_pages_size_to_application_settings.rb +++ b/db/migrate/20151215132013_add_pages_size_to_application_settings.rb @@ -1,5 +1,14 @@ class AddPagesSizeToApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + def up - add_column :application_settings, :max_pages_size, :integer, default: 100, null: false + add_column_with_default :application_settings, :max_pages_size, :integer, default: 100, allow_null: false + end + + def down + remove_column(:application_settings, :max_pages_size) end end diff --git a/db/migrate/20160210105555_create_pages_domain.rb b/db/migrate/20160210105555_create_pages_domain.rb index 9af206143bd..0e8507c7e9a 100644 --- a/db/migrate/20160210105555_create_pages_domain.rb +++ b/db/migrate/20160210105555_create_pages_domain.rb @@ -1,4 +1,6 @@ class CreatePagesDomain < ActiveRecord::Migration + DOWNTIME = false + def change create_table :pages_domains do |t| t.integer :project_id diff --git a/db/schema.rb b/db/schema.rb index dc3d8c22e8d..9863367e312 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -61,7 +61,6 @@ ActiveRecord::Schema.define(version: 20170130204620) do t.boolean "shared_runners_enabled", default: true, null: false t.integer "max_artifacts_size", default: 100, null: false t.string "runners_registration_token" - t.integer "max_pages_size", default: 100, null: false t.boolean "require_two_factor_authentication", default: false t.integer "two_factor_grace_period", default: 48 t.boolean "metrics_enabled", default: false @@ -99,17 +98,18 @@ ActiveRecord::Schema.define(version: 20170130204620) do t.text "help_page_text_html" t.text "shared_runners_text_html" t.text "after_sign_up_text_html" - t.boolean "sidekiq_throttling_enabled", default: false - t.string "sidekiq_throttling_queues" - t.decimal "sidekiq_throttling_factor" t.boolean "housekeeping_enabled", default: true, null: false t.boolean "housekeeping_bitmaps_enabled", default: true, null: false t.integer "housekeeping_incremental_repack_period", default: 10, null: false t.integer "housekeeping_full_repack_period", default: 50, null: false t.integer "housekeeping_gc_period", default: 200, null: false + t.boolean "sidekiq_throttling_enabled", default: false + t.string "sidekiq_throttling_queues" + t.decimal "sidekiq_throttling_factor" t.boolean "html_emails_enabled", default: true t.string "plantuml_url" t.boolean "plantuml_enabled" + t.integer "max_pages_size", default: 100, null: false end create_table "audit_events", force: :cascade do |t| @@ -857,11 +857,11 @@ ActiveRecord::Schema.define(version: 20170130204620) do create_table "pages_domains", force: :cascade do |t| t.integer "project_id" - t.text "certificate" - t.text "encrypted_key" - t.string "encrypted_key_iv" - t.string "encrypted_key_salt" - t.string "domain" + t.text "certificate" + t.text "encrypted_key" + t.string "encrypted_key_iv" + t.string "encrypted_key_salt" + t.string "domain" end add_index "pages_domains", ["domain"], name: "index_pages_domains_on_domain", unique: true, using: :btree From a14f11390db5b848989e00b05a63e160bf5e0fdf Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Thu, 2 Feb 2017 17:31:54 +0000 Subject: [PATCH 180/183] Added Changelog for Pages to CE port --- changelogs/unreleased/jej-pages-picked-from-ee.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/jej-pages-picked-from-ee.yml diff --git a/changelogs/unreleased/jej-pages-picked-from-ee.yml b/changelogs/unreleased/jej-pages-picked-from-ee.yml new file mode 100644 index 00000000000..ee4a43a93db --- /dev/null +++ b/changelogs/unreleased/jej-pages-picked-from-ee.yml @@ -0,0 +1,4 @@ +--- +title: Added GitLab Pages to CE +merge_request: 8463 +author: From c6dac7da6f5402333ccdd6f95d9b7b11300719f4 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Thu, 2 Feb 2017 21:23:42 +0000 Subject: [PATCH 181/183] Mentioned pages to CE port in project/pages/index.md --- doc/user/project/pages/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index a07c23a3274..fe477b1ed0b 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -3,6 +3,7 @@ > **Notes:** > - This feature was [introduced][ee-80] in GitLab EE 8.3. > - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. +> - GitLab Pages [were ported][ce-14605] to Community Edition in GitLab 8.17. > - This document is about the user guide. To learn how to enable GitLab Pages > across your GitLab instance, visit the [administrator documentation](../../../administration/pages/index.md). From 67c8526033241f6bd190fa16622970b3919e6dbd Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Thu, 2 Feb 2017 22:21:06 +0000 Subject: [PATCH 182/183] Ported max_pages_size in settings API to CE --- lib/api/settings.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/api/settings.rb b/lib/api/settings.rb index c5eff16a5de..5206ee4f521 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -57,6 +57,7 @@ module API requires :shared_runners_text, type: String, desc: 'Shared runners text ' end optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size each build's artifacts can have" + optional :max_pages_size, type: Integer, desc: 'Maximum size of pages in MB' optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)' optional :metrics_enabled, type: Boolean, desc: 'Enable the InfluxDB metrics' given metrics_enabled: ->(val) { val } do @@ -115,7 +116,7 @@ module API :send_user_confirmation_email, :domain_whitelist, :domain_blacklist_enabled, :after_sign_up_text, :signin_enabled, :require_two_factor_authentication, :home_page_url, :after_sign_out_path, :sign_in_text, :help_page_text, - :shared_runners_enabled, :max_artifacts_size, :container_registry_token_expire_delay, + :shared_runners_enabled, :max_artifacts_size, :max_pages_size, :container_registry_token_expire_delay, :metrics_enabled, :sidekiq_throttling_enabled, :recaptcha_enabled, :akismet_enabled, :admin_notification_email, :sentry_enabled, :repository_storage, :repository_checks_enabled, :koding_enabled, :plantuml_enabled, From 5af4cae544c8526de63e639bd6c7db730526add3 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Fri, 3 Feb 2017 18:25:29 +0000 Subject: [PATCH 183/183] Fix documentation link in doc/user/project/pages/index.md --- doc/user/project/pages/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index fe477b1ed0b..b814e3fccb2 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -432,3 +432,4 @@ For a list of known issues, visit GitLab's [public issue tracker]. [pages-jekyll]: https://gitlab.com/pages/jekyll [metarefresh]: https://en.wikipedia.org/wiki/Meta_refresh [public issue tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Pages +[ce-14605]: https://gitlab.com/gitlab-org/gitlab-ce/issues/14605