+{%- endif %}
+
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 00000000..cdf8e0b2
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,115 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+import os
+from datetime import date
+import sphinx_rtd_theme
+
+from sphinx.application import Sphinx
+from sphinx.transforms.post_transforms import SphinxPostTransform
+
+from mache.version import __version__
+
+# -- Project information -----------------------------------------------------
+
+project = "Mache"
+copyright = f"{date.today().year}, Energy Exascale Earth System Model Project"
+author = "E3SM Development Team"
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+if 'DOCS_VERSION' in os.environ:
+ version = os.environ.get('DOCS_VERSION')
+ release = version
+else:
+ # The short X.Y.Z version.
+ version = __version__
+ # The full version, including alpha/beta/rc tags.
+ release = __version__
+
+master_doc = "index"
+language = "en"
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ "myst_parser",
+ "sphinx_rtd_theme",
+ "sphinx_multiversion",
+ "sphinx.ext.autodoc",
+ "sphinx.ext.autosummary",
+ "sphinx.ext.intersphinx",
+ "sphinx.ext.viewcode",
+ "sphinx.ext.mathjax",
+ "sphinx.ext.napoleon",
+]
+
+autosummary_generate = ['developers_guide/api.md']
+
+# Otherwise, the Return parameter list looks different from the Parameters list
+napoleon_use_rtype = False
+# Otherwise, the Attributes parameter list looks different from the Parameters
+# list
+napoleon_use_ivar = True
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ["_templates"]
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+
+intersphinx_mapping = {
+ 'geometric_features':
+ ('https://mpas-dev.github.io/geometric_features/stable', None),
+ 'matplotlib': ('https://matplotlib.org/stable', None),
+ 'mpas_tools': ('https://mpas-dev.github.io/MPAS-Tools/stable', None),
+ 'numpy': ('https://numpy.org/doc/stable', None),
+ 'python': ('https://docs.python.org', None),
+ 'scipy': ('https://docs.scipy.org/doc/scipy/reference', None),
+ "sphinx": ("https://www.sphinx-doc.org/en/master", None),
+ 'xarray': ('https://xarray.pydata.org/en/stable', None)
+}
+
+# -- MyST settings ---------------------------------------------------
+
+myst_enable_extensions = [
+ 'colon_fence',
+ 'deflist',
+ 'dollarmath'
+]
+myst_number_code_blocks = ["typescript"]
+myst_heading_anchors = 2
+myst_footnote_transition = True
+myst_dmath_double_inline = True
+myst_enable_checkboxes = True
+
+# -- HTML output -------------------------------------------------
+
+html_theme = 'sphinx_rtd_theme'
+html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+html_title = ""
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ["_static"]
+
+html_sidebars = {
+ "**": [
+ "versions.html",
+ ],
+}
+
+smv_tag_whitelist = r"^\d+\.\d+.\d+$" # Include tags like "tags/2.5.0"
+smv_branch_whitelist = "main"
+smv_remote_whitelist = r"^(origin|upstream)$" # Use branches from origin
+
diff --git a/docs/developers_guide/adding_new_machine.md b/docs/developers_guide/adding_new_machine.md
new file mode 100644
index 00000000..ff588950
--- /dev/null
+++ b/docs/developers_guide/adding_new_machine.md
@@ -0,0 +1,61 @@
+(dev-adding-new-machine)=
+
+# Adding a New Machine to Mache
+
+Adding an E3SM-known machine to mache requires adding a new config file, as
+well as updating the list of machines in `discover.py`.
+
+:::{note}
+Only machines that are included in mache's
+[machine config list](https://github.com/E3SM-Project/mache/blob/main/mache/cime_machine_config/config_machines.xml)
+can be added to mache. This list is a *copy* of the
+[E3SM cime machine config list](https://github.com/E3SM-Project/E3SM/blob/master/cime_config/machines/config_machines.xml)
+which we try to keep up-to-date. If you wish to add a machine that is not
+included in this list, you must contact the E3SM-Project developers to add your
+machine.
+:::
+
+(dev-new-config-file)=
+
+## Adding a new config file
+
+Adding a new config file is usually straightforward if you follow the format of
+an existing config file.
+
+(dev-discover-new-machine)=
+
+## Adding the new machine to `discover.py`
+
+You will need to amend the list of machine names in `discover.py` so that mache
+can identify the new machine via its hostname. This process is typically done
+using a regular expression, which is often possible whenever the machine's
+hostname follows a standardized format. For example, we can identify known
+machines from hostnames with the following regular expressions:
+
+```python
+'^chr-\d{4}' # Chrysalis compute nodes with hostnames chr-0000 to chr-9999
+'^compy' # Compy nodes with hostname compy
+'^n\d{4}' # Anvil nodes with hostnames n0000 to n9999
+```
+
+In some cases, the hostname assigned to a machine is too generic to
+differentiate it from other machines. In these cases, we must identify the
+machine by its environment variables. However, this is *not* the recommended
+procedure and should only be done as a last resort. For example, we identify
+`frontier` by its `LMOD_SYSTEM_NAME` environment variable:
+
+```python
+if machine is None and 'LMOD_SYSTEM_NAME' in os.environ:
+ hostname = os.environ['LMOD_SYSTEM_NAME']
+ if hostname == 'frontier':
+ # frontier's hostname is too generic to detect, so relying on
+ # LMOD_SYSTEM_NAME
+ machine = 'frontier'
+```
+
+:::{note}
+Identifying the machine by environment variables is **not recommended** unless
+absolutely necessary.
+:::
+
+
diff --git a/docs/developers_guide/api.md b/docs/developers_guide/api.md
new file mode 100644
index 00000000..596cbe64
--- /dev/null
+++ b/docs/developers_guide/api.md
@@ -0,0 +1,65 @@
+(dev-api)=
+
+# API reference
+
+This page provides an auto-generated summary of the mache API. For more
+details and examples, refer to the relevant sections in the main part of the
+documentation.
+
+## discover
+
+```{eval-rst}
+.. currentmodule:: mache.discover
+
+.. autosummary::
+ :toctree: generated/
+
+ discover_machine
+```
+
+## permissions
+
+```{eval-rst}
+.. currentmodule:: mache.permissions
+
+.. autosummary::
+ :toctree: generated/
+
+ update_permissions
+```
+
+## spack
+
+```{eval-rst}
+.. currentmodule:: mache.spack
+
+.. autosummary::
+ :toctree: generated/
+
+ make_spack_env
+ get_spack_script
+ get_modules_env_vars_and_mpi_compilers
+```
+
+## sync
+
+```{eval-rst}
+.. currentmodule:: mache.sync.diags
+
+.. autosummary::
+ :toctree: generated/
+
+ sync_diags
+```
+
+## MachineInfo
+
+```{eval-rst}
+.. currentmodule:: mache
+
+.. autosummary::
+ :toctree: generated/
+
+ MachineInfo
+ MachineInfo.get_account_defaults
+```
diff --git a/docs/developers_guide/building_docs.md b/docs/developers_guide/building_docs.md
new file mode 100644
index 00000000..ac207af1
--- /dev/null
+++ b/docs/developers_guide/building_docs.md
@@ -0,0 +1,16 @@
+(dev-building-docs)=
+
+# Building the Documentation
+
+As long as you have followed the procedure in {ref}`dev-installing-mache` for
+setting up your conda environment, you will already have the packages available
+that you need to build the documentation.
+
+Then, run the following script to build the docs:
+
+```bash
+cd docs
+make clean && make html
+```
+
+You can view the documentation by opening `_build/html/index.html`.
diff --git a/docs/developers_guide/quick_start.md b/docs/developers_guide/quick_start.md
new file mode 100644
index 00000000..b0f69c7d
--- /dev/null
+++ b/docs/developers_guide/quick_start.md
@@ -0,0 +1,314 @@
+# Quick Start
+
+`mache` (Machines for E3SM) is a package for providing configuration data
+related to E3SM supported machines.
+
+(dev-installing-mache)=
+
+## Installing mache
+
+You can install the latest release of `mache` from conda-forge:
+
+```bash
+conda config --add channels conda-forge
+conda config --set channel_priority strict
+conda install mache
+```
+
+If you need to install the latest development version, you can run the
+following in the root of the `mache` branch you are developing:
+
+```bash
+conda config --add channels conda-forge
+conda config --set channel_priority strict
+conda create -y -n mache_dev --file spec-file.txt
+conda activate mache_dev
+python -m pip install -e .
+```
+
+To install the development version of `mache` in an existing
+environment, you can run:
+
+```bash
+conda install --file spec-file.txt
+python -m pip install -e .
+```
+
+(dev-code-styling)=
+
+## Code styling and linting
+
+`mache` uses `pre-commit` to lint incoming code when you make a commit (as
+long as you have your environment set up correctly), and on GitHub whenever you
+make a pull request to the `mache` repository. Linting makes sure your code
+follows the formatting guidelines of PEP8, and cleans up additional things like
+whitespace at the end of files.
+
+The first time you set up the `mache_dev` environment, you will need to set up
+`pre-commit`. This is done by running:
+
+```bash
+pre-commit install
+```
+
+You only need to do this once when you create the `mache_dev` environment. If
+you create a new version of `mache_dev`, then you will need to run it again.
+
+When you run `git commit `, `pre-commit` will automatically lint your
+code before committing. Some formatting will be updated by `pre-commit`
+automatically, in which case it will terminate the commit and inform you of the
+change. Then you can run `git commit ` again to continue the linting
+process until your commit is successful. Some changes need to be made manually,
+such as inconsistent variable types. When this happens, you must update the
+file to `pre-commit`'s standards, and then attempt to re-commit the file.
+
+Internally, `pre-commit` uses [flake8](https://flake8.pycqa.org/en/latest/) to
+check PEP8 compliance, [isort](https://pycqa.github.io/isort/) to sort, check
+and format imports, [flynt](https://github.com/ikamensh/flynt) to change any
+format strings to f-strings, and [mypy](https://mypy-lang.org/) to check for
+consistent variable types. An example error might be:
+
+```bash
+example.py:77:1: E302 expected 2 blank lines, found 1
+```
+
+For this example, we would just add an additional blank line after line 77 and
+try the commit again to make sure we've resolved the issue.
+
+You may also find it useful to use an IDE with a PEP8 style checker built in,
+such as [PyCharm](https://www.jetbrains.com/pycharm/). See
+[this tutorial](https://www.jetbrains.com/help/pycharm/tutorial-code-quality-assistance-tips-and-tricks.html)
+for some tips on checking code style in PyCharm.
+
+(dev-example-usage)=
+
+## Example usage
+
+```python
+#!/usr/bin/env python
+from mache import MachineInfo, discover_machine
+
+machine_info = MachineInfo()
+print(machine_info)
+diags_base = machine_info.config.get('diagnostics', 'base_path')
+machine = discover_machine()
+```
+
+This loads machine info the current machine, prints it (see below) and
+retrieves a config option specific to that machine. The
+`discover_machine()` function can also be used to detect which machine
+you are on, as shown.
+
+As an example, the result of `print(machine_info)` is:
+
+```
+Machine: anvil
+ E3SM Supported Machine: True
+ Compilers: intel, gnu
+ MPI libraries: impi, openmpi, mvapich
+ OS: LINUX
+
+E3SM-Unified:
+ E3SM-Unified is not currently loaded
+ Base path: /lcrc/soft/climate/e3sm-unified
+
+Diagnostics:
+ Base path: /lcrc/group/e3sm/diagnostics
+
+Config options:
+ [e3sm_unified]
+ group = cels
+ compiler = intel
+ mpi = impi
+ base_path = /lcrc/soft/climate/e3sm-unified
+
+ [diagnostics]
+ base_path = /lcrc/group/e3sm/diagnostics
+
+ [web_portal]
+ base_path = /lcrc/group/e3sm/public_html
+ base_url = https://web.lcrc.anl.gov/public/e3sm/
+
+ [parallel]
+ system = slurm
+ parallel_executable = srun
+ cores_per_node = 36
+ account = condo
+ partitions = acme-small, acme-medium, acme-large
+ qos = regular, acme_high
+```
+
+If you are on the login node of one of the following E3SM supported
+machines, you don't need to provide the machine name. It can be
+discovered automatically when you create a `MachineInfo()` object or
+call `discover_machine()`. It will be recognized from the host name:
+
+- acme1
+- andes
+- anvil
+- chicoma-cpu
+- chrysalis
+- compy
+- cooley
+- frontier
+- pm-cpu
+
+If you are on a compute node or want info about a machine you're not
+currently on, give the `machine` name in all lowercase.
+
+(dev-attributes)=
+
+## Attributes
+
+The attributes currently available are:
+
+machine : str
+
+: The name of an E3SM supported machine
+
+config : configparser.ConfigParser
+
+: Config options for this machine
+
+e3sm_supported : bool
+
+: Whether this machine supports running E3SM itself, and therefore has a list
+of compilers, MPI libraries, and the modules needed to load them
+
+compilers : list
+
+: A list of compilers for this machine if `e3sm_supported == True`
+
+mpilibs : list
+
+: A list of MPI libraries for this machine if `e3sm_supported == True`
+
+os : str
+
+: The machine's operating system if `e3sm_supported == True`
+
+e3sm_unified_mpi : {'nompi', 'system', None}
+
+: Which MPI type is included in the E3SM-Unified environment (if one is loaded)
+
+e3sm_unified_base : str
+
+: The base path where E3SM-Unified and its activation scripts are installed if
+`e3sm_unified` is not `None`
+
+e3sm_unified_activation : str
+
+: The activation script used to activate E3SM-Unified if `e3sm_unified` is not
+`None`
+
+diagnostics_base : str
+
+: The base directory for diagnostics data
+
+web_portal_base : str
+
+: The base directory for the web portal
+
+web_portal_url : str
+
+: The base URL for the web portal
+
+username : str
+
+: The name of the current user, for use in web-portal directories. This value
+is also added to the `web_portal` and `username` option of the `config`
+attribute.
+
+## Syncing diagnostics
+
+`mache` can be used to synchronize diagnostics data (observational data
+sets, testing data, mapping files, region masks, etc.) either directly
+on LCRC or from LCRC to other supported machines.
+
+E3SM maintains a set of public diagnostics data (those that we have
+permission to share with any users of our software) on LCRC machines
+(Anvil and Chrysalis) in the directory:
+
+```
+/lcrc/group/e3sm/public_html/diagnostics/
+```
+
+A set of private diagnostics data (which we do not have permission to
+share outside the E3SM project) are stored at:
+
+```
+/lcrc/group/e3sm/diagnostics_private/
+```
+
+The `mache sync diags` command can be used to synchronize both sets of
+data with a shared diagnostics directory on each supported machine.
+
+Whenever possible, we log on to the E3SM machine and download the data
+from LCRC because this allows the synchronization tool to also update
+permissions once the data has been synchronized. This is the approach
+for all machines except for Los Alamos National Laboratory's Badger,
+which is behind a firewall that prevents this approach.
+
+### One-time setup
+
+To synchronize data from LCRC to other machines, you must first provide
+your SSH keys by going to the [Argonne
+Accounts](https://accounts.cels.anl.gov/) page, logging in, and adding
+the public ssh key for each machine. If you have not yet generated an
+SSH key for the destination machine, you will need to run:
+
+```bash
+ssh-keygen -t ed25519 -C "your_email@example.com"
+```
+
+This is the same procedure as for creating an SSH key for GitHub so if
+you have already done that process, you will not need a new SSH key for
+LCRC.
+
+### Setup on Andes
+
+Andes at OLCF requires special treatment. You need to create or edit the
+file `~/.ssh/config` with the following:
+
+```
+Host blues.lcrc.anl.gov
+ User
+ PreferredAuthentications publickey
+ IdentityFile ~/.ssh/id_ed25519
+```
+
+where, again `` is your username on LCRC.
+
+### Syncing from LCRC
+
+To synchronize diagnostics data from LCRC, simply run:
+
+```bash
+mache sync diags from anvil -u
+```
+
+where `` is your account name on LCRC.
+
+## Syncing on LCRC
+
+To synchronize diagnostics on an LCRC machine, run:
+
+```bash
+mache sync diags from anvil
+```
+
+Make sure the machine after `from` is the same as the machine you are
+on, `anvil` in this example.
+
+### Syncing to machines with firewalls
+
+To synchronize diagnostics data to a machine with a firewall by using a
+tunnel, first log on to an LCRC machine, then run:
+
+```bash
+mache sync diags to chicoma-cpu -u
+```
+
+where `` is your account name on the non-LCRC machine
+(`chicoma-cpu` in this example).
+
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 00000000..3812cced
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,16 @@
+(mache)=
+
+# Mache
+
+A package for providing configuration data relate to E3SM supported machines.
+
+```{toctree}
+:caption: Developer's guide
+:maxdepth: 2
+
+developers_guide/quick_start
+developers_guide/adding_new_machine
+developers_guide/building_docs
+developers_guide/api
+```
+
diff --git a/spec-file.txt b/spec-file.txt
index 24eb2e06..1b0ea23d 100644
--- a/spec-file.txt
+++ b/spec-file.txt
@@ -13,3 +13,10 @@ isort
flake8
mypy
pre-commit
+
+# Documentation
+sphinx
+sphinx_rtd_theme
+myst-parser
+sphinx-multiversion
+rst-to-myst