Web gallery solution

A while back, I was inspired by Thealaskalinuxuser’s article about how he set up his photo sharing server at home. I experimented briefly with piwigo back when I read that, but ended up stopping that project. When I returned to this topic, I realized I just didn’t want to deal with php. I applaud his stamina and commitment, but I want something with fewer dependencies. Here is my solution.

So, here is my internal documentation that is my alternative to Google Photos.

Gallery solution for internal network


As part of my goals to run self-hosted services for myself and my family, I intend to maintain a web gallery of photos and videos.
This solution consists of several parts.

  • Tools that create symlink forests to the original image files, in a specific location
  • Static site generator sigal
  • Customized theme for sigal
  • Customized sigal config for that theme
  • Gallery id table
  • SELinux rules
  • CGI scripts for Apache httpd


The solution runs on server1, the main file server for the internal network.

Tools that create symlink forests for the gallery source directories

Generate.sh is what I used for the proof of concept. This needs to be rewritten in python, and to handle files without exif data.
My python implementation will show up on this blog at a later date.

Static site generator

The sigal site generator is installed with pip3, under local service account `sigal` on server1. A special script, /usr/local/bin/sigal.bin will generate the static pages for a site. To use this script, pass a parameter of the source directory where the sigal.conf.py exists.

/usr/local/bin/sigal.bin /mnt/public/www/example/images/.gallery

Sigal depends on ffmpeg for video thumbnailing and conversions. On CentOS 8, ffmpeg is in repository powertools.
File sigal.bin

# File: /usr/local/bin/sigal.bin
# Author: bgstack15@gmail.com
# Startdate: 2021-01-24
# Title: Run sigal static site generator
# Purpose: run sigal static site generator for provided path
# History:
# Usage:
#    called by regen.cgi, or by hand
# Dependencies:
#    60_regen_gallery_sudo
#    gallery.te
#    service account "sigal" with `pip3 install --user sigal`
# Reverse dependencies:
#    httpd conf 

test -z "${GALLERY_ID}" && export GALLERY_ID="${1}"
test -z "${GALLERY_ID}" && { echo "Pass to this script the gallery_id or directory path to a sigal.conf.py. Aborted." 1>&2 ; exit 1 ; }
if test -d "${GALLERY_ID}" ;
   cd "${GALLERY_ID}"
   if test -e /etc/gallery-cgi.conf ;
      . /etc/gallery-cgi.conf
      # no gallery_id listing exists.
      echo "No gallery_id table exists. Aborted." 1>&2 ; exit 1
if test "${GALLERY_ID}" = "init" ;
   sudo su sigal -s /bin/bash -c "${sigal_home}/.local/bin/sigal init"
   eval cd \"\${"${GALLERY_ID}"}\"
   sudo su sigal -s /bin/bash -c "${sigal_home}/.local/bin/sigal build"

Customized theme for sigal

Use theme bgstack15-gallery-theme, in any location. You just need to put its full path as the value of the theme in a sigal.conf.py for a gallery.
This is a fork of the default, included colorbox theme. The full theme is available in this project directory, as well as a diff of it to the original as of sigal version 2.1.1.
The main changes are adding extra metadata handlers and link logic for the custom links related to editing metadata.

Customized sigal config for that theme

When you run sigal init in a directory, it generates a default config you can modify. The example theme, and this whole solution, depends on adding a number of settings. A summary of the specific options is here, but the full example file is in this project directory.

source = '/var/www/gallery/.my2018'
destination = '/var/www/gallery/my2018'
use_orig = True
edit_cgi_script = "/cgi-bin/gallery/edit.cgi" # web path to edit.cgi
edit_enabled = False
edit_password = "makeupapassword"
edit_string = '[edit]' # text of link to edit metadata
toggle_enable_string = "Enable editing"
toggle_disable_string = "Disable editing"
toggle_link = "/cgi-bin/gallery/toggle-editing.cgi" # web path to toggle-editing.cgi
toggle_editing = [
        (False, 'Enable editing'),
        (True, 'Disable editing')
gallery_id = "example_images"
# A list of links (tuples (title, URL))
# links = [('Example link', 'http://example.org'),
#          ('Another link', 'http://example.org')]
links = [
    ('Regenerate pages', '/cgi-bin/gallery/regen.cgi?id=' + gallery_id)

The `gallery_id` is very important, because the cgi scripts and theme rely on it.

Gallery id table

File /etc/gallery-cgi.conf contains a list of gallery_id translations to directories with sigal.conf.py rules.


SELinux rules

The reference system, server1, runs SELinux. A custom selinux module is needed to allow all the operations that are a part of this gallery solution, which include the following.

File gallery.te can be installed as an enabled selinux module.

sudo checkmodule -M -m -o gallery.mod gallery.te && sudo semodule_package -o gallery.pp -m gallery.mod && sudo semodule -i gallery.pp

File gallery.te:

# Last modified 2021-01-30
module gallery 1.0;

require {
	type faillog_t;
	type security_t;
	type httpd_config_t;
	type init_t;
	type sssd_t;
	type mnt_t;
	type lastlog_t;
	type systemd_logind_sessions_t;
	type initrc_var_run_t;
	type tmpfs_t;
	type gconf_home_t;
	type chkpwd_t;
	type systemd_logind_t;
	type unconfined_t;
	type shadow_t;
	type httpd_sys_script_t;
	type sssd_selinux_manager_t;
	type sssd_conf_t;
	type var_t;
	type httpd_t;
	class capability { audit_write dac_read_search net_admin setgid setuid sys_resource };
	class process { noatsecure rlimitinh setrlimit siginh };
	class netlink_audit_socket { create nlmsg_relay read write };
	class netlink_selinux_socket { bind create };
	class passwd rootok;
	class dir { add_name read remove_name search write };
	class file { create execute execute_no_trans setattr getattr link lock map open read unlink write ioctl };
	class dbus send_msg;
	class fifo_file write;
	class security compute_av;
	class lnk_file read;
	class filesystem getattr;
	class process setfscreate;

#============= httpd_sys_script_t ==============
allow httpd_sys_script_t faillog_t:file { open read };
allow httpd_sys_script_t var_t:file { create ioctl setattr unlink write };
allow httpd_sys_script_t var_t:dir { read add_name remove_name write };

#!!!! This avc can be allowed using the boolean 'domain_can_mmap_files'
allow httpd_sys_script_t gconf_home_t:file map;
allow httpd_sys_script_t gconf_home_t:file { execute execute_no_trans };
allow httpd_sys_script_t httpd_config_t:dir search;
allow httpd_sys_script_t initrc_var_run_t:file { lock open read };
allow httpd_sys_script_t lastlog_t:file { open read write };
allow httpd_sys_script_t mnt_t:lnk_file read;
allow httpd_sys_script_t security_t:dir read;
allow httpd_sys_script_t security_t:file { getattr open read write };
allow httpd_sys_script_t security_t:security compute_av;
allow httpd_sys_script_t self:capability { audit_write dac_read_search net_admin setgid setuid sys_resource };
allow httpd_sys_script_t self:netlink_audit_socket { create nlmsg_relay read write };
allow httpd_sys_script_t self:netlink_selinux_socket { bind create };
allow httpd_sys_script_t self:passwd rootok;
allow httpd_sys_script_t self:process setrlimit;
allow httpd_sys_script_t shadow_t:file { getattr open read };
allow httpd_sys_script_t sssd_conf_t:dir search;
allow httpd_sys_script_t sssd_conf_t:file { getattr open read };
allow httpd_sys_script_t systemd_logind_sessions_t:fifo_file write;
allow httpd_sys_script_t systemd_logind_t:dbus send_msg;
allow httpd_sys_script_t tmpfs_t:dir { add_name remove_name write };

#!!!! This avc can be allowed using the boolean 'domain_can_mmap_files'
allow httpd_sys_script_t tmpfs_t:file map;
allow httpd_sys_script_t tmpfs_t:file { create getattr link open read unlink write };
allow httpd_sys_script_t tmpfs_t:filesystem getattr;
allow httpd_sys_script_t self:process setfscreate;

#============= init_t ==============
allow init_t chkpwd_t:process siginh;
allow init_t unconfined_t:process siginh;

#============= sssd_t ==============
allow sssd_t sssd_selinux_manager_t:process { noatsecure rlimitinh siginh };

#============= systemd_logind_t ==============
allow systemd_logind_t httpd_sys_script_t:dbus send_msg;
#============= httpd_t ==============
allow httpd_t var_t:file { getattr map open read };

Sudo rules

For apache httpd to be able to run the sigal.bin, set up sudoers rules. File 60_regen_gallery_sudo adds the permission necessary.

# file: /etc/sudoers.d/60_regen_gallery_sudo
# Reference: server4:/etc/sudoers.d/60_starbound_sudo
apache ALL=(root)   NOPASSWD: /usr/local/bin/sigal.bin *

CGI scripts for Apache httpd

The main focus of the gallery project is the ability to edit metadata from the web view. While sigal is great for developers, some users might only care about editing metadata from where they are actually viewing the media.

  • edit.cgi is called from the custom theme’s “edit” links, and includes the form for making changes to media metadata.
  • apply.cgi actually makes the changes, and is called from the edit.cgi form.
  • regen.cgi invokes sigal.bin which re-runs sigal.
  • toggle-editing.cgi enables or disables editing. Enabling requires a password.

These can be placed anywhere you have enabled CGI for httpd, but the canonical location is /var/www/cgi-bin/gallery/.


I anticipate that more work is needed on an ongoing basis. Here are some processes that can be used.

Make a new gallery

To establish a new gallery, change directory to the source directory for the gallery and run command

sigal.bin init

Which generates the basic sigal.conf.py. Add the pertinent variables, described in section “Customized sigal config for that theme” above.

Run sigal from command line

While the web links for “regen.cgi” are great for when you are viewing the web, you can also run the sigal.bin from the cli. You need to include a path to the directory that holds a sigal.conf.py, or else a gallery_id from /etc/gallery-cgi.conf.

sigal.bin example_images

Make metadata changes directly on filesystem

You can of course, as designed by the author of sigal, go edit any ${IMAGENAME%%.jpg}.md file with the relevant fields. See references 1 and 2 for the available fields.
File index.md will be the metadata for the directory itself.


In 2020, I installed piwigo on a dev system. I didn’t want to deal with php, so I dropped it. In January 2021, I started listing various options for self-hosted galleries. Read heading [Related Files] for those.
Criteria I assembled includes

  • Metadata: description, date, comments

Related Files

These files are important to this gallery project. Check them all out at my gitlab space.

  • /usr/local/bin/sigal.bin
  • /var/www/cgi-bin/apply.cgi
  • /var/www/cgi-bin/edit.cgi
  • /var/www/cgi-bin/regen.cgi
  • /var/www/cgi-bin/toggle-editing.cgi
  • gallery.te
  • /etc/sudoers.d/60_regen_gallery_sudo
  • /etc/gallery-cgi.conf
  • sigal.conf.py
  • bgstack15-gallery-theme/


Ones I considered without trying

Ones I listed as tolerable, but not focused on what I need.



  1. http://sigal.saimon.org/en/latest/album_information.html
  2. http://sigal.saimon.org/en/latest/image_information.html
  3. Home photo server, part 2: Apache, phpAlbum, and Piwigo | thealaskalinuxuser Thealaskalinuxuser’s guide to a home photo server with piwigo

2 thoughts on “Web gallery solution

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.