Commit 44ddeb8a authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'gitlab-pages' into 'master'

GitLab Pages

Yay! It works!

**Key facts:**
- 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

**What is missing:**

- The Omnibus changes (I'll do it tomorrow)
- The proper documentation for Pages in this repo (@axil Can you help?)
- The blessing of everyone involved
- @DouweM help with Project Settings page: I put there the GitLab Pages deployed link and remove button 

**Related:**
- Omnibus changes: gitlab-org/omnibus-gitlab!565



See merge request !80
parents 63cfe86e 1ce3c34d
......@@ -20,6 +20,7 @@ v 8.3.0 (unreleased)
- Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
- Recognize issue/MR/snippet/commit links as references
- Add ignore whitespace change option to commit view
- Add GitLab Pages
- Fire update hook from GitLab
- Style warning about mentioning many people in a comment
- Fix: sort milestones by due date once again (Greg Smethells)
......
......@@ -3,5 +3,5 @@
# lib/support/init.d, which call scripts in bin/ .
#
web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
worker: bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default
worker: bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -q pages
# mail_room: bundle exec mail_room -q -c config/mail_room.yml
......@@ -66,6 +66,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:user_oauth_applications,
:shared_runners_enabled,
:max_artifacts_size,
:max_pages_size,
restricted_visibility_levels: [],
import_sources: []
)
......
......@@ -167,6 +167,16 @@ class ProjectsController < 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 toggle_star
current_user.toggle_star(@project)
@project.reload
......
......@@ -227,6 +227,7 @@ class Ability
:change_visibility_level,
:rename_project,
:remove_project,
:remove_pages,
:archive_project,
:remove_fork_project
]
......
......@@ -135,12 +135,8 @@ module Ci
predefined_variables + yaml_variables + project_variables + trigger_variables
end
def project
commit.project
end
def project_id
commit.project.id
gl_project_id
end
def project_name
......@@ -270,10 +266,9 @@ module Ci
build_data = Gitlab::BuildDataBuilder.build(self)
project.execute_hooks(build_data.dup, :build_hooks)
project.execute_services(build_data.dup, :build_hooks)
UpdatePagesService.new(build_data).execute
end
private
def yaml_variables
......
......@@ -118,6 +118,7 @@ class Namespace < ActiveRecord::Base
if gitlab_shell.mv_namespace(path_was, path)
Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
Gitlab::PagesTransfer.new.rename_namespace(path_was, path)
# If repositories moved successfully we need to
# send update instructions to users.
......
......@@ -64,6 +64,8 @@ class Project < ActiveRecord::Base
update_column(:last_activity_at, self.created_at)
end
after_destroy :remove_pages
ActsAsTaggableOn.strict_case_match = true
acts_as_taggable_on :tags
......@@ -792,6 +794,7 @@ class Project < ActiveRecord::Base
end
Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path)
Gitlab::PagesTransfer.new.rename_project(path_was, path, namespace.path)
end
def hook_attrs
......@@ -980,4 +983,37 @@ class Project < ActiveRecord::Base
def open_issues_count
issues.opened.count
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
# If the project path is the same as host, leave the short version
return url if host == path
"#{url}/#{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
# 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)
PagesWorker.perform_in(5.minutes, :remove, namespace.path, temp_path)
end
end
end
......@@ -55,6 +55,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)
true
end
end
......
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(:deploy, data[:build_id])
end
end
......@@ -21,7 +21,7 @@ class ArtifactUploader < CarrierWave::Uploader::Base
end
def artifacts_path
File.join(build.created_at.utc.strftime('%Y_%m'), build.project.id.to_s, build.id.to_s)
File.join(build.created_at.utc.strftime('%Y_%m'), build.project_id.to_s, build.id.to_s)
end
def store_dir
......
......@@ -135,6 +135,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
......
......@@ -175,7 +175,46 @@
.form-actions
= 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/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"
.danger-settings
- if can? current_user, :archive_project, @project
......
class PagesWorker
include Sidekiq::Worker
include Gitlab::CurrentSettings
BLOCK_SIZE = 32.kilobytes
MAX_SIZE = 1.terabyte
sidekiq_options queue: :pages, retry: false
def perform(action, *arg)
send(action, *arg)
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
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
......@@ -37,7 +37,7 @@ start_no_deamonize()
start_sidekiq()
{
bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q pages -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
}
load_ok()
......
......@@ -136,6 +136,19 @@ 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
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
## Gravatar
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
gravatar:
......
......@@ -6,7 +6,7 @@ class Settings < Settingslogic
class << self
def gitlab_on_standard_port?
gitlab.port.to_i == (gitlab.https ? 443 : 80)
on_standard_port?(gitlab)
end
# get host without www, thanks to http://stackoverflow.com/a/6674363/1233435
......@@ -19,7 +19,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}"
......@@ -32,6 +32,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}"
......@@ -47,11 +51,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
def kerberos_protocol
......@@ -103,14 +107,18 @@ 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
def on_standard_port?(config)
config.port.to_i == (config.https ? 443 : 80)
end
end
end
......@@ -261,6 +269,18 @@ Settings.artifacts['enabled'] = true if Settings.artifacts['enabled'].nil?
Settings.artifacts['path'] = File.expand_path(Settings.artifacts['path'] || File.join(Settings.shared['path'], "artifacts"), Rails.root)
Settings.artifacts['max_size'] ||= 100 # in megabytes
#
# Pages
#
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['port'] ||= Settings.pages.https ? 443 : 80
Settings.pages['protocol'] ||= Settings.pages.https ? "https" : "http"
Settings.pages['url'] ||= Settings.send(:build_pages_url)
#
# Git LFS
#
......
......@@ -418,6 +418,7 @@ Rails.application.routes.draw do
delete :remove_fork
post :archive
post :unarchive
post :remove_pages
post :toggle_star
post :markdown_preview
get :autocomplete_sources
......
class AddPagesSizeToApplicationSettings < ActiveRecord::Migration
def up
add_column :application_settings, :max_pages_size, :integer, default: 100, null: false
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20151210125932) do
ActiveRecord::Schema.define(version: 20151215132013) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -62,6 +62,7 @@ ActiveRecord::Schema.define(version: 20151210125932) 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
end
create_table "approvals", force: :cascade do |t|
......
......@@ -15,6 +15,7 @@
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
- [GitLab Pages](pages/README.md) Using GitLab Pages.
## CI Documentation
......@@ -70,6 +71,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.
- [Downgrade back to CE](downgrade_ee_to_ce/README.md) Follow this guide if you need to downgrade from EE to CE.
- [Git LFS configuration](workflow/lfs/lfs_administration.md)
- [GitLab Pages configuration](pages/administration.md)
## Contributor documentation
......
......@@ -269,6 +269,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 GitLab Pages 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
......@@ -433,6 +436,10 @@ Make sure to edit the config file to match your setup:
# or else sudo rm -f /etc/nginx/sites-enabled/default
sudo editor /etc/nginx/sites-available/gitlab
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).
**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
......
# GitLab Pages
_**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.
## 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:
script:
- gem install jekyll
- jekyll build -d public/
artifacts:
paths:
- public
```
## Example projects
Below is a list of example projects that make use of static generators.
Contributions are very welcome.
* [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll)
## 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.
## Frequently Asked Questions
**Q:** Can I download my generated pages?
**A:** 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
# 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 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:
```
*.gitlab.io. 60 IN A 1.2.3.4
```
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.
You should not use the GitLab domain to serve user pages. For more information
see the [security section](#security).
### Omnibus package installations
See the relevant documentation at <http://doc.gitlab.com/omnibus/settings/pages.html>.
### 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` 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
# 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
```
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 `gitlab.io`, replace
```bash
server_name ~^(?<group>.*)\.YOUR_GITLAB_PAGES\.DOMAIN$;
```
with
```
server_name ~^(?<group>.*)\.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 ~^(?<group>.*)\.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.
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: 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
```
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
```
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.
## 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
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
```
Restart GitLab for the changes to take effect:
```bash
sudo service gitlab restart
```
## 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 attacks.
[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80
[wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record
......@@ -27,15 +27,6 @@ sudo gitlab-rake gitlab:backup:create
# if you've installed GitLab from source
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
Also you can choose what should be backed up by adding environment variable SKIP. Available options: db,
uploads (attachments), repositories, builds(CI build output logs), artifacts (CI build artifacts), lfs (LFS objects).
Use a comma to specify several options at the same time.
```
sudo gitlab-rake gitlab:backup:create SKIP=db,uploads
```
Example output:
```
......@@ -65,6 +56,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.
......
......@@ -154,7 +154,7 @@ module Backup
end
def archives_to_backup
%w{uploads builds artifacts lfs}.map{ |name| (name + ".tar.gz") unless skipped?(name) }.compact
%w{uploads builds artifacts pages lfs}.map{ |name| (name + ".tar.gz") unless skipped?(name) }.compact
end
def folders_to_backup
......
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
module Gitlab
class PagesTransfer < ProjectTransfer
def root_dir
Gitlab.config.pages.path
end
end
end
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
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
......
## 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 ~^(?<group>.*)\.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;
}
# Define custom error pages
error_page 403 /403.html;
error_page 404 /404.html;
}
## 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 ~^(?<group>.*)\.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 ~^(?<group>.*)\.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;
## [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 /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;
}
# Define custom error pages
error_page 403 /403.html;
error_page 404 /404.html;
}
......@@ -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
backup = Backup::Manager.new
......@@ -35,6 +36,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?("artifacts")
Rake::Task["gitlab:backup:lfs:restore"].invoke unless backup.skipped?("lfs")
Rake::Task["gitlab:shell:setup"].invoke
......@@ -136,6 +138,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 ... ".blue
......
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
......
......@@ -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
......
require 'spec_helper'
describe UpdatePagesService, services: true do
let(:build) { create(:ci_build) }
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' }
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
......@@ -16,7 +16,7 @@ describe 'gitlab:app namespace rake task' do
end
def reenable_backup_sub_tasks
%w{db repo uploads builds artifacts lfs}.each do |subtask|
%w{db repo uploads builds artifacts pages lfs}.each do |subtask|
Rake::Task["gitlab:backup:#{subtask}:create"].reenable
end
end
......@@ -57,6 +57,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:shell:setup"]).to receive(:invoke)
expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error
......@@ -115,7 +116,7 @@ describe 'gitlab:app namespace rake task' do
it 'should set 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}
%W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz}
)
expect(exit_status).to eq(0)
expect(tar_contents).to match('db/')
......@@ -123,13 +124,14 @@ 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).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.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)\/$/)
end
it 'should delete temp directories' do
temp_dirs = Dir.glob(
File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,lfs}')
File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,pages,lfs}')
)
expect(temp_dirs).to be_empty
......@@ -165,7 +167,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}
%W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz}
)
expect(tar_contents).to match('db/')
......@@ -173,6 +175,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).not_to match('repositories/')
end
......@@ -186,6 +189,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:shell:setup"]).to receive :invoke
expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error
......
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.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.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.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.deploy(build.id)).to_not be_truthy
end
it 'fails for empty file fails' do
build.update_attributes(artifacts_file: empty_file)
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.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.deploy(build.id)).to_not be_truthy
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment