Merge branch 'release/1.0.0'

This commit is contained in:
Simon Brooke 2017-09-12 17:05:16 +01:00
commit 1f2bb0bc6d
17 changed files with 415 additions and 134 deletions

View file

@ -1,3 +1,29 @@
FROM tomcat:alpine FROM alpine:3.6
COPY target/smeagol-*-standalone.war $CATALINA_HOME/webapps/smeagol.war
MAINTAINER Simon Brooke <simon@journeyman.cc>
ENV JAVA_HOME=/usr/lib/jvm/default-jvm
RUN apk add --no-cache openjdk8 && \
ln -sf "${JAVA_HOME}/bin/"* "/usr/bin/"
# ensure the directories I'm going to write to actually exist!
RUN mkdir -p /usr/local/bin
RUN mkdir -p /usr/local/etc
COPY target/smeagol-*-standalone.jar /usr/local/bin/smeagol.jar
COPY resources/passwd /usr/local/etc/passwd
COPY resources/config.edn /usr/local/etc/config.edn
COPY resources/public/content /usr/local/etc/content
ENV SMEAGOL_CONFIG=/usr/local/etc/config.edn
ENV SMEAGOL_CONTENT_DIR=/usr/local/etc/content
ENV SMEAGOL_PASSWD=/usr/local/etc/passwd
ENV TIMBRE_DEFAULT_STACKTRACE_FONTS="{}"
ENV TIMBRE_LEVEL=':info'
ENV PORT=80
EXPOSE 80
CMD java -jar /usr/local/bin/smeagol.jar

View file

@ -1,4 +1,4 @@
(defproject smeagol "1.0.0-rc3" (defproject smeagol "1.0.0"
:description "A simple Git-backed Wiki inspired by Gollum" :description "A simple Git-backed Wiki inspired by Gollum"
:url "https://github.com/simon-brooke/smeagol" :url "https://github.com/simon-brooke/smeagol"
:license {:name "GNU General Public License,version 2.0 or (at your option) any later version" :license {:name "GNU General Public License,version 2.0 or (at your option) any later version"
@ -8,9 +8,7 @@
[com.cemerick/url "0.1.1"] [com.cemerick/url "0.1.1"]
[com.fzakaria/slf4j-timbre "0.3.7"] [com.fzakaria/slf4j-timbre "0.3.7"]
[com.taoensso/encore "2.92.0"] [com.taoensso/encore "2.92.0"]
[com.cemerick/url "0.1.1"]
[com.taoensso/timbre "4.10.0"] [com.taoensso/timbre "4.10.0"]
[com.fzakaria/slf4j-timbre "0.3.7"]
[com.taoensso/tower "3.0.2" :exclusions [com.taoensso/encore]] [com.taoensso/tower "3.0.2" :exclusions [com.taoensso/encore]]
[crypto-password "0.2.0"] [crypto-password "0.2.0"]
[environ "1.1.0"] [environ "1.1.0"]
@ -64,7 +62,7 @@
;; ["vcs" "tag"] -- not working, problems with secret key ;; ["vcs" "tag"] -- not working, problems with secret key
["clean"] ["clean"]
["bower" "install"] ["bower" "install"]
["ring" "uberwar"] ["ring" "uberjar"]
["docker" "build"] ["docker" "build"]
["docker" "push"] ["docker" "push"]
["change" "version" "leiningen.release/bump-version"] ["change" "version" "leiningen.release/bump-version"]

View file

@ -22,14 +22,22 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; config.edn: a simple configuration map for Smeagol; inspired by Cryogen. ;;; config.edn: a simple configuration map for Smeagol; inspired by Cryogen.
;;; This is top-level configuration. ;;; This is top-level configuration. All values can be overridden with
;;; environment variables.
;; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ; ; ;
{ {
:site-title "Smeagol" ;; overall title of the site, used in page headings :content-dir "resources/public/content"
:default-locale "en-GB" ;; default language used for messages ;; where content is served from.
:formatters {"vega" smeagol.formatting/process-vega :default-locale "en-GB" ;; default language used for messages
:formatters {"vega" smeagol.formatting/process-vega
"vis" smeagol.formatting/process-vega "vis" smeagol.formatting/process-vega
"mermaid" smeagol.formatting/process-mermaid "mermaid" smeagol.formatting/process-mermaid
"backticks" smeagol.formatting/process-backticks} "backticks" smeagol.formatting/process-backticks}
:log-level :info ;; the minimum logging level; one of
;; :trace :debug :info :warn :error :fatal
:passwd "resources/passwd"
;; where the password file is stored
:site-title "Smeagol" ;; overall title of the site, used in
;; page headings
} }

View file

@ -1 +1 @@
{:admin {:admin true, :email "info@weft.scot", :password "admin"}, :jenny {:email "jenny@auchencairn.org", :admin false, :password "$s0$f0801$1uniQfftB37G5e5GklJANQ==$kQ0+/YcCuaz2x5iYjwhNlDlnWX/exE/8pSC+R4C0WvQ="}} {:admin {:admin true, :email "info@weft.scot", :password "admin"}, :simon {:email "simon@journeyman.cc", :admin true, :password "$s0$f0801$sqhbxtzK6nx9RnVUhwtQlg==$dMIUbof8esjsGyiB+zb3gMH21L/WSCR+wD3vIag4EVc="}}

View file

@ -2,19 +2,32 @@ Smeagol reads a configuration file, whose content should be formatted as a cloju
The default content is as follows: The default content is as follows:
{ ```
:site-title "Smeagol" ;; overall title of the site, used in page headings {
:default-locale "en-GB" ;; default language used for messages :site-title "Smeagol" ;; overall title of the site, used in page headings
:formatters {"vega" smeagol.formatting/process-vega :default-locale "en-GB" ;; default language used for messages
"vis" smeagol.formatting/process-vega :content-dir "/usr/local/etc/content"
"mermaid" smeagol.formatting/process-mermaid ;; where content is served from
"backticks" smeagol.formatting/process-backticks} :passwd "/usr/local/etc/passwd"
} ;; where the password file is stored
:log-level :info ;; the minimum logging level; one of
;; :trace :debug :info :warn :error :fatal
:formatters {"vega" smeagol.formatting/process-vega
"vis" smeagol.formatting/process-vega
"mermaid" smeagol.formatting/process-mermaid
"backticks" smeagol.formatting/process-backticks}
}
```
The three keys given above should be present. The values should be: The values should be:
* **:site-title** The title for your wiki * `:content-dir` The directory in which your editable content is stored;
* **:default-locale** A string comprising a lower-case [ISO 639](https://en.wikipedia.org/wiki/ISO_639) code specifying a language, optionally followed by a hyphen and an upper-case [ISO 3166](https://en.wikipedia.org/wiki/ISO_3166) specifying a country. * `:default-locale` A string comprising a lower-case [ISO 639](https://en.wikipedia.org/wiki/ISO_639) code specifying a language, optionally followed by a hyphen and an upper-case [ISO 3166](https://en.wikipedia.org/wiki/ISO_3166) specifying a country.
* **:formatters** A map of formatters used in [[Extensible Markup]], q.v. * `:formatters` A map of formatters used in [[Extensible Markup]], q.v.
* `:log-level` The minimum level of log messages to be logged; one of `:trace :debug :info :warn :error :fatal`
* `:passwd` The path to your `passwd` file - see [[Security and authentication]];
* `:site-title` The title for your wiki.
The default file is at `resources/config.edn`; this default can be overridden by providing an environment variable, `SMEAGOL_CONFIG`, whose value is the full or relative pathname of a suitable file. The default file is at `resources/config.edn`; this default can be overridden by providing an environment variable, `SMEAGOL_CONFIG`, whose value is the full or relative pathname of a suitable file.
Note that all the values in the configuration can be overridden with [[Environment Variables]].

View file

@ -1,3 +1,8 @@
## Choosing a deployment mechanism
There are currently three ways you can deploy Smeagol: as an executable Jar file, as a Docker image, and as a web-app in a [Servlet container](https://en.wikipedia.org/wiki/Web_container). Each method has advantages and disadvantages.
The Jar file is extremely easy to deploy and to configure, but cannot currently serve [HTTPS](https://en.wikipedia.org/wiki/HTTPS), which, on the modern web, is a significant disadvantage. The Docker image is just a wrapper around the Jar file; it's particularly suitable for automated deployment. The web-app solution offloads responsibility for things like HTTPS to the Servlet container, and consequently can be much more secure; but it can really only be configured at compile time.
## Deploying as a stand-alone application ## Deploying as a stand-alone application
To deploy Smeagol as a stand-alone application, either download the jar file for the release you want to deploy, or clone the source and compile it with: To deploy Smeagol as a stand-alone application, either download the jar file for the release you want to deploy, or clone the source and compile it with:
@ -6,19 +11,26 @@ To deploy Smeagol as a stand-alone application, either download the jar file for
This will create a jar file in the `target` directory, named `smeagol-`*VERSION*`-standalone.jar`. This will create a jar file in the `target` directory, named `smeagol-`*VERSION*`-standalone.jar`.
Smeagol cannot access either its configuration or its content from the jar file, as otherwise they would not be editable. Consequently you should set up three environment variables: Smeagol cannot access either its configuration or its content from the jar file, as otherwise they would not be editable. There are three solutions to this:
1. `SMEAGOL_CONFIG` should be the full or relative pathname of a Smeagol [[Configuration]] file; ### Custom configuration file
2. `SMEAGOL_CONTENT_DIR` should be the full or relative pathname of the directory from which Smeagol should serve content (which may initially be empty, but must be writable by the process which runs Smeagol)' You can copy the standard configuration file `resources/config.edn` to somewhere outside the jar file, edit it to suit your installation, and set up a single environment variable, `SMEAGOL_CONFIG`, whose value is the path to your new configuration file.
3. `SMEAGOL_PASSWD` should be the full or relative pathname of a Smeagol Passwd file - see [[Security and authentication]]. This file must contain an entry for at least your initial user, and, if you want to administer users through the user interface, must be writable by the process which runs Smeagol.
**NOTE** that `SMEAGOL_CONTENT_DIR` must contain at least the following files: ### Environment variables
Alternatively, you can configure everything through [[Environment Variables]].
### Hybrid strategy
You can have both a configuration file and environment variables. If you do this, the environment variables override the values in the configuration file.
### Necessary content
**NOTE** that the directory at `SMEAGOL_CONTENT_DIR` must contain at least the following files:
1. `_edit-side-bar.md` - the side-bar that should be displayed when editing pages; 1. `_edit-side-bar.md` - the side-bar that should be displayed when editing pages;
2. `_header.md` - the header to be displayed on all pages; 2. `_header.md` - the header to be displayed on all pages;
3. `_side-bar.md` - the side-bar that should be displayed when not editing pages. 3. `_side-bar.md` - the side-bar that should be displayed when not editing pages.
Standard versions of these files can be found in the [source repository](https://github.com/journeyman-cc/smeagol/tree/master/resources/public/content). All these files should be in markdown format - see [[Extensible Markup]]. Standard versions of these files can be found in the [source repository](https://github.com/journeyman-cc/smeagol/tree/master/resources/public/content).
You can run the jar file with: You can run the jar file with:
@ -32,7 +44,9 @@ To deploy Smeagol within a servlet container, either download the jar file for t
This will create a war file in the `target` directory, named `smeagol-`*VERSION*`-standalone.war`. Deploy this to your servlet container in the normal way; details will depend on your container. Instructions for Tomcat are [here](https://tomcat.apache.org/tomcat-8.0-doc/deployer-howto.html). This will create a war file in the `target` directory, named `smeagol-`*VERSION*`-standalone.war`. Deploy this to your servlet container in the normal way; details will depend on your container. Instructions for Tomcat are [here](https://tomcat.apache.org/tomcat-8.0-doc/deployer-howto.html).
The problem with this is that unless the environment variables (see above) were already set up in the environment of the servlet container at the time when the servlet container were launched, Smeagol will run with its built-in defaults. This will run perfectly satisfactorily provided your servlet container is configured to unpack war files, which most are. The problem with this is that unless the environment variables (see above) were already set up in the environment of the servlet container at the time when the servlet container were launched, Smeagol will run with its built-in defaults. If you want to change the defaults, you would have to edit the `resources/config.edn` file and recompile the war file.
Smeagol will run as a web-app with the default configuration perfectly satisfactorily.
## Experimental Docker image ## Experimental Docker image

View file

@ -0,0 +1,55 @@
Smeagol is available as a Docker image
To run my Docker image, use
docker run -p 127.0.0.1:80:80 simonbrooke/smeagol
Where 127.0.0.1 is the IP address through which you want to forward port 80 (in real life it wouldn't be 127.0.0.1, but that's safe for testing).
You can then browse to Smeagol by pointing your browser at http://localhost/.
As of version 0.99.10, the Docker image is now based on the Jetty, rather than the Tomcat, deployment of Smeagol (that is to say, it runs the executable jar file). This makes for a lighter weight Docker image. All configuration can be overridden with [[Environment Variables]], which can be passed into the Docker container when the image is invoked, or from a [[Configuration]] file.
The `config.edn` and `passwd` files and the `content` directory are copied into `/usr/local/etc` in the Docker image, and the appropriate environment variables are set up to point to them:
```
COPY resources/passwd /usr/local/etc/passwd
COPY resources/config.edn /usr/local/etc/config.edn
COPY resources/public/content /usr/local/etc/content
ENV SMEAGOL_CONFIG=/usr/local/etc/config.edn
ENV SMEAGOL_CONTENT_DIR=/usr/local/etc/content
ENV SMEAGOL_PASSWD=/usr/local/etc/passwd
```
This works for play purposes. However, it means that any edits made to either the `passwd` file or the `content` directory will be lost when the Docker image is shut down. You really need to have these resources copied to a place in a real file system which is mounted by the image. While I intend that by the 1.1.0 release of Smeagol it will be possible to configure a remote origin repository to which changes are periodically pushed, which will backup and preserve the content, this won't save the `passwd` file, as this is deliberately not stored in the git repository for security reasons.
## Mounting real file systems
It's possible to mount external file systems, and to override environment variables, with arguments to Docker's extraordinarily complex [run command](https://docs.docker.com/engine/reference/commandline/run/).
I'm currently working with a recipe:
docker run -p 127.0.0.1:80:80 -v ~/tmp/etc:/usr/local/etc simonbrooke/smeagol
Where:
1. `127.0.0.1` is the IP address on the real host on which you wish to serve;
2. `:80:80` maps port 80 on the image to port 80 on the specified IP address;
3. `~/tmp/etc` is the directory on the file system of the real host where files are stored;
4. `/usr/local/etc` is the directory within the image file system to which that will be mounted;
This works, and uses the default values of the environment variables which are set up in the Docker image. However, I'm very much prepared to believe there are better recipes.
## Status
This image is _experimental_, but it does seem to work fairly well. What it does **not** yet do, however, is push the git repository to a remote location, so when you tear the Docker image down your edits will be lost. My next objective for this image is for it to have a cammand line parameter being the git address of a repository from which it can initialise the Wiki content, and to which it will periodically push local changes to the Wiki content.
## Building the Docker image
To build your own Docker image, run:
lein clean
lein bower install
lein ring uberjar
lein docker build
This will build a new Docker image locally; you can, obviously, push it to your own Docker repository if you wish.

View file

@ -0,0 +1,23 @@
## Smeagol-specific environment variables
Smeagol can be configured entirely with environment variables. The variables are:
1. `SMEAGOL_CONFIG` (optional but advised) should be the full or relative pathname of a Smeagol [[Configuration]] file;
2. `SMEAGOL_CONTENT_DIR` should be the full or relative pathname of the directory from which Smeagol should serve content (which may initially be empty, but must be writable by the process which runs Smeagol);
3. `SMEAGOL_DEFAULT_LOCALE` which should be a locale specification in the form "en-GB", "fr-FR", or whatever to suit your users;
4. `SMEAGOL_FORMATTERS` should be an [edn](https://github.com/edn-format/edn)-formatted map of formatter directives (this would be pretty hard to do from an environment variable);
5. `SMEAGOL_LOG_LEVEL` which should be one of `TRACE DEBUG INFO WARN ERROR FATAL`
6. `SMEAGOL_PASSWD` should be the full or relative pathname of a Smeagol Passwd file - see [[Security and authentication]]. This file must contain an entry for at least your initial user, and, if you want to administer users through the user interface, must be writable by the process which runs Smeagol.
7. `SMEAGOL_SITE_TITLE` which should be the title you want shown on the header of all pages.
You can have both a configuration file and environment variables; if you do, the values of the environment variables take precedence over the values in the config file.
## Other environment variables
If Smeagol is compiled as an executable jar file, the actual web server component is [Ring server](https://github.com/weavejester/ring-server). This recognises the `PORT` environment variable, and, if this is present and its value is a positive integer, will listen on the specified port (otherwise its default is 3000, which is... unusual).
Smeagol uses the [Timbre](https://github.com/ptaoussanis/timbre) logging library. This recognises the following environment variables:
1. `TIMBRE_DEFAULT_STACKTRACE_FONTS` Timbre by default colourises stacktrace dumps using ANSI terminal codes. This can be quite useful in a console, but is a real pain in a log file. To turn colourised stacktraces off, set the value of this to an empty string;
2. `TIMBRE_LEVEL` Sets the minimum logging level; but there are two problems with this. The first is that the environment variable is only read at compile time not at run time, and the second is that the syntax is a bit odd, which is why I've implemented `SMEAGOL_LOG_LEVEL` (above);
3. `TIMBRE_NS_WHITELIST` Sets a list of [Clojure namespaces](https://clojure.org/reference/namespaces) from which messages should be logged; however this is only read at compile time so isn't much use in practice;
4. `TIMBRE_NS_BLACKLIST` As above, but sets a list of namespaces from which messages should **not** be logged.

View file

@ -265,6 +265,14 @@ th {
padding: 0 2em 0 0; padding: 0 2em 0 0;
} }
.sanity-cause .sanity-stacktrace {
display: none;
}
.sanity-cause:hover .sanity-stacktrace {
display: block;
}
.vega-bindings, .vega-actions { .vega-bindings, .vega-actions {
font-size: 66%; font-size: 66%;
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View file

@ -4,6 +4,7 @@
(:require [crypto.password.scrypt :as password] (:require [crypto.password.scrypt :as password]
[environ.core :refer [env]] [environ.core :refer [env]]
[noir.io :as io] [noir.io :as io]
[smeagol.configuration :refer [config]]
[taoensso.timbre :as timbre])) [taoensso.timbre :as timbre]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -37,8 +38,8 @@
;; the relative path to the password file. ;; the relative path to the password file.
(def password-file-path (def password-file-path
(or (or
(env :smeagol-passwd) (:passwd config)
(str (clojure.java.io/resource "passwd")))) (str (io/resource-path) "../passwd")))
(defn- get-users (defn- get-users
@ -112,7 +113,7 @@
(timbre/info (str "Successfully changed password for user " username)) (timbre/info (str "Successfully changed password for user " username))
true)) true))
(catch Exception any (catch Exception any
(timbre/error (timbre/error any
(format "Changing password failed for user %s failed: %s (%s)" (format "Changing password failed for user %s failed: %s (%s)"
username (.getName (.getClass any)) (.getMessage any))) username (.getName (.getClass any)) (.getMessage any)))
false)))) false))))
@ -162,7 +163,7 @@
(timbre/info "Successfully added user " username) (timbre/info "Successfully added user " username)
true) true)
(catch Exception any (catch Exception any
(timbre/error (timbre/error any
(format "Adding user %s failed: %s (%s)" (format "Adding user %s failed: %s (%s)"
username (.getName (.getClass any)) (.getMessage any))) username (.getName (.getClass any)) (.getMessage any)))
false))))) false)))))
@ -179,7 +180,7 @@
(timbre/info (str "Successfully deleted user " username)) (timbre/info (str "Successfully deleted user " username))
true) true)
(catch Exception any (catch Exception any
(timbre/error (timbre/error any
(format "Deleting user %s failed: %s (%s)" (format "Deleting user %s failed: %s (%s)"
username (.getName (.getClass any)) (.getMessage any))) username (.getName (.getClass any)) (.getMessage any)))
false)))) false))))

View file

@ -1,7 +1,9 @@
(ns ^{:doc "Read and make available configuration." (ns ^{:doc "Read and make available configuration."
:author "Simon Brooke"} :author "Simon Brooke"}
smeagol.configuration smeagol.configuration
(:require [environ.core :refer [env]] (:require [clojure.pprint :refer [pprint]]
[clojure.string :as s]
[environ.core :refer [env]]
[noir.io :as io] [noir.io :as io]
[taoensso.timbre :as timbre])) [taoensso.timbre :as timbre]))
@ -37,7 +39,6 @@
;;;; ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def config-file-path (def config-file-path
"The relative path to the config file." "The relative path to the config file."
(or (or
@ -45,10 +46,92 @@
(str (io/resource-path) "../config.edn"))) (str (io/resource-path) "../config.edn")))
(def config (defn- from-env-vars
"The actual configuration, as a map." "Read a map from those of these environment variables which have values"
[& vars]
(reduce
#(let [v (env %2)]
(if v (assoc %1 %2 v) %1))
{}
vars))
(defn to-keyword
"Convert this argument into an idiomatic clojure keyword."
[arg]
(if (and arg (not (keyword? arg)))
(keyword
(s/lower-case
(s/replace (str arg) #"[^A-Za-z0-9]+" "-")))
arg))
(defn transform-map
"transform this map `m` by applying these `transforms`. Each transforms
is expected to comprise a map with the keys :from and :to, whose values
are respectively a key to match and a key to replace that match with,
and optionally a key :transform, whose value is a function of one
argument to be used to transform the value of that key."
[m tuples]
(timbre/debug
"transform-map:\n"
(with-out-str (clojure.pprint/pprint m)))
(reduce
(fn [m tuple]
(if
(and (map? tuple) (map? m) (m (:from tuple)))
(let [old-val (m (:from tuple))
t (:transform tuple)]
(assoc
(dissoc m (:from tuple))
(:to tuple)
(if-not
(nil? t)
(eval (list t old-val)) old-val)))
m))
m
tuples))
(def config-env-transforms
"Transforms to use with `transform-map` to convert environment
variable names (which need to be specific) into the shorter names
used internally"
'( {:from :smeagol-content-dir :to :content-dir}
{:from :smeagol-default-locale :to :default-locale}
{:from :smeagol-formatters :to :formatters :transform read-string}
{:from :smeagol-log-level :to :log-level :transform to-keyword}
{:from :smeagol-passwd :to :passwd}
{:from :smeagol-site-title :to :site-title}))
(defn build-config
[]
"The actual configuration, as a map. The idea here is that the config
file is read (if it is specified and present), but that individual
values can be overridden by environment variables."
(try (try
(read-string (slurp config-file-path)) (let [file-contents (try
(read-string (slurp config-file-path))
(catch Exception _ {}))
config (merge
file-contents
(transform-map
(from-env-vars
:smeagol-content-dir
:smeagol-default-locale
:smeagol-formatters
:smeagol-log-level
:smeagol-passwd
:smeagol-site-title)
config-env-transforms))]
(if (env :dev)
(timbre/debug
"Loaded configuration\n"
(with-out-str (clojure.pprint/pprint config))))
config)
(catch Exception any (catch Exception any
(timbre/error "Could not load configuration" any) (timbre/error any "Could not load configuration")
{}))) {})))
(def config (build-config))

View file

@ -2,6 +2,7 @@
:author "Simon Brooke"} :author "Simon Brooke"}
smeagol.handler smeagol.handler
(:require [clojure.java.io :as cjio] (:require [clojure.java.io :as cjio]
[clojure.string :refer [lower-case]]
[compojure.core :refer [defroutes]] [compojure.core :refer [defroutes]]
[compojure.route :as route] [compojure.route :as route]
[cronj.core :as cronj] [cronj.core :as cronj]
@ -11,6 +12,7 @@
[noir.util.middleware :refer [app-handler]] [noir.util.middleware :refer [app-handler]]
[ring.middleware.defaults :refer [site-defaults]] [ring.middleware.defaults :refer [site-defaults]]
[selmer.parser :as parser] [selmer.parser :as parser]
[smeagol.configuration :refer [config]]
[smeagol.routes.wiki :refer [wiki-routes]] [smeagol.routes.wiki :refer [wiki-routes]]
[smeagol.middleware :refer [load-middleware]] [smeagol.middleware :refer [load-middleware]]
[smeagol.session-manager :as session-manager] [smeagol.session-manager :as session-manager]
@ -43,6 +45,7 @@
(defn user-access [request] (defn user-access [request]
(session/get :user)) (session/get :user))
(defroutes base-routes (defroutes base-routes
(route/resources "/") (route/resources "/")
(route/not-found "Not Found")) (route/not-found "Not Found"))
@ -69,14 +72,18 @@
{:rotor (rotor/rotor-appender {:rotor (rotor/rotor-appender
{:path "smeagol.log" {:path "smeagol.log"
:max-size (* 512 1024) :max-size (* 512 1024)
:backlog 10})}}) :backlog 10})}
:level (or
(:log-level config)
(if (env :dev) :debug)
:info)})
(cronj/start! session-manager/cleanup-job) (cronj/start! session-manager/cleanup-job)
(if (env :dev) (parser/cache-off!)) (if (env :dev) (parser/cache-off!))
;;start the expired session cleanup job ;;start the expired session cleanup job
(timbre/info "\n-=[ smeagol started successfully" (timbre/info "\n-=[ smeagol started successfully"
(when (env :dev) "using the development profile") "]=-") (when (env :dev) "using the development profile") "]=-")
(catch Exception any (catch Exception any
(timbre/error "Failure during startup" any) (timbre/error any "Failure during startup")
(destroy)))) (destroy))))
;; timeout sessions after 30 minutes ;; timeout sessions after 30 minutes

View file

@ -98,7 +98,7 @@
:details details :details details
:users (auth/list-users)}))) :users (auth/list-users)})))
(catch Exception any (catch Exception any
(timbre/error (.getMessage any)) (timbre/error any)
(layout/render "edit-user.html" (layout/render "edit-user.html"
(merge (util/standard-params request) (merge (util/standard-params request)
{:title (str (:edit-title-prefix (util/get-messages request)) " " (:target params)) {:title (str (:edit-title-prefix (util/get-messages request)) " " (:target params))

View file

@ -45,13 +45,6 @@
;;;; ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn get-git-repo
"Get the git repository for my content, creating it if necessary"
[]
(hist/load-or-init-repo util/content-dir))
(defn process-source (defn process-source
"Process `source-text` and save it to the specified `file-path`, committing it "Process `source-text` and save it to the specified `file-path`, committing it
to Git and finally redirecting to wiki-page." to Git and finally redirecting to wiki-page."
@ -61,7 +54,7 @@
file-name (str page suffix) file-name (str page suffix)
file-path (cjio/file util/content-dir file-name) file-path (cjio/file util/content-dir file-name)
exists? (.exists (cjio/as-file file-path)) exists? (.exists (cjio/as-file file-path))
git-repo (get-git-repo) git-repo (hist/load-or-init-repo util/content-dir)
user (session/get :user) user (session/get :user)
email (auth/get-email user) email (auth/get-email user)
summary (format "%s: %s" user (or (:summary params) "no summary"))] summary (format "%s: %s" user (or (:summary params) "no summary"))]
@ -157,7 +150,7 @@
[request] [request]
(let [params (keywordize-keys (:params request)) (let [params (keywordize-keys (:params request))
data-path (str (io/resource-path) "/content/uploads/") data-path (str (io/resource-path) "/content/uploads/")
git-repo (get-git-repo) git-repo (hist/load-or-init-repo util/content-dir)
upload (:upload params) upload (:upload params)
uploaded (if upload (ul/store-upload params data-path)) uploaded (if upload (ul/store-upload params data-path))
user (session/get :user) user (session/get :user)

View file

@ -1,4 +1,5 @@
(ns ^{:doc "Functions related to sanity checks and error reporting in conditions where the environment may not be sane." (ns ^{:doc "Functions related to sanity checks and error reporting in conditions
where the environment may not be sane."
:author "Simon Brooke"} :author "Simon Brooke"}
smeagol.sanity smeagol.sanity
(:import (java.util Locale)) (:import (java.util Locale))
@ -252,13 +253,28 @@
(as-hiccup [this dictionary] "") (as-hiccup [this dictionary] "")
clojure.lang.Keyword clojure.lang.Keyword
(as-hiccup [this dictionary] (str (or (this dictionary)(string/replace (name this) "-" " ")) " ")) (as-hiccup [this dictionary]
(str
(or
(this dictionary)
(string/replace (name this) "-" " "))
" "))
clojure.lang.PersistentList clojure.lang.PersistentList
(as-hiccup [this dictionary] (apply vector (cons :div (map #(as-hiccup % dictionary) this)))) (as-hiccup [this dictionary]
(apply
vector
(cons
:div
(map #(as-hiccup % dictionary) this))))
clojure.lang.PersistentVector clojure.lang.PersistentVector
(as-hiccup [this dictionary] (apply vector (cons :div (map #(as-hiccup % dictionary) this)))) (as-hiccup [this dictionary]
(apply
vector
(cons
:div
(map #(as-hiccup % dictionary) this))))
clojure.lang.PersistentArrayMap clojure.lang.PersistentArrayMap
(as-hiccup [this dictionary] (as-hiccup [this dictionary]
@ -296,66 +312,94 @@
vector vector
(cons (cons
:div :div
(cons (cons
{:class "sanity-exception"} {:class "sanity-exception"}
(map (map
(fn [x] (fn [x]
[:div [:div
{:class "sanity-cause"} {:class "sanity-cause"}
(.getMessage x) [:h2 (.getMessage x)]
[:div {:class "sanity-stacktrace"} [:div {:class "sanity-stacktrace"}
(apply (apply
vector vector
(cons (cons
:ol :ol
(map (map
as-hiccup as-hiccup
(.getStackTrace x) (.getStackTrace x)
dictionary)))]]) dictionary)))]])
(get-causes this)))))) (get-causes this))))))
java.lang.Object java.lang.Object
(as-hiccup [this dictionary] (str this " "))) (as-hiccup [this dictionary] (str this " ")))
(defn sanity-check-report (defn get-locale-messages
[problems] "Get messages for the server-side locale."
[]
(let [locale (Locale/getDefault) (let [locale (Locale/getDefault)
locale-specifier (str (.getLanguage locale) "-" (.getCountry locale)) locale-specifier (str (.getLanguage locale) "-" (.getCountry locale))]
messages (try (try
(i18n/get-messages locale-specifier "i18n" "en-GB") (i18n/get-messages locale-specifier "i18n" "en-GB")
(catch Exception any {}))] (catch Exception any {}))))
;; Prepackaged hiccup sub-units
(defn as-hiccup-head
[messages]
[:head
[:title (as-hiccup :smeagol-not-initialised messages)]
[:link {:href "/content/stylesheet.css" :rel "stylesheet"}]])
(defn as-hiccup-header
[messages]
[:header
[:div {:id "nav"} "&nbsp;"]
[:h1 (as-hiccup :smeagol-not-initialised messages)]
[:p "&nbsp;"]])
(defn as-hiccup-see-doc
[messages]
[:p (as-hiccup :see-documentation messages)
[:a
{:href
"https://github.com/journeyman-cc/smeagol/wiki/Deploying-Smeagol"}
(as-hiccup :here messages)] "."])
(defn as-hiccup-footer
[messages]
[:footer
[:div {:id "credits"}
[:div
[:img {:height "16" :width "16" :alt "one wiki to rule them all" :src "img/smeagol.png"}]
" One Wiki to rule them all || Smeagol wiki engine || "
[:img
{:height "16" :width "16"
:alt "The Web Engineering Factory &amp; Toolworks"
:src "http://www.weft.scot/images/weft.logo.64.png"}]
" Developed by "
[:a {:href "http://www.weft.scot/"}"WEFT"]]]])
(defn sanity-check-report
"Convert this `problem` report into a nicely formatted HTML page"
[problems]
(let [messages (get-locale-messages)]
(html (html
[:html [:html
[:head (as-hiccup-head messages)
[:title (as-hiccup :smeagol-not-initialised messages)]
[:link {:href "/content/stylesheet.css" :rel "stylesheet"}]]
[:body [:body
[:header (as-hiccup-header messages)
[:div {:id "nav"} "&nbsp;"] [:div {:id "error"}
[:h1 (as-hiccup :smeagol-not-initialised messages)] [:p {:class "error"}
[:p "&nbsp;"]] (rest (as-hiccup [(count (keys problems)) :problems-found] messages))]]
[:div {:id "error" :class "error"}
[:div {:class "error"}
(as-hiccup [(count (keys problems)) :problems-found] messages)]]
[:div {:id "main-container" :class "sanity-check-report"} [:div {:id "main-container" :class "sanity-check-report"}
[:p (as-hiccup :smeagol-misconfiguration messages)] [:p (as-hiccup :smeagol-misconfiguration messages)]
(as-hiccup problems messages) (as-hiccup problems messages)
[:p (as-hiccup :see-documentation messages) (as-hiccup-see-doc messages)]
[:a (as-hiccup-footer messages)]])))
{:href
"https://github.com/journeyman-cc/smeagol/blob/master/resources/public/content/Deploying%20Smeagol.md"}
(as-hiccup :here messages)]]]
[:footer
[:div {:id "credits"}
[:div
[:img {:height "16" :width "16" :alt "one wiki to rule them all" :src "img/smeagol.png"}]
" One Wiki to rule them all || Smeagol wiki engine || "
[:img
{:height "16" :width "16"
:alt "The Web Engineering Factory &amp; Toolworks"
:src "http://www.weft.scot/images/weft.logo.64.png"}]
" Developed by "
[:a {:href "http://www.weft.scot/"}"WEFT"]]]]]])))
(defn- raw-sanity-check-installation (defn- raw-sanity-check-installation
@ -369,7 +413,7 @@
(timbre/warn "Sanity check completed; " (count (keys result)) " problem(s) found") (timbre/warn "Sanity check completed; " (count (keys result)) " problem(s) found")
(sanity-check-report result)) (sanity-check-report result))
(do (do
(timbre/info "Sanity check completed; no problem(s) found") (timbre/info "Sanity check completed; no problems found")
nil)))) nil))))
@ -383,26 +427,26 @@
If no argument is passed, run the sanity check and if it fails return page contents; If no argument is passed, run the sanity check and if it fails return page contents;
if `error` is passed, just return page content describing the error." if `error` is passed, just return page content describing the error."
([error] ([error]
(html (let [messages (get-locale-messages)]
[:html (html
[:head [:html
[:title "Smeagol is not initialised correctly"] (as-hiccup-head messages)
[:link {:href "/content/stylesheet.css" :rel "stylesheet"}]] [:body
[:body (as-hiccup-header messages)
[:header [:div {:id "error"}
[:h1 "Smeagol is not initialised correctly"]] [:p {:class "error"} (.getMessage error)]]
[:div {:id "error"} [:div {:id "main-container" :class "sanity-check-report"}
[:p {:class "error"} (.getMessage error)]] [:p (as-hiccup :smeagol-misconfiguration messages)]
[:p "There was a problem launching Smeagol probably because of misconfiguration:"] (as-hiccup error messages)
(apply (as-hiccup-see-doc messages)]
vector (as-hiccup-footer messages)]])))
(cons :ol
(map #(vector :li (.getMessage %))
(get-causes error))))
[:p :see-documentation
[:a {:href "https://github.com/journeyman-cc/smeagol/blob/develop/resources/public/content/Deploying%20Smeagol.md"} "here"]]]]))
([] ([]
(try (try
(sanity-check-installation) (sanity-check-installation)
(catch Exception any (show-sanity-check-error any))))) (catch Exception any
(timbre/error any "Failure during sanity check")
(show-sanity-check-error any)))))
(show-sanity-check-error (Exception. "That's insane!"))

View file

@ -8,7 +8,8 @@
[scot.weft.i18n.core :as i18n] [scot.weft.i18n.core :as i18n]
[smeagol.authenticate :as auth] [smeagol.authenticate :as auth]
[smeagol.configuration :refer [config]] [smeagol.configuration :refer [config]]
[smeagol.formatting :refer [md->html]])) [smeagol.formatting :refer [md->html]]
[taoensso.timbre :as timbre]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; ;;;;
@ -36,7 +37,7 @@
(def content-dir (def content-dir
(or (or
(env :smeagol-content-dir) (:content-dir config)
(cjio/file (io/resource-path) "content"))) (cjio/file (io/resource-path) "content")))
@ -55,12 +56,19 @@
"Return the most acceptable messages collection we have given the "Return the most acceptable messages collection we have given the
`Accept-Language` header in this `request`." `Accept-Language` header in this `request`."
[request] [request]
(merge (let [specifier ((:headers request) "accept-language")
(i18n/get-messages messages (try
((:headers request) "accept-language") (i18n/get-messages specifier "i18n" "en-GB")
"i18n" (catch Exception any
"en-GB") (timbre/error
config)) any
(str
"Failed to parse accept-language header "
specifier))
{}))]
(merge
messages
config)))
(def get-messages (memoize raw-get-messages)) (def get-messages (memoize raw-get-messages))