99 Commits

Author SHA1 Message Date
James Allen d2ba7da012 Start putting together .deb package builder 2014-05-15 17:45:24 +01:00
James Allen 6bb5bcb198 Neaten settings 2014-04-30 11:34:32 +01:00
James Allen a1ecd03f73 add in docstore to settings 2014-04-30 11:16:49 +01:00
James Allen f73b8e3937 Add in docstore 2014-04-29 12:05:24 +01:00
James Allen 64a673ec6d Merge pull request #117 from davidediger/master
Adding fairy configuration
2014-04-28 10:26:10 +01:00
David Ediger 6af53b3697 Adding fairy configuration 2014-04-25 16:17:17 -04:00
James Allen dcec6837b4 Require redis 2.6.12 or greater 2014-04-09 10:56:00 +01:00
James Allen 6e698b0c05 Run at the apt cookbook to keep apt up to date 2014-04-01 13:26:49 +01:00
James Allen f5f46af5a5 Add in TeXLive and imagemagick 2014-04-01 13:26:49 +01:00
James Allen 2a79a6066f Fix settings to use global directories 2014-04-01 13:26:49 +01:00
James Allen 5933dd60f5 Create Vagrant + Chef config for setting up ShareLaTeX 2014-04-01 13:26:49 +01:00
James Allen 55b9ac7d5a Import apt version 2.3.8 2014-04-01 13:26:49 +01:00
James Allen aa5eda9f1b Link to survey for about what people want 2014-03-31 19:48:13 +01:00
James Allen be1bb918e4 Create user_files by default 2014-03-28 12:54:30 +00:00
James Allen 5f0785c818 Update templates api location 2014-03-28 12:54:30 +00:00
James Allen 593f48419b Merge pull request #73 from cwoac/nodemailer
config changes for nodemailer
2014-03-19 22:31:18 +00:00
Oliver Matthews d9fdd93a2e config changes for nodemailer 2014-03-12 15:43:00 +00:00
James Allen c4d84487cd Add in Docker based install instructions 2014-03-06 10:12:53 +00:00
James Allen 6ab07443e3 add in track-changes settings 2014-03-05 13:41:00 +00:00
James Allen bbdc08249b Make track changes api a first class citizen 2014-03-05 13:39:21 +00:00
James Allen b846033c15 Fix typo 2014-03-05 12:52:04 +00:00
James Allen c9cf01a9bb Update README.md 2014-03-05 12:51:42 +00:00
James Allen f238f7726f Use file storage out of the box 2014-03-05 12:49:51 +00:00
Henry Oswald 36e31fda5d Merge pull request #69 from cwoac/refactor_config
refactor config settings.
2014-03-05 10:48:18 +00:00
Oliver Matthews a038b56d89 refactor config settings. 2014-03-04 15:35:32 +00:00
James Allen f85178bf1d Update README.md 2014-02-28 18:36:50 +00:00
James Allen b2057cb310 Add in Vagrant/Ansible backed installation method 2014-02-28 18:36:27 +00:00
James Allen 9f28ba29cb Add in track changes as a 'hidden' service 2014-02-26 16:24:11 +00:00
Henry Oswald ff04b104a5 Updated readme links
Changed links for mongo and redis to point at install instructions which can people can look at if they need to install these dependencies.
2014-02-26 13:05:22 +00:00
Henry Oswald 7c8f11d102 Adds filestoreBackend property to example settings 2014-02-26 09:12:55 +00:00
James Allen 7713de24dc Merge pull request #52 from cirosantilli/add-nvmrc
Add .nvmrc
2014-02-25 15:59:37 +00:00
James Allen f204c5d440 Merge pull request #57 from cirosantilli/rm-latexmk-install
Remove latexmk intall instructions from README.
2014-02-25 15:58:49 +00:00
Ciro Santillli c403003030 Remove latexmk intall instructions from README. 2014-02-25 16:38:41 +01:00
Ciro Santillli fbb33bda0f Add .nvmrc 2014-02-25 16:02:23 +01:00
James Allen 61bb54a2be Merge pull request #53 from cirosantilli/add-dummy-version
Add dummy version to package.json to fix install.
2014-02-25 14:43:41 +00:00
James Allen 94aae37025 Merge pull request #56 from cirosantilli/remove-dollars
Remove dollars from readme bash code.
2014-02-25 14:42:29 +00:00
Ciro Santillli 0b3224c05f Remove dollars from readme bash code. 2014-02-25 14:30:31 +01:00
Ciro Santillli 217e5f74ca Add dummy version to package.json to fix install. 2014-02-25 13:33:44 +01:00
James Allen aca7b3f961 Update Gruntfile.coffee 2014-02-24 19:08:08 +00:00
James Allen c3ff6c7770 Update CONTRIBUTING.md 2014-02-24 15:24:47 +00:00
James Allen 0fc386eebf Add in Travis CI badges 2014-02-24 14:44:35 +00:00
Henry Oswald 119ed217ad Revert "Update settings.development.coffee.example"
This reverts commit 20866189ab.
2014-02-24 13:00:30 +00:00
James Allen 072a45d853 Add in CLA info 2014-02-24 12:55:27 +00:00
Henry Oswald dec7347f08 Merge pull request #46 from kbasten/patch-1
Update settings.development.coffee.example
2014-02-24 12:10:46 +00:00
James Allen b1eaa97530 Update CONTRIBUTING.md 2014-02-23 21:38:02 +00:00
James Allen 6280a5a002 Update wiki link 2014-02-23 21:37:07 +00:00
kbasten 20866189ab Update settings.development.coffee.example 2014-02-23 21:22:53 +01:00
James Allen 90fdbf5681 Update README with config instructions 2014-02-23 11:55:06 +00:00
James Allen 31b41e156d Check that make is installed before installing modules that need compiling 2014-02-23 11:53:46 +00:00
James Allen bf528709d2 Use example config file so that config changes do not interfere with git 2014-02-23 11:45:20 +00:00
James Allen 89bff17891 Update email address to team@sharelatex.com 2014-02-23 10:52:43 +00:00
James Allen 6279c63dfb Update email address to team@sharelatex.com 2014-02-23 10:52:20 +00:00
James Allen 0d531d2641 Add in check for S3 credentials 2014-02-22 15:02:21 +00:00
James Allen 84dfc633bd Remove Github ssh keys message
This is now redundant since everything uses https.
2014-02-22 14:11:12 +00:00
James Allen 5840e8f88d Remove HN post from REAMDE.
Old news now :)
2014-02-22 14:10:28 +00:00
James Allen b461178ff0 Fix latexmk version checking 2014-02-22 14:08:49 +00:00
James Allen 69f79386f4 Add in check task to check that redis and latexmk are installed correctly 2014-02-22 12:08:03 +00:00
James Allen 179bc4df33 Update default features in settings 2014-02-22 10:50:45 +00:00
James Allen a577a17116 Merge pull request #38 from wokkaflokka/master
update README instructions to use 'https' scheme for installation instru...
2014-02-22 09:43:38 +00:00
Emery Coxe d0bfa69a23 update README instructions to use 'https' scheme for installation instructions rather than 'git' scheme 2014-02-21 20:13:26 -06:00
James Allen 5bc02b12fe Update README.md 2014-02-21 20:37:26 +00:00
James Allen 019a4f26f3 Update CONTRIBUTING.md 2014-02-21 20:36:33 +00:00
James Allen 3e9c3bfcce Merge pull request #32 from swapagarwal/master
Fix Minor Typo in README.md
2014-02-21 19:31:48 +00:00
Swapnil Agarwal 9b6377f4dc Fix Minor Typo in README.md 2014-02-22 00:59:52 +05:30
James Allen 78408be110 Update latexmk install instructions 2014-02-21 19:16:01 +00:00
James Allen 1d8e019e33 Update CONTRIBUTING.md 2014-02-21 17:53:58 +00:00
James Allen c2c092fb4d Update README.md with Redis version
Doesn't really solve #25, but should make it clearer
2014-02-21 14:27:06 +00:00
James Allen 1b51f9a6af Merge pull request #24 from goodbest/patch-1
change git clone manner from ssh to https
2014-02-21 14:19:29 +00:00
goodbest 00af21cc5e change git clone manner from ssh to https 2014-02-21 22:16:36 +08:00
James Allen 7c86c6e243 Merge pull request #23 from fayimora/patch-1
Fix minor typos in readme
2014-02-21 14:05:53 +00:00
Fayimora Femi-Balogun 9b53af0a26 Fix minor typos in readme 2014-02-21 14:00:12 +00:00
James Allen c3d6d6704c Update README.md 2014-02-21 13:32:37 +00:00
James Allen 0a707f9b05 Update README.md 2014-02-21 10:44:48 +00:00
James Allen bce30bcfeb Update dependencies in README to include latexmk 2014-02-21 10:20:33 +00:00
James Allen 62ffdc816a Update README.md 2014-02-21 10:18:33 +00:00
James Allen 87376120c9 Update README.md 2014-02-20 16:37:34 +00:00
James Allen ad6ed1cbff Create LICENSE 2014-02-20 16:36:39 +00:00
James Allen 09ea2f68de Update dependencies 2014-02-19 15:37:47 +00:00
James Allen 56ecb546f4 Update README with new settings details 2014-02-19 13:24:52 +00:00
James Allen af2476d46c Remove old method of installing local settings 2014-02-19 13:23:49 +00:00
James Allen 432f45ac59 Configure CLSI to use sqllite 2014-02-18 17:40:23 +00:00
James Allen c71cb09fdd Update README 2014-02-18 16:28:48 +00:00
Henry Oswald 65f3d2365e Updated readme to have authors name at the bottom
I like to follow/look into the people behind a project sometimes, just making it easier for them.
2014-02-17 20:01:21 +00:00
James Allen 62b187eb10 Update CONTRIBUTING.md 2014-02-15 18:31:12 +00:00
James Allen 34796ac040 Merge branch 'master' of github.com:sharelatex/sharelatex 2014-02-14 17:35:41 +00:00
James Allen a440b5435a Add in filestore 2014-02-14 17:30:43 +00:00
James Allen 6e150b1edc Update CONTRIBUTING.md 2014-02-13 13:21:36 +00:00
James Allen 3492e118fd Update README.md 2014-02-13 13:07:03 +00:00
James Allen d02c777e55 Add CLSI to known repos 2014-02-13 12:37:47 +00:00
James Allen 8ed522774c Fix up some Gruntfile errors 2014-02-12 12:15:47 +00:00
James Allen d301e54b45 Allow custom configs to be installed via git 2014-02-12 12:11:58 +00:00
James Allen 3576b096ea Update installation details 2014-02-12 11:24:13 +00:00
James Allen 7082842d00 Use git repos 2014-02-12 11:18:25 +00:00
James Allen 5911a7fb43 Import web and doc updater as git repos, not npm modules 2014-02-12 10:21:20 +00:00
James Allen 81024f869d Add commented settings file 2014-02-10 12:26:34 +00:00
James Allen 7ead61dbf2 Add in settings module 2014-02-08 21:53:07 +00:00
James Allen 098be1495e Update Gruntfile 2014-02-08 21:52:45 +00:00
James Allen 536f7b5b85 Add README and CONTRIBUTING 2014-02-08 19:40:56 +00:00
James Allen 6a1a965ffd Initial commit with web-sharelatex and document-updater-sharelatex 2014-02-08 14:44:47 +00:00
6633 changed files with 3924 additions and 918155 deletions
-25
View File
@@ -1,25 +0,0 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[Makefile]
indent_style = tab
[*.go]
indent_style = tab
[*.{pug,coffee}]
indent_style = tab
[*.{pug,patch}]
trim_trailing_whitespace = false
[Jenkinsfile]
insert_final_newline = false
max_line_length = off
-383
View File
@@ -1,383 +0,0 @@
name: Build and Deploy Verso (prod)
# Production deploy. Triggered only by pushes to the `prod` branch — keep `main`
# for day-to-day work and fast-forward `prod` when a build is stable.
#
# Differences from the test deploy (deploy-verso.yml):
# - Runs in the `verso` namespace (test runs in `test`).
# - Mongo / Redis / app data live on PersistentVolumeClaims and are applied
# idempotently: this workflow NEVER deletes them, so data survives deploys.
# - The replica set is initialised only once.
# - Builds/pushes a distinct image tag (verso:stable) so prod and test never
# clobber each other's image.
# - SMTP comes from the `verso-smtp` Secret (create it with kubectl); email is
# optional so the app still boots before the secret exists.
# - Public self-registration stays off (CE default): friends-only, admin
# creates accounts / sends invites.
#
# Out of band (do once): create the PVCs (server-ce/k8s/verso-prod-pvcs.yaml,
# with your storageClass), the `verso-smtp` Secret, and a verso.alocoq.fr
# Ingress (see server-ce/k8s/verso-prod-ingress.example.yaml) + DNS.
on:
push:
branches:
- prod
workflow_dispatch:
env:
SITE_URL: https://verso.alocoq.fr
jobs:
deploy:
runs-on: native
timeout-minutes: 240
steps:
- name: Build and push Verso prod image with BuildKit
run: |
kubectl -n ci delete job verso-buildkit-prod --ignore-not-found=true --wait=true
cat <<'EOF' | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: verso-buildkit-prod
namespace: ci
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
initContainers:
- name: prepare
image: alpine/git:latest
command: ["sh", "-c"]
args:
- |
set -eux
REG=registry.git.svc.cluster.local:5000
git clone --depth 1 --branch prod https://git.alocoq.fr/alois/verso.git /workspace/repo
# Build the base image only when Dockerfile-base changes
# (content-hash tag); otherwise reuse the cached base.
BTAG=$(sha256sum /workspace/repo/server-ce/Dockerfile-base | cut -c1-16)
printf '%s' "$BTAG" > /workspace/base_tag
if wget -qO- "http://$REG/v2/verso-base/tags/list" 2>/dev/null | grep -q "\"base-$BTAG\""; then
echo "Base image base-$BTAG already present - skipping base build"
else
touch /workspace/build-base
echo "Base image base-$BTAG not found - base will be built"
fi
volumeMounts:
- name: workspace
mountPath: /workspace
containers:
- name: buildkit
image: moby/buildkit:latest
securityContext:
privileged: true
command: ["sh", "-c"]
args:
- |
set -eux
REG=registry.git.svc.cluster.local:5000
mkdir -p /etc/buildkit
printf '[registry."%s"]\n http = true\n insecure = true\n' "$REG" > /etc/buildkit/buildkitd.toml
BTAG=$(cat /workspace/base_tag)
BASE_REF="$REG/verso-base:base-$BTAG"
if [ -f /workspace/build-base ]; then
buildctl-daemonless.sh build \
--frontend=dockerfile.v0 \
--local context=/workspace/repo \
--local dockerfile=/workspace/repo/server-ce \
--opt filename=Dockerfile-base \
--import-cache type=registry,ref=$REG/verso-cache:base \
--export-cache type=registry,ref=$REG/verso-cache:base,mode=max \
--output type=image,name=$BASE_REF,push=true,registry.insecure=true
else
echo "Reusing existing base image $BASE_REF"
fi
# App image → verso:stable (prod tag).
buildctl-daemonless.sh build \
--frontend=dockerfile.v0 \
--local context=/workspace/repo \
--local dockerfile=/workspace/repo/server-ce \
--opt filename=Dockerfile \
--opt build-arg:OVERLEAF_BASE_TAG=$BASE_REF \
--import-cache type=registry,ref=$REG/verso-cache:app \
--export-cache type=registry,ref=$REG/verso-cache:app,mode=max \
--output type=image,name=$REG/verso:stable,push=true,registry.insecure=true
volumeMounts:
- name: workspace
mountPath: /workspace
volumes:
- name: workspace
emptyDir: {}
EOF
- name: Wait for build
run: |
kubectl -n ci wait --for=condition=complete job/verso-buildkit-prod --timeout=14400s
- name: Show build logs
if: always()
run: |
kubectl -n ci logs job/verso-buildkit-prod -c prepare || true
kubectl -n ci logs job/verso-buildkit-prod -c buildkit || true
- name: Ensure data services (Mongo + Redis, never deleted)
run: |
# Mongo/Redis. Applied idempotently — this step must never delete
# these, so project data survives every deploy. The namespace and the
# PVCs (server-ce/k8s/verso-prod-pvcs.yaml) are provisioned out of
# band, so the runner only needs namespaced rights in `verso` (like
# `test`). This step assumes the namespace and the
# mongo-data / redis-data / verso-data PVCs already exist.
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongo
namespace: verso
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongo
image: mongo:8
command: ["mongod", "--replSet", "rs0", "--bind_ip_all"]
ports:
- containerPort: 27017
volumeMounts:
- name: mongo-data
mountPath: /data/db
volumes:
- name: mongo-data
persistentVolumeClaim:
claimName: mongo-data
---
apiVersion: v1
kind: Service
metadata:
name: mongo
namespace: verso
spec:
selector:
app: mongo
ports:
- name: mongo
port: 27017
targetPort: 27017
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: verso
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7
# AOF persistence so a restart doesn't drop in-flight edits
# before they're flushed to Mongo.
command: ["redis-server", "--appendonly", "yes"]
ports:
- containerPort: 6379
volumeMounts:
- name: redis-data
mountPath: /data
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: redis-data
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: verso
spec:
selector:
app: redis
ports:
- name: redis
port: 6379
targetPort: 6379
EOF
kubectl -n verso rollout status deployment/mongo --timeout=300s
kubectl -n verso rollout status deployment/redis --timeout=300s
- name: Initialise Mongo replica set (only if not already initialised)
run: |
kubectl -n verso exec deploy/mongo -- mongosh --quiet --eval '
try {
rs.status()
print("replica set already initialised")
} catch (e) {
if (e.codeName === "NotYetInitialized" || /no replset config/i.test(e.message)) {
rs.initiate({ _id: "rs0", members: [{ _id: 0, host: "mongo:27017" }] })
print("replica set initiated")
} else {
throw e
}
}
'
kubectl -n verso exec deploy/mongo -- mongosh --quiet --eval '
while (rs.status().myState !== 1) { sleep(1000) }
print("Mongo replica set is PRIMARY")
'
- name: Ensure Verso deployment + service
run: |
# Stamp the instance name with this build number, e.g. "Verso V0.12 Alpha".
NAV_TITLE="Verso V0.${GITHUB_RUN_NUMBER:-${GITEA_RUN_NUMBER:-0}} Alpha"
cat <<'EOF' | sed "s|__NAV_TITLE__|${NAV_TITLE}|g" | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: verso
namespace: verso
spec:
replicas: 1
# RWO data volume → can't run two pods at once; recreate on update.
strategy:
type: Recreate
selector:
matchLabels:
app: verso
template:
metadata:
labels:
app: verso
spec:
securityContext:
# App runs as www-data (uid/gid 33); make the data volume
# group-writable by it.
fsGroup: 33
initContainers:
- name: init-data-perms
image: busybox:latest
command: ["sh", "-c"]
args:
- |
set -eux
mkdir -p /data/template_files /data/user_files \
/data/compiles /data/cache /data/output /data/published
chown -R 33:33 /data
volumeMounts:
- name: verso-data
mountPath: /data
containers:
- name: verso
image: registry.alocoq.fr/verso:stable
# :stable is a fixed tag, so force a pull on every rollout to
# pick up the freshly built image.
imagePullPolicy: Always
ports:
- containerPort: 80
env:
- name: OVERLEAF_MONGO_URL
value: mongodb://mongo:27017/sharelatex?replicaSet=rs0
- name: OVERLEAF_REDIS_HOST
value: redis
- name: REDIS_HOST
value: redis
- name: OVERLEAF_APP_NAME
value: Verso
- name: OVERLEAF_NAV_TITLE
value: "__NAV_TITLE__"
- name: OVERLEAF_SITE_URL
value: https://verso.alocoq.fr
- name: OVERLEAF_SITE_LANGUAGE
value: fr
# Allow anonymous visitors so public published-presentation
# links and read-only share links work without login.
- name: OVERLEAF_ALLOW_PUBLIC_ACCESS
value: "true"
# NB: anonymous read-AND-write sharing is intentionally NOT
# enabled (compiles are unsandboxed → only trusted accounts
# may trigger them). Public self-registration is also off
# (CE default): admin creates accounts / sends invites.
- name: OVERLEAF_ENABLE_PROJECT_PYTHON_VENV
value: "true"
# (SMTP email vars are loaded below via envFrom.)
# SMTP for password-reset / invite emails. All
# OVERLEAF_EMAIL_* vars come from the optional 'verso-smtp'
# Secret (its keys must be named exactly like those env
# vars). Optional, so the app boots before the secret exists.
envFrom:
- secretRef:
name: verso-smtp
optional: true
volumeMounts:
- name: verso-data
mountPath: /var/lib/overleaf/data
volumes:
- name: verso-data
persistentVolumeClaim:
claimName: verso-data
---
apiVersion: v1
kind: Service
metadata:
name: verso
namespace: verso
spec:
selector:
app: verso
ports:
- name: http
port: 80
targetPort: 80
EOF
- name: Deploy Verso image
run: |
kubectl -n verso set image deployment/verso \
verso=registry.alocoq.fr/verso:stable
kubectl -n verso rollout restart deployment/verso
kubectl -n verso rollout status deployment/verso --timeout=600s
- name: Create initial admin (only if no users exist)
run: |
COUNT=$(kubectl -n verso exec deploy/mongo -- mongosh sharelatex --quiet --eval 'db.users.countDocuments()' | tr -d '[:space:]')
if [ "$COUNT" = "0" ]; then
echo "No users yet — creating the initial admin account"
kubectl -n verso exec deploy/verso -- bash -lc '
cd /overleaf/services/web
node modules/server-ce-scripts/scripts/create-user \
--admin \
--email=alois.coquillard@gmail.com
'
else
echo "Users already exist ($COUNT) — skipping admin creation"
fi
-339
View File
@@ -1,339 +0,0 @@
name: Build and Deploy Verso
on:
push:
branches:
- main
workflow_dispatch:
env:
SITE_URL: https://test.alocoq.fr
jobs:
deploy:
runs-on: native
timeout-minutes: 240
steps:
- name: Build and push Verso images with BuildKit
run: |
kubectl -n ci delete job verso-buildkit --ignore-not-found=true --wait=true
cat <<'EOF' | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: verso-buildkit
namespace: ci
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
initContainers:
- name: prepare
image: alpine/git:latest
command: ["sh", "-c"]
args:
- |
set -eux
REG=registry.git.svc.cluster.local:5000
git clone --depth 1 https://git.alocoq.fr/alois/verso.git /workspace/repo
# (#1) Build the base image only when it actually changes.
# The base layers' only repo input is Dockerfile-base, so
# we key on a content hash of that file: the base is tagged
# verso-base:base-<hash> and the app builds FROM that exact
# tag. If a base with this hash is already in the registry,
# the heavy base build (apt, TeX Live, Quarto) is skipped.
BTAG=$(sha256sum /workspace/repo/server-ce/Dockerfile-base | cut -c1-16)
printf '%s' "$BTAG" > /workspace/base_tag
if wget -qO- "http://$REG/v2/verso-base/tags/list" 2>/dev/null | grep -q "\"base-$BTAG\""; then
echo "Base image base-$BTAG already present - skipping base build"
else
touch /workspace/build-base
echo "Base image base-$BTAG not found - base will be built"
fi
volumeMounts:
- name: workspace
mountPath: /workspace
containers:
- name: buildkit
image: moby/buildkit:latest
securityContext:
privileged: true
command: ["sh", "-c"]
args:
- |
set -eux
# Push to the in-cluster registry (plain HTTP) to bypass
# the Traefik ingress, whose read timeout was killing the
# multi-GB TeX Live layer upload mid-stream. Mark the
# registry http+insecure so both push and the base pull
# for the app build treat it as plain HTTP. Written inside
# the container so no extra k8s resources are needed.
REG=registry.git.svc.cluster.local:5000
mkdir -p /etc/buildkit
printf '[registry."%s"]\n http = true\n insecure = true\n' "$REG" > /etc/buildkit/buildkitd.toml
BTAG=$(cat /workspace/base_tag)
BASE_REF="$REG/verso-base:base-$BTAG"
# (#1) Base build, only when prepare flagged it changed.
# (#2) Import/export a registry layer cache so that, when
# the base does change, unchanged layers (e.g. apt) are
# still reused instead of rebuilt from scratch.
if [ -f /workspace/build-base ]; then
buildctl-daemonless.sh build \
--frontend=dockerfile.v0 \
--local context=/workspace/repo \
--local dockerfile=/workspace/repo/server-ce \
--opt filename=Dockerfile-base \
--import-cache type=registry,ref=$REG/verso-cache:base \
--export-cache type=registry,ref=$REG/verso-cache:base,mode=max \
--output type=image,name=$BASE_REF,push=true,registry.insecure=true
else
echo "Reusing existing base image $BASE_REF"
fi
# App image, built FROM the content-pinned base tag.
# (#2) The registry cache lets yarn install be skipped when
# package.json is unchanged; the web build only re-runs
# when the frontend source actually changes.
buildctl-daemonless.sh build \
--frontend=dockerfile.v0 \
--local context=/workspace/repo \
--local dockerfile=/workspace/repo/server-ce \
--opt filename=Dockerfile \
--opt build-arg:OVERLEAF_BASE_TAG=$BASE_REF \
--import-cache type=registry,ref=$REG/verso-cache:app \
--export-cache type=registry,ref=$REG/verso-cache:app,mode=max \
--output type=image,name=$REG/verso:latest,push=true,registry.insecure=true
volumeMounts:
- name: workspace
mountPath: /workspace
volumes:
- name: workspace
emptyDir: {}
EOF
- name: Wait for build
run: |
kubectl -n ci wait --for=condition=complete job/verso-buildkit --timeout=14400s
- name: Show build logs
if: always()
run: |
kubectl -n ci logs job/verso-buildkit -c prepare || true
kubectl -n ci logs job/verso-buildkit -c buildkit || true
- name: Recreate test dependencies
run: |
kubectl -n test delete deployment mongo redis --ignore-not-found=true --wait=true
kubectl -n test delete service mongo redis --ignore-not-found=true --wait=true
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongo
namespace: test
spec:
replicas: 1
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongo
image: mongo:8
command: ["mongod", "--replSet", "rs0", "--bind_ip_all"]
ports:
- containerPort: 27017
volumeMounts:
- name: mongo-data
mountPath: /data/db
volumes:
- name: mongo-data
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: mongo
namespace: test
spec:
selector:
app: mongo
ports:
- name: mongo
port: 27017
targetPort: 27017
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: test
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7
ports:
- containerPort: 6379
volumeMounts:
- name: redis-data
mountPath: /data
volumes:
- name: redis-data
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: test
spec:
selector:
app: redis
ports:
- name: redis
port: 6379
targetPort: 6379
EOF
kubectl -n test rollout status deployment/mongo --timeout=180s
kubectl -n test rollout status deployment/redis --timeout=180s
sleep 5
kubectl -n test exec deploy/mongo -- mongosh --eval '
rs.initiate({
_id: "rs0",
members: [{ _id: 0, host: "mongo:27017" }]
})
'
kubectl -n test exec deploy/mongo -- mongosh --eval '
while (rs.status().myState !== 1) {
sleep(1000)
}
print("Mongo replica set is PRIMARY")
'
- name: Ensure Verso deployment exists
run: |
# Stamp the instance name with this build number, e.g. "Verso V0.83 Alpha".
NAV_TITLE="Verso V0.${GITHUB_RUN_NUMBER:-${GITEA_RUN_NUMBER:-0}} Alpha"
cat <<'EOF' | sed "s|__NAV_TITLE__|${NAV_TITLE}|g" | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: verso
namespace: test
spec:
replicas: 1
selector:
matchLabels:
app: verso
template:
metadata:
labels:
app: verso
spec:
containers:
- name: verso
# Pull via the public address: the cluster nodes' containerd
# is configured for registry.alocoq.fr, not the in-cluster
# service name. Both front the same registry storage, so the
# image pushed via the in-cluster address resolves here too.
image: registry.alocoq.fr/verso:latest
ports:
- containerPort: 80
env:
- name: OVERLEAF_MONGO_URL
value: mongodb://mongo:27017/sharelatex?replicaSet=rs0
- name: OVERLEAF_REDIS_HOST
value: redis
- name: REDIS_HOST
value: redis
- name: OVERLEAF_APP_NAME
value: Verso
- name: OVERLEAF_NAV_TITLE
value: "__NAV_TITLE__"
- name: OVERLEAF_SITE_URL
value: https://test.alocoq.fr
# Default UI language for the instance.
- name: OVERLEAF_SITE_LANGUAGE
value: fr
# Allow anonymous visitors to reach the site so link
# sharing and public presentation links work without a
# login. Per-project and per-route access checks still
# apply; private presentation links still require login.
- name: OVERLEAF_ALLOW_PUBLIC_ACCESS
value: "true"
# Also let anonymous visitors use read-AND-write share
# links (edit without an account). Read-only links only
# need OVERLEAF_ALLOW_PUBLIC_ACCESS above.
- name: OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING
value: "true"
# Let Quarto Python cells use a project's requirements.txt:
# the compiler installs it into a cached venv. Gated to the
# project owner + invited collaborators (never anonymous /
# link-sharing users).
- name: OVERLEAF_ENABLE_PROJECT_PYTHON_VENV
value: "true"
---
apiVersion: v1
kind: Service
metadata:
name: verso
namespace: test
spec:
selector:
app: verso
ports:
- name: http
port: 80
targetPort: 80
EOF
- name: Deploy Verso image
run: |
kubectl -n test set image deployment/verso \
verso=registry.alocoq.fr/verso:latest
kubectl -n test rollout restart deployment/verso
kubectl -n test rollout status deployment/verso --timeout=300s
- name: Create admin user
run: |
sleep 20
kubectl -n test exec deploy/verso -- bash -lc '
cd /overleaf/services/web
node modules/server-ce-scripts/scripts/create-user \
--admin \
--email=test@example.com || true
'
- name: Cleanup
if: always()
run: |
kubectl -n ci delete job verso-buildkit --ignore-not-found=true --wait=true
-56
View File
@@ -1,56 +0,0 @@
---
name: Bug report
about: Report a bug
title: ''
labels: type:bug
assignees: ''
---
<!--
Note: If you are using www.overleaf.com and have a problem,
or if you would like to request a new feature please contact
the support team at support@overleaf.com
This form should only be used to report bugs in the
Community Edition release of Overleaf.
-->
<!-- BUG REPORT TEMPLATE -->
## Steps to Reproduce
<!-- Describe the steps leading up to when / where you found the bug. -->
<!-- Screenshots may be helpful here. -->
1.
2.
3.
## Expected Behaviour
<!-- What should have happened when you completed the steps above? -->
## Observed Behaviour
<!-- What actually happened when you completed the steps above? -->
<!-- Screenshots may be helpful here. -->
## Context
<!-- How has this issue affected you? What were you trying to accomplish? -->
## Technical Info
<!-- Provide any technical details that may be applicable (or N/A if not applicable). -->
- URL:
- Browser Name and version:
- Operating System and version (desktop or mobile):
- Signed in as:
- Project and/or file:
## Analysis
<!--- Optionally, document investigation of / suggest a fix for the bug, e.g. 'comes from this line / commit' -->
-11
View File
@@ -1,11 +0,0 @@
## Description
<!-- Goal of the pull request -->
## Related issues / Pull Requests
<!-- Fixes #xyz, Contributes to #xyz, Related to #xyz-->
## Contributor Agreement
- [ ] I confirm I have signed the [Contributor License Agreement](https://github.com/overleaf/overleaf/blob/main/CONTRIBUTING.md#contributor-license-agreement)
+18 -5
View File
@@ -1,6 +1,19 @@
# docker image build
.dockerignore
config
config-local
node_modules
# user defined files
.env
docker-compose.override.yml
web
document-updater
clsi
filestore
track-changes
docstore
compiles
cache
user_files
template_files
db.sqlite
.vagrant
+1
View File
@@ -0,0 +1 @@
0.10.26
@@ -1,16 +0,0 @@
diff --git a/dist/index.mjs b/dist/index.mjs
index b781835b37a1262510bdd66d8d3b399a076e9d68..312c314186d85bb3bc2ab2620e99bca259ef7167 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -44,10 +44,9 @@ import { z as z2 } from "zod/v4";
// src/tool/types.ts
import { z } from "zod/v4";
-var LATEST_PROTOCOL_VERSION = "2025-11-25";
+var LATEST_PROTOCOL_VERSION = "2025-06-18";
var SUPPORTED_PROTOCOL_VERSIONS = [
LATEST_PROTOCOL_VERSION,
- "2025-06-18",
"2025-03-26",
"2024-11-05"
];
@@ -1,831 +0,0 @@
diff --git a/dist/index.cjs b/dist/index.cjs
index 39215ae..b44cb76 100644
--- a/dist/index.cjs
+++ b/dist/index.cjs
@@ -187,16 +187,23 @@ Helper function that returns a transaction spec which inserts a
completion's text in the main selection range, and any other
selection range that has the same text in front of it.
*/
-function insertCompletionText(state$1, text, from, to) {
+function insertCompletionText(state$1, text, from, to, extend) {
let { main } = state$1.selection, fromOff = from - main.from, toOff = to - main.from;
return Object.assign(Object.assign({}, state$1.changeByRange(range => {
if (range != main && from != to &&
state$1.sliceDoc(range.from + fromOff, range.from + toOff) != state$1.sliceDoc(from, to))
return { range };
- let lines = state$1.toText(text);
+ let change = {
+ from: range.from + fromOff,
+ to: to == main.from ? range.to : range.from + toOff,
+ insert: text instanceof state.Text ? text : state$1.toText(text),
+ };
+ if (extend) {
+ extend(state$1, change);
+ }
return {
- changes: { from: range.from + fromOff, to: to == main.from ? range.to : range.from + toOff, insert: lines },
- range: state.EditorSelection.cursor(range.from + fromOff + lines.length)
+ changes: change,
+ range: state.EditorSelection.cursor(change.from + change.insert.length)
};
})), { scrollIntoView: true, userEvent: "input.complete" });
}
@@ -389,7 +396,9 @@ const completionConfig = state.Facet.define({
filterStrict: false,
compareCompletions: (a, b) => a.label.localeCompare(b.label),
interactionDelay: 75,
- updateSyncTime: 100
+ updateSyncTime: 100,
+ // overleaf: default to at top which is default CM6 behaviour
+ unfilteredResultsAtEnd: false
}, {
defaultKeymap: (a, b) => a && b,
closeOnBlur: (a, b) => a && b,
@@ -744,6 +753,7 @@ function score(option) {
(option.type ? 1 : 0);
}
function sortOptions(active, state) {
+ var _a;
let options = [];
let sections = null;
let addOption = (option) => {
@@ -763,7 +773,8 @@ function sortOptions(active, state) {
let getMatch = a.result.getMatch;
if (a.result.filter === false) {
for (let option of a.result.options) {
- addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], 1e9 - options.length));
+ let defaultScore = conf.unfilteredResultsAtEnd ? -1e9 : 1e9;
+ addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], defaultScore - options.length));
}
}
else {
@@ -790,15 +801,42 @@ function sortOptions(active, state) {
}
}
let result = [], prev = null;
+ const priorityIndices = new Map();
let compare = conf.compareCompletions;
for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
+ // overleaf: Deduplicate results with dedup options
+ // The goal is to keep only the highest priority option, in the
+ // highest scoring position.
+ const key = (_a = opt.completion.deduplicate) === null || _a === void 0 ? void 0 : _a.key;
+ if (key) {
+ // Handle merging specifically for deduplicated items item
+ const currentOptionIndex = priorityIndices.get(key);
+ if (currentOptionIndex === undefined) {
+ priorityIndices.set(key, result.length);
+ result.push(opt);
+ prev = opt.completion;
+ }
+ else {
+ if (result[currentOptionIndex].completion.deduplicate.priority < opt.completion.deduplicate.priority) {
+ result[currentOptionIndex] = opt;
+ if (currentOptionIndex === result.length - 1) {
+ prev = opt.completion;
+ }
+ }
+ }
+ continue;
+ }
+ // overleaf: end
let cur = opt.completion;
- if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
- (prev.type != null && cur.type != null && prev.type != cur.type) ||
- prev.apply != cur.apply || prev.boost != cur.boost)
+ if (!prev || prev.label != cur.label)
+ result.push(opt);
+ // overleaf: we're already handling deduplication, so skip extra merges
+ else if (prev.deduplicate)
result.push(opt);
else if (score(opt.completion) > score(prev))
result[result.length - 1] = opt;
+ else if (opt.completion.info)
+ result[result.length - 1] = opt;
prev = opt.completion;
}
return result;
@@ -817,8 +855,9 @@ class CompletionDialog {
: new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled);
}
static build(active, state, id, prev, conf, didSetActive) {
- if (prev && !didSetActive && active.some(s => s.isPending))
- return prev.setDisabled();
+ // Overleaf: avoid setting the previous completion state to disabled while completion sources are pending
+ // if (prev && !didSetActive && active.some(s => s.isPending))
+ // return prev.setDisabled()
let options = sortOptions(active, state);
if (!options.length)
return prev && active.some(a => a.isPending) ? prev.setDisabled() : null;
@@ -1017,13 +1056,14 @@ const completionState = state.StateField.define({
view.EditorView.contentAttributes.from(f, state => state.attrs)
]
});
+const getCompletionTooltip = (state) => { var _a; return (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.tooltip; };
function applyCompletion(view, option) {
const apply = option.completion.apply || option.completion.label;
let result = view.state.field(completionState).active.find(a => a.source == option.source);
if (!(result instanceof ActiveResult))
return false;
if (typeof apply == "string")
- view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
+ view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to, option.completion.extend)), { annotations: pickedCompletion.of(option.completion) }));
else
apply(view, option.completion, result.from, result.to);
return true;
@@ -1559,20 +1599,42 @@ interpreted as indicating a placeholder.
function snippet(template) {
let snippet = Snippet.parse(template);
return (editor, completion, from, to) => {
- let { text, ranges } = snippet.instantiate(editor.state, from);
- let { main } = editor.state.selection;
- let spec = {
- changes: { from, to: to == main.from ? main.to : to, insert: state.Text.of(text) },
- scrollIntoView: true,
- annotations: completion ? [pickedCompletion.of(completion), state.Transaction.userEvent.of("input.complete")] : undefined
- };
+ let { main } = editor.state.selection, fromOff = from - main.from, toOff = to - main.from;
+ let ranges = [];
+ let totalOffset = 0;
+ let spec = Object.assign(Object.assign({}, editor.state.changeByRange(range => {
+ if (range != main && from != to &&
+ editor.state.sliceDoc(range.from + fromOff, range.from + toOff) != editor.state.sliceDoc(from, to))
+ return { range };
+ let { text, ranges: fieldRanges } = snippet.instantiate(editor.state, range.from + fromOff);
+ let change = {
+ from: range.from + fromOff,
+ to: range.from + toOff,
+ insert: state.Text.of(text)
+ };
+ let originalTo = change.to;
+ let offset = change.insert.length + fromOff;
+ if (completion.extend) {
+ completion.extend(editor.state, change);
+ offset += originalTo - change.to;
+ }
+ for (const fieldRange of fieldRanges) {
+ ranges.push(new FieldRange(fieldRange.field, fieldRange.from + totalOffset, fieldRange.to + totalOffset));
+ }
+ totalOffset += offset;
+ return {
+ changes: change,
+ range: state.EditorSelection.cursor(change.from + change.insert.length)
+ };
+ })), { scrollIntoView: true, annotations: completion ? [pickedCompletion.of(completion), state.Transaction.userEvent.of("input.complete")] : undefined, effects: [] });
if (ranges.length)
spec.selection = fieldSelection(ranges, 0);
if (ranges.some(r => r.field > 0)) {
let active = new ActiveSnippet(ranges, 0);
- let effects = spec.effects = [setActive.of(active)];
- if (editor.state.field(snippetState, false) === undefined)
- effects.push(state.StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
+ spec.effects.push(setActive.of(active));
+ if (editor.state.field(snippetState, false) === undefined) {
+ spec.effects.push(state.StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
+ }
}
editor.dispatch(editor.state.update(spec));
};
@@ -1746,7 +1808,8 @@ const completeAnyWord = context => {
const defaults = {
brackets: ["(", "[", "{", "'", '"'],
before: ")]}:;>",
- stringPrefixes: []
+ stringPrefixes: [],
+ buildInsert: (state, range, open, close) => open + close,
};
const closeBracketEffect = state.StateEffect.define({
map(value, mapping) {
@@ -1854,8 +1917,8 @@ function insertBracket(state$1, bracket) {
for (let tok of tokens) {
let closed = closing(state.codePointAt(tok, 0));
if (bracket == tok)
- return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
- : handleOpen(state$1, tok, closed, conf.before || defaults.before);
+ return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok) > -1, tokens.indexOf(tok + tok + tok) > -1, conf)
+ : handleOpen(state$1, tok, closed, conf.before || defaults.before, conf);
if (bracket == closed && closedBracketAt(state$1, state$1.selection.main.from))
return handleClose(state$1, tok, closed);
}
@@ -1877,17 +1940,21 @@ function prevChar(doc, pos) {
let prev = doc.sliceString(pos - 2, pos);
return state.codePointSize(state.codePointAt(prev, 0)) == prev.length ? prev : prev.slice(1);
}
-function handleOpen(state$1, open, close, closeBefore) {
+function handleOpen(state$1, open, close, closeBefore, config) {
+ let buildInsert = config.buildInsert || defaults.buildInsert;
let dont = null, changes = state$1.changeByRange(range => {
+ var _a;
if (!range.empty)
return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
effects: closeBracketEffect.of(range.to + open.length),
range: state.EditorSelection.range(range.anchor + open.length, range.head + open.length) };
let next = nextChar(state$1.doc, range.head);
- if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
- return { changes: { insert: open + close, from: range.head },
- effects: closeBracketEffect.of(range.head + open.length),
+ if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1) {
+ const insert = (_a = buildInsert(state$1, range, open, close)) !== null && _a !== void 0 ? _a : open + close;
+ return { changes: { insert, from: range.head },
+ effects: insert === open ? [] : closeBracketEffect.of(range.head + open.length),
range: state.EditorSelection.cursor(range.head + open.length) };
+ }
return { range: dont = range };
});
return dont ? null : state$1.update(changes, {
@@ -1909,18 +1976,36 @@ function handleClose(state$1, _open, close) {
}
// Handles cases where the open and close token are the same, and
// possibly triple quotes (as in `"""abc"""`-style quoting).
-function handleSame(state$1, token, allowTriple, config) {
+function handleSame(state$1, token, allowDouble, allowTriple, config) {
let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
+ let buildInsert = config.buildInsert || defaults.buildInsert;
let dont = null, changes = state$1.changeByRange(range => {
+ var _a, _b, _c;
if (!range.empty)
return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
effects: closeBracketEffect.of(range.to + token.length),
range: state.EditorSelection.range(range.anchor + token.length, range.head + token.length) };
let pos = range.head, next = nextChar(state$1.doc, pos), start;
- if (next == token) {
+ if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
+ (start = canStartStringAt(state$1, pos - 2 * token.length, stringPrefixes)) > -1 &&
+ nodeStart(state$1, start)) {
+ return { changes: { insert: token + token + token + token, from: pos },
+ effects: closeBracketEffect.of(pos + token.length),
+ range: state.EditorSelection.cursor(pos + token.length) };
+ }
+ else if (allowDouble && state$1.sliceDoc(pos - token.length, pos) == token &&
+ (start = canStartStringAt(state$1, pos - token.length, stringPrefixes)) > -1 &&
+ nodeStart(state$1, start)) {
+ let insert = (_a = buildInsert(state$1, range, token, token)) !== null && _a !== void 0 ? _a : token + token;
+ return { changes: { insert, from: pos },
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
+ range: state.EditorSelection.cursor(pos + token.length) };
+ }
+ else if (next == token) {
if (nodeStart(state$1, pos)) {
- return { changes: { insert: token + token, from: pos },
- effects: closeBracketEffect.of(pos + token.length),
+ let insert = (_b = buildInsert(state$1, range, token, token)) !== null && _b !== void 0 ? _b : token + token;
+ return { changes: { insert, from: pos },
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
range: state.EditorSelection.cursor(pos + token.length) };
}
else if (closedBracketAt(state$1, pos)) {
@@ -1930,18 +2015,13 @@ function handleSame(state$1, token, allowTriple, config) {
range: state.EditorSelection.cursor(pos + content.length) };
}
}
- else if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
- (start = canStartStringAt(state$1, pos - 2 * token.length, stringPrefixes)) > -1 &&
- nodeStart(state$1, start)) {
- return { changes: { insert: token + token + token + token, from: pos },
- effects: closeBracketEffect.of(pos + token.length),
- range: state.EditorSelection.cursor(pos + token.length) };
- }
else if (state$1.charCategorizer(pos)(next) != state.CharCategory.Word) {
- if (canStartStringAt(state$1, pos, stringPrefixes) > -1 && !probablyInString(state$1, pos, token, stringPrefixes))
- return { changes: { insert: token + token, from: pos },
- effects: closeBracketEffect.of(pos + token.length),
+ if (canStartStringAt(state$1, pos, stringPrefixes) > -1 && !probablyInString(state$1, pos, token, stringPrefixes)) {
+ const insert = (_c = buildInsert(state$1, range, token, token)) !== null && _c !== void 0 ? _c : token + token;
+ return { changes: { insert, from: pos },
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
range: state.EditorSelection.cursor(pos + token.length) };
+ }
}
return { range: dont = range };
});
@@ -2086,6 +2166,7 @@ exports.completionKeymap = completionKeymap;
exports.completionStatus = completionStatus;
exports.currentCompletions = currentCompletions;
exports.deleteBracketPair = deleteBracketPair;
+exports.getCompletionTooltip = getCompletionTooltip;
exports.hasNextSnippetField = hasNextSnippetField;
exports.hasPrevSnippetField = hasPrevSnippetField;
exports.ifIn = ifIn;
@@ -2093,8 +2174,10 @@ exports.ifNotIn = ifNotIn;
exports.insertBracket = insertBracket;
exports.insertCompletionText = insertCompletionText;
exports.moveCompletionSelection = moveCompletionSelection;
+exports.nextChar = nextChar;
exports.nextSnippetField = nextSnippetField;
exports.pickedCompletion = pickedCompletion;
+exports.prevChar = prevChar;
exports.prevSnippetField = prevSnippetField;
exports.selectedCompletion = selectedCompletion;
exports.selectedCompletionIndex = selectedCompletionIndex;
diff --git a/dist/index.d.cts b/dist/index.d.cts
index b57b8f6..fce47ab 100644
--- a/dist/index.d.cts
+++ b/dist/index.d.cts
@@ -1,6 +1,6 @@
import * as _codemirror_state from '@codemirror/state';
-import { EditorState, ChangeDesc, TransactionSpec, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
-import { EditorView, Rect, KeyBinding, Command } from '@codemirror/view';
+import { EditorState, Text, ChangeDesc, TransactionSpec, StateCommand, Transaction, Facet, SelectionRange, Extension, StateEffect } from '@codemirror/state';
+import { EditorView, Rect, KeyBinding, Tooltip, Command } from '@codemirror/view';
import * as _lezer_common from '@lezer/common';
/**
@@ -73,6 +73,19 @@ interface Completion {
a `{name}` object.
*/
section?: string | CompletionSection;
+ /**
+ Can be used to alter the change created when the completion is applied
+ */
+ extend?: ExtendCompletion;
+ /**
+ If multiple sources return the same result, use this field to specifiy a
+ deduplication key as well as a priority. For each unique key, only the
+ completion with the highest priority will be shown.
+ */
+ deduplicate?: {
+ key: string;
+ priority: number;
+ };
}
/**
The type returned from
@@ -306,12 +319,17 @@ This annotation is added to transactions that are produced by
picking a completion.
*/
declare const pickedCompletion: _codemirror_state.AnnotationType<Completion>;
+type ExtendCompletion = (state: EditorState, change: {
+ from: number;
+ to: number;
+ insert: string | Text;
+}) => void;
/**
Helper function that returns a transaction spec which inserts a
completion's text in the main selection range, and any other
selection range that has the same text in front of it.
*/
-declare function insertCompletionText(state: EditorState, text: string, from: number, to: number): TransactionSpec;
+declare function insertCompletionText(state: EditorState, text: string | Text, from: number, to: number, extend?: ExtendCompletion): TransactionSpec;
interface CompletionConfig {
/**
@@ -441,6 +459,10 @@ interface CompletionConfig {
milliseconds.
*/
updateSyncTime?: number;
+ /**
+ overleaf: Move unfiltered results after the filtered ones
+ */
+ unfilteredResultsAtEnd?: boolean;
}
/**
@@ -514,6 +536,8 @@ applies the snippet.
*/
declare function snippetCompletion(template: string, completion: Completion): Completion;
+declare const getCompletionTooltip: (state: EditorState) => Tooltip | undefined | null;
+
/**
Returns a command that moves the completion selection forward or
backward by the given amount.
@@ -562,6 +586,11 @@ interface CloseBracketConfig {
these prefixes before the opening quote.
*/
stringPrefixes?: string[];
+ /**
+ An optional callback for overriding the content that's inserted
+ based on surrounding characters
+ */
+ buildInsert?: (state: EditorState, range: SelectionRange, open: string, close: string) => string;
}
/**
Extension to enable bracket-closing behavior. When a closeable
@@ -593,6 +622,8 @@ to programmatically insert brackets—the
take care of running this for user input.)
*/
declare function insertBracket(state: EditorState, bracket: string): Transaction | null;
+declare function nextChar(doc: Text, pos: number): string;
+declare function prevChar(doc: Text, pos: number): string;
/**
Returns an extension that enables autocompletion.
@@ -636,4 +667,5 @@ the currently selected completion.
*/
declare function setSelectedCompletion(index: number): StateEffect<unknown>;
-export { type CloseBracketConfig, type Completion, CompletionContext, type CompletionInfo, type CompletionResult, type CompletionSection, type CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
+export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, getCompletionTooltip, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextChar, nextSnippetField, pickedCompletion, prevChar, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
+export type { CloseBracketConfig, Completion, CompletionInfo, CompletionResult, CompletionSection, CompletionSource };
diff --git a/dist/index.d.ts b/dist/index.d.ts
index b57b8f6..fce47ab 100644
--- a/dist/index.d.ts
+++ b/dist/index.d.ts
@@ -1,6 +1,6 @@
import * as _codemirror_state from '@codemirror/state';
-import { EditorState, ChangeDesc, TransactionSpec, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
-import { EditorView, Rect, KeyBinding, Command } from '@codemirror/view';
+import { EditorState, Text, ChangeDesc, TransactionSpec, StateCommand, Transaction, Facet, SelectionRange, Extension, StateEffect } from '@codemirror/state';
+import { EditorView, Rect, KeyBinding, Tooltip, Command } from '@codemirror/view';
import * as _lezer_common from '@lezer/common';
/**
@@ -73,6 +73,19 @@ interface Completion {
a `{name}` object.
*/
section?: string | CompletionSection;
+ /**
+ Can be used to alter the change created when the completion is applied
+ */
+ extend?: ExtendCompletion;
+ /**
+ If multiple sources return the same result, use this field to specifiy a
+ deduplication key as well as a priority. For each unique key, only the
+ completion with the highest priority will be shown.
+ */
+ deduplicate?: {
+ key: string;
+ priority: number;
+ };
}
/**
The type returned from
@@ -306,12 +319,17 @@ This annotation is added to transactions that are produced by
picking a completion.
*/
declare const pickedCompletion: _codemirror_state.AnnotationType<Completion>;
+type ExtendCompletion = (state: EditorState, change: {
+ from: number;
+ to: number;
+ insert: string | Text;
+}) => void;
/**
Helper function that returns a transaction spec which inserts a
completion's text in the main selection range, and any other
selection range that has the same text in front of it.
*/
-declare function insertCompletionText(state: EditorState, text: string, from: number, to: number): TransactionSpec;
+declare function insertCompletionText(state: EditorState, text: string | Text, from: number, to: number, extend?: ExtendCompletion): TransactionSpec;
interface CompletionConfig {
/**
@@ -441,6 +459,10 @@ interface CompletionConfig {
milliseconds.
*/
updateSyncTime?: number;
+ /**
+ overleaf: Move unfiltered results after the filtered ones
+ */
+ unfilteredResultsAtEnd?: boolean;
}
/**
@@ -514,6 +536,8 @@ applies the snippet.
*/
declare function snippetCompletion(template: string, completion: Completion): Completion;
+declare const getCompletionTooltip: (state: EditorState) => Tooltip | undefined | null;
+
/**
Returns a command that moves the completion selection forward or
backward by the given amount.
@@ -562,6 +586,11 @@ interface CloseBracketConfig {
these prefixes before the opening quote.
*/
stringPrefixes?: string[];
+ /**
+ An optional callback for overriding the content that's inserted
+ based on surrounding characters
+ */
+ buildInsert?: (state: EditorState, range: SelectionRange, open: string, close: string) => string;
}
/**
Extension to enable bracket-closing behavior. When a closeable
@@ -593,6 +622,8 @@ to programmatically insert brackets—the
take care of running this for user input.)
*/
declare function insertBracket(state: EditorState, bracket: string): Transaction | null;
+declare function nextChar(doc: Text, pos: number): string;
+declare function prevChar(doc: Text, pos: number): string;
/**
Returns an extension that enables autocompletion.
@@ -636,4 +667,5 @@ the currently selected completion.
*/
declare function setSelectedCompletion(index: number): StateEffect<unknown>;
-export { type CloseBracketConfig, type Completion, CompletionContext, type CompletionInfo, type CompletionResult, type CompletionSection, type CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
+export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, getCompletionTooltip, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextChar, nextSnippetField, pickedCompletion, prevChar, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
+export type { CloseBracketConfig, Completion, CompletionInfo, CompletionResult, CompletionSection, CompletionSource };
diff --git a/dist/index.js b/dist/index.js
index 4729223..9361a53 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -1,4 +1,4 @@
-import { Annotation, StateEffect, EditorSelection, codePointAt, codePointSize, fromCodePoint, Facet, combineConfig, StateField, Prec, Text, Transaction, MapMode, RangeValue, RangeSet, CharCategory } from '@codemirror/state';
+import { Annotation, StateEffect, Text, EditorSelection, codePointAt, codePointSize, fromCodePoint, Facet, combineConfig, StateField, Prec, Transaction, MapMode, RangeValue, RangeSet, CharCategory } from '@codemirror/state';
import { Direction, logException, showTooltip, EditorView, ViewPlugin, getTooltip, Decoration, WidgetType, keymap } from '@codemirror/view';
import { syntaxTree, indentUnit } from '@codemirror/language';
@@ -185,16 +185,23 @@ Helper function that returns a transaction spec which inserts a
completion's text in the main selection range, and any other
selection range that has the same text in front of it.
*/
-function insertCompletionText(state, text, from, to) {
+function insertCompletionText(state, text, from, to, extend) {
let { main } = state.selection, fromOff = from - main.from, toOff = to - main.from;
return Object.assign(Object.assign({}, state.changeByRange(range => {
if (range != main && from != to &&
state.sliceDoc(range.from + fromOff, range.from + toOff) != state.sliceDoc(from, to))
return { range };
- let lines = state.toText(text);
+ let change = {
+ from: range.from + fromOff,
+ to: to == main.from ? range.to : range.from + toOff,
+ insert: text instanceof Text ? text : state.toText(text),
+ };
+ if (extend) {
+ extend(state, change);
+ }
return {
- changes: { from: range.from + fromOff, to: to == main.from ? range.to : range.from + toOff, insert: lines },
- range: EditorSelection.cursor(range.from + fromOff + lines.length)
+ changes: change,
+ range: EditorSelection.cursor(change.from + change.insert.length)
};
})), { scrollIntoView: true, userEvent: "input.complete" });
}
@@ -387,7 +394,9 @@ const completionConfig = /*@__PURE__*/Facet.define({
filterStrict: false,
compareCompletions: (a, b) => a.label.localeCompare(b.label),
interactionDelay: 75,
- updateSyncTime: 100
+ updateSyncTime: 100,
+ // overleaf: default to at top which is default CM6 behaviour
+ unfilteredResultsAtEnd: false
}, {
defaultKeymap: (a, b) => a && b,
closeOnBlur: (a, b) => a && b,
@@ -742,6 +751,7 @@ function score(option) {
(option.type ? 1 : 0);
}
function sortOptions(active, state) {
+ var _a;
let options = [];
let sections = null;
let addOption = (option) => {
@@ -761,7 +771,8 @@ function sortOptions(active, state) {
let getMatch = a.result.getMatch;
if (a.result.filter === false) {
for (let option of a.result.options) {
- addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], 1e9 - options.length));
+ let defaultScore = conf.unfilteredResultsAtEnd ? -1e9 : 1e9;
+ addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], defaultScore - options.length));
}
}
else {
@@ -788,15 +799,42 @@ function sortOptions(active, state) {
}
}
let result = [], prev = null;
+ const priorityIndices = new Map();
let compare = conf.compareCompletions;
for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
+ // overleaf: Deduplicate results with dedup options
+ // The goal is to keep only the highest priority option, in the
+ // highest scoring position.
+ const key = (_a = opt.completion.deduplicate) === null || _a === void 0 ? void 0 : _a.key;
+ if (key) {
+ // Handle merging specifically for deduplicated items item
+ const currentOptionIndex = priorityIndices.get(key);
+ if (currentOptionIndex === undefined) {
+ priorityIndices.set(key, result.length);
+ result.push(opt);
+ prev = opt.completion;
+ }
+ else {
+ if (result[currentOptionIndex].completion.deduplicate.priority < opt.completion.deduplicate.priority) {
+ result[currentOptionIndex] = opt;
+ if (currentOptionIndex === result.length - 1) {
+ prev = opt.completion;
+ }
+ }
+ }
+ continue;
+ }
+ // overleaf: end
let cur = opt.completion;
- if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
- (prev.type != null && cur.type != null && prev.type != cur.type) ||
- prev.apply != cur.apply || prev.boost != cur.boost)
+ if (!prev || prev.label != cur.label)
+ result.push(opt);
+ // overleaf: we're already handling deduplication, so skip extra merges
+ else if (prev.deduplicate)
result.push(opt);
else if (score(opt.completion) > score(prev))
result[result.length - 1] = opt;
+ else if (opt.completion.info)
+ result[result.length - 1] = opt;
prev = opt.completion;
}
return result;
@@ -815,8 +853,9 @@ class CompletionDialog {
: new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled);
}
static build(active, state, id, prev, conf, didSetActive) {
- if (prev && !didSetActive && active.some(s => s.isPending))
- return prev.setDisabled();
+ // Overleaf: avoid setting the previous completion state to disabled while completion sources are pending
+ // if (prev && !didSetActive && active.some(s => s.isPending))
+ // return prev.setDisabled()
let options = sortOptions(active, state);
if (!options.length)
return prev && active.some(a => a.isPending) ? prev.setDisabled() : null;
@@ -1015,13 +1054,14 @@ const completionState = /*@__PURE__*/StateField.define({
EditorView.contentAttributes.from(f, state => state.attrs)
]
});
+const getCompletionTooltip = (state) => { var _a; return (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.tooltip; };
function applyCompletion(view, option) {
const apply = option.completion.apply || option.completion.label;
let result = view.state.field(completionState).active.find(a => a.source == option.source);
if (!(result instanceof ActiveResult))
return false;
if (typeof apply == "string")
- view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
+ view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to, option.completion.extend)), { annotations: pickedCompletion.of(option.completion) }));
else
apply(view, option.completion, result.from, result.to);
return true;
@@ -1557,20 +1597,42 @@ interpreted as indicating a placeholder.
function snippet(template) {
let snippet = Snippet.parse(template);
return (editor, completion, from, to) => {
- let { text, ranges } = snippet.instantiate(editor.state, from);
- let { main } = editor.state.selection;
- let spec = {
- changes: { from, to: to == main.from ? main.to : to, insert: Text.of(text) },
- scrollIntoView: true,
- annotations: completion ? [pickedCompletion.of(completion), Transaction.userEvent.of("input.complete")] : undefined
- };
+ let { main } = editor.state.selection, fromOff = from - main.from, toOff = to - main.from;
+ let ranges = [];
+ let totalOffset = 0;
+ let spec = Object.assign(Object.assign({}, editor.state.changeByRange(range => {
+ if (range != main && from != to &&
+ editor.state.sliceDoc(range.from + fromOff, range.from + toOff) != editor.state.sliceDoc(from, to))
+ return { range };
+ let { text, ranges: fieldRanges } = snippet.instantiate(editor.state, range.from + fromOff);
+ let change = {
+ from: range.from + fromOff,
+ to: range.from + toOff,
+ insert: Text.of(text)
+ };
+ let originalTo = change.to;
+ let offset = change.insert.length + fromOff;
+ if (completion.extend) {
+ completion.extend(editor.state, change);
+ offset += originalTo - change.to;
+ }
+ for (const fieldRange of fieldRanges) {
+ ranges.push(new FieldRange(fieldRange.field, fieldRange.from + totalOffset, fieldRange.to + totalOffset));
+ }
+ totalOffset += offset;
+ return {
+ changes: change,
+ range: EditorSelection.cursor(change.from + change.insert.length)
+ };
+ })), { scrollIntoView: true, annotations: completion ? [pickedCompletion.of(completion), Transaction.userEvent.of("input.complete")] : undefined, effects: [] });
if (ranges.length)
spec.selection = fieldSelection(ranges, 0);
if (ranges.some(r => r.field > 0)) {
let active = new ActiveSnippet(ranges, 0);
- let effects = spec.effects = [setActive.of(active)];
- if (editor.state.field(snippetState, false) === undefined)
- effects.push(StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
+ spec.effects.push(setActive.of(active));
+ if (editor.state.field(snippetState, false) === undefined) {
+ spec.effects.push(StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
+ }
}
editor.dispatch(editor.state.update(spec));
};
@@ -1744,7 +1806,8 @@ const completeAnyWord = context => {
const defaults = {
brackets: ["(", "[", "{", "'", '"'],
before: ")]}:;>",
- stringPrefixes: []
+ stringPrefixes: [],
+ buildInsert: (state, range, open, close) => open + close,
};
const closeBracketEffect = /*@__PURE__*/StateEffect.define({
map(value, mapping) {
@@ -1852,8 +1915,8 @@ function insertBracket(state, bracket) {
for (let tok of tokens) {
let closed = closing(codePointAt(tok, 0));
if (bracket == tok)
- return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
- : handleOpen(state, tok, closed, conf.before || defaults.before);
+ return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok) > -1, tokens.indexOf(tok + tok + tok) > -1, conf)
+ : handleOpen(state, tok, closed, conf.before || defaults.before, conf);
if (bracket == closed && closedBracketAt(state, state.selection.main.from))
return handleClose(state, tok, closed);
}
@@ -1875,17 +1938,21 @@ function prevChar(doc, pos) {
let prev = doc.sliceString(pos - 2, pos);
return codePointSize(codePointAt(prev, 0)) == prev.length ? prev : prev.slice(1);
}
-function handleOpen(state, open, close, closeBefore) {
+function handleOpen(state, open, close, closeBefore, config) {
+ let buildInsert = config.buildInsert || defaults.buildInsert;
let dont = null, changes = state.changeByRange(range => {
+ var _a;
if (!range.empty)
return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
effects: closeBracketEffect.of(range.to + open.length),
range: EditorSelection.range(range.anchor + open.length, range.head + open.length) };
let next = nextChar(state.doc, range.head);
- if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
- return { changes: { insert: open + close, from: range.head },
- effects: closeBracketEffect.of(range.head + open.length),
+ if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1) {
+ const insert = (_a = buildInsert(state, range, open, close)) !== null && _a !== void 0 ? _a : open + close;
+ return { changes: { insert, from: range.head },
+ effects: insert === open ? [] : closeBracketEffect.of(range.head + open.length),
range: EditorSelection.cursor(range.head + open.length) };
+ }
return { range: dont = range };
});
return dont ? null : state.update(changes, {
@@ -1907,18 +1974,36 @@ function handleClose(state, _open, close) {
}
// Handles cases where the open and close token are the same, and
// possibly triple quotes (as in `"""abc"""`-style quoting).
-function handleSame(state, token, allowTriple, config) {
+function handleSame(state, token, allowDouble, allowTriple, config) {
let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
+ let buildInsert = config.buildInsert || defaults.buildInsert;
let dont = null, changes = state.changeByRange(range => {
+ var _a, _b, _c;
if (!range.empty)
return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
effects: closeBracketEffect.of(range.to + token.length),
range: EditorSelection.range(range.anchor + token.length, range.head + token.length) };
let pos = range.head, next = nextChar(state.doc, pos), start;
- if (next == token) {
+ if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
+ (start = canStartStringAt(state, pos - 2 * token.length, stringPrefixes)) > -1 &&
+ nodeStart(state, start)) {
+ return { changes: { insert: token + token + token + token, from: pos },
+ effects: closeBracketEffect.of(pos + token.length),
+ range: EditorSelection.cursor(pos + token.length) };
+ }
+ else if (allowDouble && state.sliceDoc(pos - token.length, pos) == token &&
+ (start = canStartStringAt(state, pos - token.length, stringPrefixes)) > -1 &&
+ nodeStart(state, start)) {
+ let insert = (_a = buildInsert(state, range, token, token)) !== null && _a !== void 0 ? _a : token + token;
+ return { changes: { insert, from: pos },
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
+ range: EditorSelection.cursor(pos + token.length) };
+ }
+ else if (next == token) {
if (nodeStart(state, pos)) {
- return { changes: { insert: token + token, from: pos },
- effects: closeBracketEffect.of(pos + token.length),
+ let insert = (_b = buildInsert(state, range, token, token)) !== null && _b !== void 0 ? _b : token + token;
+ return { changes: { insert, from: pos },
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
range: EditorSelection.cursor(pos + token.length) };
}
else if (closedBracketAt(state, pos)) {
@@ -1928,18 +2013,13 @@ function handleSame(state, token, allowTriple, config) {
range: EditorSelection.cursor(pos + content.length) };
}
}
- else if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
- (start = canStartStringAt(state, pos - 2 * token.length, stringPrefixes)) > -1 &&
- nodeStart(state, start)) {
- return { changes: { insert: token + token + token + token, from: pos },
- effects: closeBracketEffect.of(pos + token.length),
- range: EditorSelection.cursor(pos + token.length) };
- }
else if (state.charCategorizer(pos)(next) != CharCategory.Word) {
- if (canStartStringAt(state, pos, stringPrefixes) > -1 && !probablyInString(state, pos, token, stringPrefixes))
- return { changes: { insert: token + token, from: pos },
- effects: closeBracketEffect.of(pos + token.length),
+ if (canStartStringAt(state, pos, stringPrefixes) > -1 && !probablyInString(state, pos, token, stringPrefixes)) {
+ const insert = (_c = buildInsert(state, range, token, token)) !== null && _c !== void 0 ? _c : token + token;
+ return { changes: { insert, from: pos },
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
range: EditorSelection.cursor(pos + token.length) };
+ }
}
return { range: dont = range };
});
@@ -2071,4 +2151,4 @@ function setSelectedCompletion(index) {
return setSelectedEffect.of(index);
}
-export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
+export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, getCompletionTooltip, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextChar, nextSnippetField, pickedCompletion, prevChar, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
@@ -1,381 +0,0 @@
diff --git a/dist/index.cjs b/dist/index.cjs
index 46231ae..fb0f9aa 100644
--- a/dist/index.cjs
+++ b/dist/index.cjs
@@ -592,6 +592,7 @@ class SearchQuery {
this.valid = !!this.search && (!this.regexp || validRegExp(this.search));
this.unquoted = this.unquote(this.search);
this.wholeWord = !!config.wholeWord;
+ this.scope = config.scope;
}
/**
@internal
@@ -606,7 +607,7 @@ class SearchQuery {
eq(other) {
return this.search == other.search && this.replace == other.replace &&
this.caseSensitive == other.caseSensitive && this.regexp == other.regexp &&
- this.wholeWord == other.wholeWord;
+ this.wholeWord == other.wholeWord && this.scope == other.scope;
}
/**
@internal
@@ -631,7 +632,12 @@ class QueryType {
}
}
function stringCursor(spec, state, from, to) {
- return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined);
+ const test = spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined;
+ const testWithinScope = (from, to, buffer, bufferPos) => {
+ return (!test || test(from, to, buffer, bufferPos))
+ && (!spec.scope || spec.scope.some(range => from >= range.from && from <= range.to && to >= range.from && to <= range.to));
+ };
+ return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), testWithinScope);
}
function stringWordTest(doc, categorizer) {
return (from, to, buf, bufPos) => {
@@ -695,9 +701,14 @@ class StringQuery extends QueryType {
}
}
function regexpCursor(spec, state, from, to) {
+ const test = spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined;
+ const testWithinScope = (from, to, match) => {
+ return (!test || test(from, to, match))
+ && (!spec.scope || spec.scope.some(range => from >= range.from && from <= range.to && to >= range.from && to <= range.to));
+ };
return new RegExpCursor(state.doc, spec.search, {
ignoreCase: !spec.caseSensitive,
- test: spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined
+ test: testWithinScope,
}, from, to);
}
function charBefore(str, index) {
@@ -737,10 +748,18 @@ class RegExpQuery extends QueryType {
this.prevMatchInRange(state, curTo, state.doc.length);
}
getReplacement(result) {
- return this.spec.unquote(this.spec.replace).replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
- : i == "&" ? result.match[0]
- : i != "0" && +i < result.match.length ? result.match[i]
- : m);
+ return this.spec.unquote(this.spec.replace).replace(/\$([$&]|\d+)/g, (m, i) => {
+ if (i == "&")
+ return result.match[0];
+ if (i == "$")
+ return "$";
+ for (let l = i.length; l > 0; l--) {
+ let n = +i.slice(0, l);
+ if (n > 0 && n < result.match.length)
+ return result.match[n] + i.slice(l);
+ }
+ return m;
+ });
}
matchAll(state, limit) {
let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];
@@ -1227,7 +1246,9 @@ const searchExtensions = [
exports.RegExpCursor = RegExpCursor;
exports.SearchCursor = SearchCursor;
exports.SearchQuery = SearchQuery;
+exports.StringQuery = StringQuery;
exports.closeSearchPanel = closeSearchPanel;
+exports.createSearchPanel = createSearchPanel;
exports.findNext = findNext;
exports.findPrevious = findPrevious;
exports.getSearchQuery = getSearchQuery;
@@ -1242,4 +1263,6 @@ exports.searchPanelOpen = searchPanelOpen;
exports.selectMatches = selectMatches;
exports.selectNextOccurrence = selectNextOccurrence;
exports.selectSelectionMatches = selectSelectionMatches;
+exports.selectWord = selectWord;
exports.setSearchQuery = setSearchQuery;
+exports.togglePanel = togglePanel;
diff --git a/dist/index.d.cts b/dist/index.d.cts
index 08f5696..663d192 100644
--- a/dist/index.d.cts
+++ b/dist/index.d.cts
@@ -1,6 +1,6 @@
import * as _codemirror_state from '@codemirror/state';
import { Text, Extension, StateCommand, EditorState, SelectionRange, StateEffect } from '@codemirror/state';
-import { Command, KeyBinding, EditorView, Panel } from '@codemirror/view';
+import { Command, EditorView, Panel, KeyBinding } from '@codemirror/view';
/**
A search cursor provides an iterator over text matches in a
@@ -161,6 +161,7 @@ the `"cm-selectionMatch"` class for the highlighting. When
itself will be highlighted with `"cm-selectionMatch-main"`.
*/
declare function highlightSelectionMatches(options?: HighlightOptions): Extension;
+declare const selectWord: StateCommand;
/**
Select next occurrence of the current selection. Expand selection
to the surrounding word when the selection is empty.
@@ -264,6 +265,13 @@ declare class SearchQuery {
*/
readonly wholeWord: boolean;
/**
+ When set, only include search matches within these ranges
+ */
+ readonly scope?: Readonly<{
+ from: number;
+ to: number;
+ }[]>;
+ /**
Create a query object.
*/
constructor(config: {
@@ -293,6 +301,13 @@ declare class SearchQuery {
Enable whole-word matching.
*/
wholeWord?: boolean;
+ /**
+ The ranges to match within
+ */
+ scope?: Readonly<{
+ from: number;
+ to: number;
+ }[]>;
});
/**
Compare this query to another query.
@@ -307,6 +322,34 @@ declare class SearchQuery {
to: number;
}>;
}
+type SearchResult = typeof SearchCursor.prototype.value;
+declare abstract class QueryType<Result extends SearchResult = SearchResult> {
+ readonly spec: SearchQuery;
+ constructor(spec: SearchQuery);
+ abstract nextMatch(state: EditorState, curFrom: number, curTo: number): Result | null;
+ abstract prevMatch(state: EditorState, curFrom: number, curTo: number): Result | null;
+ abstract getReplacement(result: Result): string;
+ abstract matchAll(state: EditorState, limit: number): readonly Result[] | null;
+ abstract highlight(state: EditorState, from: number, to: number, add: (from: number, to: number) => void): void;
+}
+declare class StringQuery extends QueryType<SearchResult> {
+ constructor(spec: SearchQuery);
+ nextMatch(state: EditorState, curFrom: number, curTo: number): {
+ from: number;
+ to: number;
+ } | null;
+ private prevMatchInRange;
+ prevMatch(state: EditorState, curFrom: number, curTo: number): {
+ from: number;
+ to: number;
+ } | null;
+ getReplacement(_result: SearchResult): string;
+ matchAll(state: EditorState, limit: number): {
+ from: number;
+ to: number;
+ }[] | null;
+ highlight(state: EditorState, from: number, to: number, add: (from: number, to: number) => void): void;
+}
/**
A state effect that updates the current search query. Note that
this only has an effect if the search state has been initialized
@@ -315,6 +358,7 @@ by running [`openSearchPanel`](https://codemirror.net/6/docs/ref/#search.openSea
once).
*/
declare const setSearchQuery: _codemirror_state.StateEffectType<SearchQuery>;
+declare const togglePanel: _codemirror_state.StateEffectType<boolean>;
/**
Get the current search query from an editor state.
*/
@@ -353,6 +397,7 @@ Replace all instances of the search query with the given
replacement.
*/
declare const replaceAll: Command;
+declare function createSearchPanel(view: EditorView): Panel;
/**
Make sure the search panel is open and focused.
*/
@@ -372,4 +417,4 @@ Default search-related key bindings.
*/
declare const searchKeymap: readonly KeyBinding[];
-export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };
+export { RegExpCursor, SearchCursor, SearchQuery, StringQuery, closeSearchPanel, createSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, selectWord, setSearchQuery, togglePanel };
diff --git a/dist/index.d.ts b/dist/index.d.ts
index 08f5696..663d192 100644
--- a/dist/index.d.ts
+++ b/dist/index.d.ts
@@ -1,6 +1,6 @@
import * as _codemirror_state from '@codemirror/state';
import { Text, Extension, StateCommand, EditorState, SelectionRange, StateEffect } from '@codemirror/state';
-import { Command, KeyBinding, EditorView, Panel } from '@codemirror/view';
+import { Command, EditorView, Panel, KeyBinding } from '@codemirror/view';
/**
A search cursor provides an iterator over text matches in a
@@ -161,6 +161,7 @@ the `"cm-selectionMatch"` class for the highlighting. When
itself will be highlighted with `"cm-selectionMatch-main"`.
*/
declare function highlightSelectionMatches(options?: HighlightOptions): Extension;
+declare const selectWord: StateCommand;
/**
Select next occurrence of the current selection. Expand selection
to the surrounding word when the selection is empty.
@@ -264,6 +265,13 @@ declare class SearchQuery {
*/
readonly wholeWord: boolean;
/**
+ When set, only include search matches within these ranges
+ */
+ readonly scope?: Readonly<{
+ from: number;
+ to: number;
+ }[]>;
+ /**
Create a query object.
*/
constructor(config: {
@@ -293,6 +301,13 @@ declare class SearchQuery {
Enable whole-word matching.
*/
wholeWord?: boolean;
+ /**
+ The ranges to match within
+ */
+ scope?: Readonly<{
+ from: number;
+ to: number;
+ }[]>;
});
/**
Compare this query to another query.
@@ -307,6 +322,34 @@ declare class SearchQuery {
to: number;
}>;
}
+type SearchResult = typeof SearchCursor.prototype.value;
+declare abstract class QueryType<Result extends SearchResult = SearchResult> {
+ readonly spec: SearchQuery;
+ constructor(spec: SearchQuery);
+ abstract nextMatch(state: EditorState, curFrom: number, curTo: number): Result | null;
+ abstract prevMatch(state: EditorState, curFrom: number, curTo: number): Result | null;
+ abstract getReplacement(result: Result): string;
+ abstract matchAll(state: EditorState, limit: number): readonly Result[] | null;
+ abstract highlight(state: EditorState, from: number, to: number, add: (from: number, to: number) => void): void;
+}
+declare class StringQuery extends QueryType<SearchResult> {
+ constructor(spec: SearchQuery);
+ nextMatch(state: EditorState, curFrom: number, curTo: number): {
+ from: number;
+ to: number;
+ } | null;
+ private prevMatchInRange;
+ prevMatch(state: EditorState, curFrom: number, curTo: number): {
+ from: number;
+ to: number;
+ } | null;
+ getReplacement(_result: SearchResult): string;
+ matchAll(state: EditorState, limit: number): {
+ from: number;
+ to: number;
+ }[] | null;
+ highlight(state: EditorState, from: number, to: number, add: (from: number, to: number) => void): void;
+}
/**
A state effect that updates the current search query. Note that
this only has an effect if the search state has been initialized
@@ -315,6 +358,7 @@ by running [`openSearchPanel`](https://codemirror.net/6/docs/ref/#search.openSea
once).
*/
declare const setSearchQuery: _codemirror_state.StateEffectType<SearchQuery>;
+declare const togglePanel: _codemirror_state.StateEffectType<boolean>;
/**
Get the current search query from an editor state.
*/
@@ -353,6 +397,7 @@ Replace all instances of the search query with the given
replacement.
*/
declare const replaceAll: Command;
+declare function createSearchPanel(view: EditorView): Panel;
/**
Make sure the search panel is open and focused.
*/
@@ -372,4 +417,4 @@ Default search-related key bindings.
*/
declare const searchKeymap: readonly KeyBinding[];
-export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };
+export { RegExpCursor, SearchCursor, SearchQuery, StringQuery, closeSearchPanel, createSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, selectWord, setSearchQuery, togglePanel };
diff --git a/dist/index.js b/dist/index.js
index 22172ef..08a9974 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -590,6 +590,7 @@ class SearchQuery {
this.valid = !!this.search && (!this.regexp || validRegExp(this.search));
this.unquoted = this.unquote(this.search);
this.wholeWord = !!config.wholeWord;
+ this.scope = config.scope;
}
/**
@internal
@@ -604,7 +605,7 @@ class SearchQuery {
eq(other) {
return this.search == other.search && this.replace == other.replace &&
this.caseSensitive == other.caseSensitive && this.regexp == other.regexp &&
- this.wholeWord == other.wholeWord;
+ this.wholeWord == other.wholeWord && this.scope == other.scope;
}
/**
@internal
@@ -629,7 +630,12 @@ class QueryType {
}
}
function stringCursor(spec, state, from, to) {
- return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined);
+ const test = spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined;
+ const testWithinScope = (from, to, buffer, bufferPos) => {
+ return (!test || test(from, to, buffer, bufferPos))
+ && (!spec.scope || spec.scope.some(range => from >= range.from && from <= range.to && to >= range.from && to <= range.to));
+ };
+ return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), testWithinScope);
}
function stringWordTest(doc, categorizer) {
return (from, to, buf, bufPos) => {
@@ -693,9 +699,14 @@ class StringQuery extends QueryType {
}
}
function regexpCursor(spec, state, from, to) {
+ const test = spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined;
+ const testWithinScope = (from, to, match) => {
+ return (!test || test(from, to, match))
+ && (!spec.scope || spec.scope.some(range => from >= range.from && from <= range.to && to >= range.from && to <= range.to));
+ };
return new RegExpCursor(state.doc, spec.search, {
ignoreCase: !spec.caseSensitive,
- test: spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined
+ test: testWithinScope,
}, from, to);
}
function charBefore(str, index) {
@@ -735,10 +746,18 @@ class RegExpQuery extends QueryType {
this.prevMatchInRange(state, curTo, state.doc.length);
}
getReplacement(result) {
- return this.spec.unquote(this.spec.replace).replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
- : i == "&" ? result.match[0]
- : i != "0" && +i < result.match.length ? result.match[i]
- : m);
+ return this.spec.unquote(this.spec.replace).replace(/\$([$&]|\d+)/g, (m, i) => {
+ if (i == "&")
+ return result.match[0];
+ if (i == "$")
+ return "$";
+ for (let l = i.length; l > 0; l--) {
+ let n = +i.slice(0, l);
+ if (n > 0 && n < result.match.length)
+ return result.match[n] + i.slice(l);
+ }
+ return m;
+ });
}
matchAll(state, limit) {
let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];
@@ -1222,4 +1241,4 @@ const searchExtensions = [
baseTheme
];
-export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };
+export { RegExpCursor, SearchCursor, SearchQuery, StringQuery, closeSearchPanel, createSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, selectWord, setSearchQuery, togglePanel };
@@ -1,44 +0,0 @@
diff --git a/lib/read.js b/lib/read.js
index fce6283..6131c31 100644
--- a/lib/read.js
+++ b/lib/read.js
@@ -18,7 +18,7 @@ var iconv = require('iconv-lite')
var onFinished = require('on-finished')
var unpipe = require('unpipe')
var zlib = require('zlib')
-
+var Stream = require('stream')
/**
* Module exports.
*/
@@ -166,25 +166,25 @@ function contentstream (req, debug, inflate) {
case 'deflate':
stream = zlib.createInflate()
debug('inflate body')
- req.pipe(stream)
+ // req.pipe(stream)
break
case 'gzip':
stream = zlib.createGunzip()
debug('gunzip body')
- req.pipe(stream)
+ // req.pipe(stream)
break
case 'identity':
stream = req
stream.length = length
- break
+ return req
default:
throw createError(415, 'unsupported content encoding "' + encoding + '"', {
encoding: encoding,
type: 'encoding.unsupported'
})
}
-
- return stream
+ var pass = new Stream.PassThrough(); Stream.pipeline(req, stream, pass, () => {})
+ return pass
}
/**
@@ -1,13 +0,0 @@
diff --git a/lib/MultiReporters.js b/lib/MultiReporters.js
index 98dc4ef..b2a97bf 100644
--- a/lib/MultiReporters.js
+++ b/lib/MultiReporters.js
@@ -160,7 +160,7 @@ MultiReporters.prototype.getCustomOptions = function (options) {
debug('options file (custom)', customOptionsFile);
try {
- if ('.js' === path.extname(customOptionsFile)) {
+ if (['.js', '.cjs'].includes(path.extname(customOptionsFile))) {
customOptions = require(customOptionsFile);
}
else {
-13
View File
@@ -1,13 +0,0 @@
diff --git a/index.js b/index.js
index b2b6bdd..75e6254 100644
--- a/index.js
+++ b/index.js
@@ -46,7 +46,7 @@ function forwarded (req) {
function getSocketAddr (req) {
return req.socket
? req.socket.remoteAddress
- : req.connection.remoteAddress
+ : req.connection && req.connection.remoteAddress
}
/**
@@ -1,13 +0,0 @@
diff --git a/lib/MultiReporters.js b/lib/MultiReporters.js
index d61e019711d5ac7f82c0fb90548bb0eb41ebbb85..e7a9515e05287621301b831191884a84d72ad0a1 100644
--- a/lib/MultiReporters.js
+++ b/lib/MultiReporters.js
@@ -153,7 +153,7 @@ MultiReporters.prototype.getCustomOptions = function (options) {
debug('options file (custom)', customOptionsFile);
try {
- if ('.js' === path.extname(customOptionsFile)) {
+ if (['.js', '.cjs'].includes(path.extname(customOptionsFile))) {
customOptions = require(customOptionsFile);
}
else {
-13
View File
@@ -1,13 +0,0 @@
diff --git a/lib/make-middleware.js b/lib/make-middleware.js
index ee50988..de77364 100644
--- a/lib/make-middleware.js
+++ b/lib/make-middleware.js
@@ -164,7 +164,7 @@ function makeMiddleware (setup) {
if (fieldname == null) return abortWithCode('MISSING_FIELD_NAME')
// don't attach to the files object, if there is no file
- if (!filename) return fileStream.resume()
+ if (!filename) filename = 'undefined'
// Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6)
if (limits && Object.prototype.hasOwnProperty.call(limits, 'fieldNameSize')) {
-76
View File
@@ -1,76 +0,0 @@
diff --git a/lib/index.js b/lib/index.js
index 567ff5d..8eb45f7 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -545,8 +545,8 @@ function clone(instance) {
// tee instance body
p1 = new PassThrough();
p2 = new PassThrough();
- body.pipe(p1);
- body.pipe(p2);
+ Stream.pipeline(body, p1, () => {});
+ Stream.pipeline(body, p2, () => {});
// set instance body to teed body and return the other teed body
instance[INTERNALS].body = p1;
body = p2;
@@ -648,14 +648,14 @@ function writeToStream(dest, instance) {
// body is null
dest.end();
} else if (isBlob(body)) {
- body.stream().pipe(dest);
+ Stream.pipeline(body.stream(), dest, () => {});
} else if (Buffer.isBuffer(body)) {
// body is buffer
dest.write(body);
dest.end();
} else {
// body is stream
- body.pipe(dest);
+ Stream.pipeline(body, dest, () => {});
}
}
@@ -1638,7 +1638,7 @@ function fetch(url, opts) {
res.once('end', function () {
if (signal) signal.removeEventListener('abort', abortAndFinalize);
});
- let body = res.pipe(new PassThrough$1());
+ let body = Stream.pipeline(res, new PassThrough(), error => { if (error) reject(error); });
const response_options = {
url: request.url,
@@ -1679,7 +1679,7 @@ function fetch(url, opts) {
// for gzip
if (codings == 'gzip' || codings == 'x-gzip') {
- body = body.pipe(zlib.createGunzip(zlibOptions));
+ body = Stream.pipeline(body, zlib.createGunzip(zlibOptions), error => { if (error) reject(error); });
response = new Response(body, response_options);
resolve(response);
return;
@@ -1689,13 +1689,13 @@ function fetch(url, opts) {
if (codings == 'deflate' || codings == 'x-deflate') {
// handle the infamous raw deflate response from old servers
// a hack for old IIS and Apache servers
- const raw = res.pipe(new PassThrough$1());
+ const raw = Stream.pipeline(res, new PassThrough(), error => { if (error) reject(error); });
raw.once('data', function (chunk) {
// see http://stackoverflow.com/questions/37519828
if ((chunk[0] & 0x0F) === 0x08) {
- body = body.pipe(zlib.createInflate());
+ body = Stream.pipeline(body, zlib.createInflate(), error => { if (error) reject(error); });
} else {
- body = body.pipe(zlib.createInflateRaw());
+ body = Stream.pipeline(body, zlib.createInflateRaw(), error => { if (error) reject(error); });
}
response = new Response(body, response_options);
resolve(response);
@@ -1712,7 +1712,7 @@ function fetch(url, opts) {
// for br
if (codings == 'br' && typeof zlib.createBrotliDecompress === 'function') {
- body = body.pipe(zlib.createBrotliDecompress());
+ body = Stream.pipeline(body, zlib.createBrotliDecompress(), error => { if (error) reject(error); });
response = new Response(body, response_options);
resolve(response);
return;
@@ -1,13 +0,0 @@
diff --git a/lib/utils.js b/lib/utils.js
index 486f9e1..4584507 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -24,7 +24,7 @@ exports.originalURL = function(req, options) {
var trustProxy = options.proxy;
var proto = (req.headers['x-forwarded-proto'] || '').toLowerCase()
- , tls = req.connection.encrypted || (trustProxy && 'https' == proto.split(/\s*,\s*/)[0])
+ , tls = (req.connection && req.connection.encrypted) || (trustProxy && 'https' == proto.split(/\s*,\s*/)[0])
, host = (trustProxy && req.headers['x-forwarded-host']) || req.headers.host
, protocol = tls ? 'https' : 'http'
, path = req.url || '';
-22
View File
@@ -1,22 +0,0 @@
diff --git a/build/pdf.worker.mjs b/build/pdf.worker.mjs
index 6c5c6f1..bb6b7d1 100644
--- a/build/pdf.worker.mjs
+++ b/build/pdf.worker.mjs
@@ -1830,7 +1830,7 @@ async function __wbg_init(module_or_path) {
}
}
if (typeof module_or_path === 'undefined') {
- module_or_path = new URL('qcms_bg.wasm', import.meta.url);
+ module_or_path = new URL(/* webpackIgnore: true */ 'qcms_bg.wasm', import.meta.url);
}
const imports = __wbg_get_imports();
if (typeof module_or_path === 'string' || typeof Request === 'function' && module_or_path instanceof Request || typeof URL === 'function' && module_or_path instanceof URL) {
@@ -5358,7 +5358,7 @@ var OpenJPEG = (() => {
if (Module["locateFile"]) {
return locateFile("openjpeg.wasm");
}
- return new URL("openjpeg.wasm", import.meta.url).href;
+ return new URL(/* webpackIgnore: true */ "openjpeg.wasm", import.meta.url).href;
}
function getBinarySync(file) {
if (file == wasmBinaryFile && wasmBinary) {
File diff suppressed because it is too large Load Diff
@@ -1,30 +0,0 @@
diff --git a/index.js b/index.js
index 2fae107d03b30aff9320d135ec79c049c51f298a..32ec707ddbf8937e3e130c3deac1060c308bf439 100644
--- a/index.js
+++ b/index.js
@@ -1,6 +1,6 @@
'use strict';
-const {PassThrough} = require('stream');
+const {PassThrough, pipeline} = require('stream');
const extend = require('extend');
let debug = () => {};
@@ -185,7 +185,7 @@ function retryRequest(requestOpts, opts, callback) {
.on('complete', (...params) => handleFinish(params))
.on('finish', (...params) => handleFinish(params));
- requestStream.pipe(delayStream);
+ pipeline(requestStream, delayStream, () => {});
} else {
activeRequest = opts.request(requestOpts, onResponse);
}
@@ -251,7 +251,7 @@ function retryRequest(requestOpts, opts, callback) {
// No more attempts need to be made, just continue on.
if (streamMode) {
retryStream.emit('response', response);
- delayStream.pipe(retryStream);
+ pipeline(delayStream, retryStream, () => {});
requestStream.on('error', err => {
retryStream.destroy(err);
});
@@ -1,30 +0,0 @@
diff --git a/index.js b/index.js
index 298a351097d70a7fb005b6961f4d58247e391d8f..6a809ace0349d40cb2b6732ad66fd8ad208698ca 100644
--- a/index.js
+++ b/index.js
@@ -1,6 +1,6 @@
'use strict';
-const {PassThrough} = require('stream');
+const {PassThrough, pipeline} = require('stream');
const extend = require('extend');
let debug = () => {};
@@ -185,7 +185,7 @@ function retryRequest(requestOpts, opts, callback) {
.on('complete', (...params) => handleFinish(params))
.on('finish', (...params) => handleFinish(params));
- requestStream.pipe(delayStream);
+ pipeline(requestStream, delayStream, () => {});
} else {
activeRequest = opts.request(requestOpts, onResponse);
}
@@ -251,7 +251,7 @@ function retryRequest(requestOpts, opts, callback) {
// No more attempts need to be made, just continue on.
if (streamMode) {
retryStream.emit('response', response);
- delayStream.pipe(retryStream);
+ pipeline(delayStream, retryStream, () => {});
requestStream.on('error', err => {
retryStream.destroy(err);
});
@@ -1,57 +0,0 @@
diff --git a/lib/sandboxed_module.js b/lib/sandboxed_module.js
index 1cd6743..4718b97 100644
--- a/lib/sandboxed_module.js
+++ b/lib/sandboxed_module.js
@@ -4,7 +4,7 @@ var Module = require('module');
var fs = require('fs');
var vm = require('vm');
var path = require('path');
-var builtinModules = require('./builtin_modules.json');
+var builtinModules = Module.builtinModules || require('./builtin_modules.json');
var parent = module.parent;
var globalOptions = {};
var registeredBuiltInSourceTransformers = ['coffee'];
@@ -157,12 +157,20 @@ SandboxedModule.prototype._createRecursiveRequireProxy = function() {
var cache = Object.create(null);
var required = this._getRequires();
for (var key in required) {
- var injectedFilename = requireLike(this.filename).resolve(key);
- cache[injectedFilename] = required[key];
+ // Under Yarn PnP, resolution from a transitive dependency's context may fail
+ // for packages not declared in that dependency's package.json. Silently skip
+ // cache pre-population on failure; the mock will still be injected via the
+ // inject map in requireInterceptor or resolved via RecursiveRequireProxy fallback.
+ try {
+ var injectedFilename = requireLike(this.filename).resolve(key);
+ cache[injectedFilename] = required[key];
+ } catch (e) {}
}
cache[this.filename] = this.exports;
var globals = this.globals;
+ // Store the top-level module's filename for PnP fallback resolution
+ var topLevelFilename = this.filename;
var options;
if(!this._options.sourceTransformersSingleOnly && this._options.sourceTransformers){
options = {
@@ -208,8 +216,18 @@ SandboxedModule.prototype._createRecursiveRequireProxy = function() {
if (request in cache) return cache[request];
return require(request);
}
- // cached modules
- var requestedFilename = requireLike(this.filename).resolve(request);
+ // Resolve the requested module filename.
+ // Under Yarn PnP, packages can only resolve their declared dependencies.
+ // When sandboxed-module loads a transitive dependency, the resolution context
+ // may not have access to all needed packages. Fall back to resolving from
+ // the top-level module's context (the module under test).
+ var requestedFilename;
+ try {
+ requestedFilename = requireLike(this.filename).resolve(request);
+ } catch (e) {
+ if (this.filename === topLevelFilename) throw e;
+ requestedFilename = requireLike(topLevelFilename).resolve(request);
+ }
if (requestedFilename in cache) return cache[requestedFilename];
var sandboxedModule = createInnerSandboxedModule(requestedFilename)
return sandboxedModule.exports;
-57
View File
@@ -1,57 +0,0 @@
diff --git a/index.js b/index.js
index 768f8ca..a882f4d 100644
--- a/index.js
+++ b/index.js
@@ -788,29 +788,29 @@ SendStream.prototype.stream = function stream (path, options) {
// pipe
var stream = fs.createReadStream(path, options)
this.emit('stream', stream)
- stream.pipe(res)
-
- // cleanup
- function cleanup () {
- destroy(stream, true)
- }
-
- // response finished, cleanup
- onFinished(res, cleanup)
-
- // error handling
- stream.on('error', function onerror (err) {
- // clean up stream early
- cleanup()
-
- // error
- self.onStatError(err)
- })
-
- // end
- stream.on('end', function onend () {
- self.emit('end')
- })
+ Stream.pipeline(stream, res, err => { if (err) { self.onStatError(err) } else { self.emit('end') } })
+
+ // // cleanup
+ // function cleanup () {
+ // destroy(stream, true)
+ // }
+ //
+ // // response finished, cleanup
+ // onFinished(res, cleanup)
+ //
+ // // error handling
+ // stream.on('error', function onerror (err) {
+ // // clean up stream early
+ // cleanup()
+ //
+ // // error
+ // self.onStatError(err)
+ // })
+ //
+ // // end
+ // stream.on('end', function onend () {
+ // self.emit('end')
+ // })
}
/**
@@ -1,57 +0,0 @@
diff --git a/build/src/index.js b/build/src/index.js
index a101736..a87f6b9 100644
--- a/build/src/index.js
+++ b/build/src/index.js
@@ -130,6 +130,9 @@ function createMultipartStream(boundary, multipart) {
}
else {
part.body.pipe(stream, { end: false });
+ part.body.on('error', (err) => {
+ stream.destroy(err);
+ });
part.body.on('end', () => {
stream.write('\r\n');
stream.write(finale);
@@ -184,25 +187,25 @@ function teenyRequest(reqOpts, callback) {
// Stream mode
const requestStream = streamEvents(new stream_1.PassThrough());
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- let responseStream;
- requestStream.once('reading', () => {
- if (responseStream) {
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
- }
- else {
- requestStream.once('response', () => {
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
- });
- }
- });
+ // let responseStream;
+ // requestStream.once('reading', () => {
+ // if (responseStream) {
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
+ // }
+ // else {
+ // requestStream.once('response', () => {
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
+ // });
+ // }
+ // });
options.compress = false;
teenyRequest.stats.requestStarting();
- fetch(uri, options).then(res => {
+ (0, node_fetch_1.default)(uri, options).then(res => {
- teenyRequest.stats.requestFinished();
- responseStream = res.body;
- responseStream.on('error', (err) => {
- requestStream.emit('error', err);
- });
+ teenyRequest.stats.requestFinished(); (0, stream_1.pipeline)(res.body, requestStream, () => {});
+ // responseStream = res.body;
+ // responseStream.on('error', (err) => {
+ // requestStream.emit('error', err);
+ // });
const response = fetchToRequestResponse(options, res);
requestStream.emit('response', response);
}, err => {
@@ -1,58 +0,0 @@
diff --git a/build/src/index.js b/build/src/index.js
index af5d15e260e2a47588c7c536447fe84bd3f86136..2b63d0c0b1eb6595c7a0bb314c1df792454c1a72 100644
--- a/build/src/index.js
+++ b/build/src/index.js
@@ -115,6 +115,9 @@ function createMultipartStream(boundary, multipart) {
}
else {
part.body.pipe(stream, { end: false });
+ part.body.on('error', (err) => {
+ stream.destroy(err);
+ });
part.body.on('end', () => {
stream.write('\r\n');
stream.write(finale);
@@ -168,25 +171,27 @@ function teenyRequest(reqOpts, callback) {
// Stream mode
const requestStream = streamEvents(new stream_1.PassThrough());
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- let responseStream;
- requestStream.once('reading', () => {
- if (responseStream) {
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
- }
- else {
- requestStream.once('response', () => {
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
- });
- }
- });
+ // let responseStream;
+ // requestStream.once('reading', () => {
+ // if (responseStream) {
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
+ // }
+ // else {
+ // requestStream.once('response', () => {
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
+ // });
+ // }
+ // });
+
+
options.compress = false;
teenyRequest.stats.requestStarting();
(0, node_fetch_1.default)(uri, options).then(res => {
- teenyRequest.stats.requestFinished();
- responseStream = res.body;
- responseStream.on('error', (err) => {
- requestStream.emit('error', err);
- });
+ teenyRequest.stats.requestFinished(); stream_1.pipeline(res.body, requestStream, () => {});
+ // responseStream = res.body;
+ // responseStream.on('error', (err) => {
+ // requestStream.emit('error', err);
+ // });
const response = fetchToRequestResponse(options, res);
requestStream.emit('response', response);
}, err => {
@@ -1,81 +0,0 @@
diff --git a/dist/WorkerPool.js b/dist/WorkerPool.js
index 4145779f08eefafd0c18394806b6409c595ac5bb..aa16dd6a0f463804455164493a1e75e80cdf656a 100644
--- a/dist/WorkerPool.js
+++ b/dist/WorkerPool.js
@@ -258,6 +258,19 @@ class PoolWorker {
finalCallback();
break;
}
+ case 'logMessage':
+ {
+ const {
+ data: { loggerName, methodName, args }
+ } = message;
+ const {
+ data: jobData
+ } = this.jobs[id];
+ const logger = jobData.getLogger(loggerName);
+ logger[methodName].apply(logger, args);
+ finalCallback();
+ break;
+ }
case 'emitWarning':
{
const {
diff --git a/dist/index.js b/dist/index.js
index 75cd30fb63dc864057c1afc866f43fc7cc0a8020..d834af6ce1e2cd4473c4ae1b325d63eb1190c17e 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -43,6 +43,7 @@ function pitch() {
sourceMap: this.sourceMap,
emitError: this.emitError,
emitWarning: this.emitWarning,
+ getLogger: this.getLogger,
loadModule: this.loadModule,
resolve: this.resolve,
getResolve: this.getResolve,
diff --git a/dist/worker.js b/dist/worker.js
index 8e67959e4b7c9fd0db116509b1459636ba13a097..aca94f1442906baf179ada8e6a56501bbf71c480 100644
--- a/dist/worker.js
+++ b/dist/worker.js
@@ -90,6 +90,22 @@ function writeJson(data) {
writePipeWrite(lengthBuffer);
writePipeWrite(messageBuffer);
}
+const LOGGER_METHODS = ['error', 'warn', 'info', 'log', 'debug', 'trace', 'group', 'groupEnd', 'groupCollapsed', 'status', 'clear', 'profile', 'profileEnd'];
+class Logger {
+ constructor(id, loggerName) {
+ this.id = id
+ this.loggerName = loggerName
+ for (const methodName of LOGGER_METHODS) {
+ this[methodName] = (...args) => {
+ writeJson({
+ type: 'logMessage',
+ id: this.id,
+ data: { loggerName, methodName, args }
+ })
+ }
+ }
+ }
+}
const queue = (0, _queue.default)(({
id,
data
@@ -190,6 +206,7 @@ const queue = (0, _queue.default)(({
}
return options;
},
+ getLogger: (name) => new Logger(id, name),
emitWarning: warning => {
writeJson({
type: 'emitWarning',
@@ -211,6 +228,9 @@ const queue = (0, _queue.default)(({
module._compile(code, filename); // eslint-disable-line no-underscore-dangle
return module.exports;
},
+ addDependency: filename => {
+ buildDependencies.push(filename);
+ },
addBuildDependency: filename => {
buildDependencies.push(filename);
},
-25
View File
@@ -1,25 +0,0 @@
approvedGitRepositories:
- "**"
enableGlobalCache: false
enableScripts: true
enableTelemetry: false
nodeLinker: node-modules
npmMinimalAgeGate: 3d
supportedArchitectures:
cpu:
- current
- arm64
- x64
libc:
- current
- glibc
os:
- current
- darwin
- linux
+43 -18
View File
@@ -1,38 +1,63 @@
# Contributing to Overleaf
Contributing to ShareLaTeX
==========================
Thank you for reading this! If you'd like to report a bug or join in the development
of Overleaf, then here are some notes on how to do that.
of ShareLaTeX, then here are some notes on how to do that.
## Reporting bugs and opening issues
*Note that ShareLaTeX is actually made up of many seperate repositories (a list is available
[here](https://github.com/sharelatex/sharelatex/blob/master/README.md#other-repositories)).*
If you'd like to report a bug or open an issue, please **[check if there is an existing issue](https://github.com/overleaf/overleaf/issues).**
If there is then please add any more information that you have, or give it a 👍.
Reporting bugs and opening issues
---------------------------------
If you'd like a report a bug or open an issue then please:
1. **Find the correct repository.** ShareLaTeX is split across multiple different repositories, each containing a different service (you can find a list of [all repositories here](https://github.com/sharelatex/sharelatex/blob/master/README.md#other-repositories)). If you know the bug only applies to one service, then please open an issue in that repository. For general bugs and issues that span more than one service, please open an issue in the [sharelatex/sharelatex](https://github.com/sharelatex/sharelatex) repository.
2. **Check if there is an existing issue.** If there is then please add
any more information that you have, or give it a "+1" in the comments.
When submitting an issue please describe the issue as clearly as possible, including how to
reproduce the bug, which situations it appears in, what you expected to happen, and what actually happens.
If you can include a screenshot for front end issues that is very helpful.
**Note**: If you are using [www.overleaf.com](www.overleaf.com) and have a problem, or if you would like to request a new feature, please contact the Support team at support@overleaf.com. Raise an issue here only to report bugs in the Community Edition release of Overleaf.
Pull Requests
-------------
## Pull Requests
See [our wiki](https://github.com/overleaf/overleaf/wiki)
for how to manage the Overleaf development environment and for our developer guidelines.
See [our wiki](https://github.com/sharelatex/sharelatex/wiki/Developer-Guidelines)
for how to manage the ShareLaTeX development environment and for our developer guidelines.
We love pull requests, so be bold with them! Don't be afraid of going ahead
and changing something, or adding a new feature. We're very happy to work with you
to get your changes merged into Overleaf.
to get your changes merged into ShareLaTeX.
If you're looking for something to work on, have a look at the [open issues](https://github.com/overleaf/overleaf/issues).
If you've got an idea for a change then please discuss it in the open first,
either by opening an issue, or by joining us in our
[development chat room](http://www.hipchat.com/g1nJMcj7b).
## Security
If you're looking for something to work on, then take a look at our [development roadmap](https://github.com/sharelatex/sharelatex/wiki/Development-Roadmap), or have a look at the open issues in any of the repositories listed [here](https://github.com/sharelatex/sharelatex/blob/master/README.md#other-repositories).
Please see [our security policy](https://github.com/overleaf/overleaf/security/policy) if you would like to report a potential security vulnerability.
Developer Chat Room
-------------------
## Contributor License Agreement
If you want to ask any questions in real-time, or get a feel for what's going on
then please drop into our [development chat room](http://www.hipchat.com/g1nJMcj7b).
If no one is online then you can still leave a message that will hopefully get a reply
when we return.
Before we can accept any contributions of code, we need you to agree to our
[Contributor License Agreement](https://docs.google.com/forms/d/e/1FAIpQLSef79XH3mb7yIiMzZw-yALEegS-wyFetvjTiNBfZvf_IHD2KA/viewform?usp=sf_link).
Security
--------
Please do not publish security vulnerabilities publicly until we've had a chance
to address them. All security related issues/patches should be sent directly to
team@sharelatex.com where we will attempt to address them quickly. If you're
unsure whether something is a security issue or not, then please be cautious and
contact us at team@sharelatex.com first.
Contributor License Agreement
-----------------------------
Before we can accept and contributions of code, we need you to agree to our
[Contributor License Agreement](https://sharelatex.wufoo.com/forms/sharelatex-contributor-license-agreement/).
This is to ensure that you own the copyright of your contribution, and that you
agree to give us a license to use it in both the open source version, and the version
of Overleaf running at www.overleaf.com, which may have additional changes.
of ShareLaTeX running at www.sharelatex.com, which may have additional changes.
+373
View File
@@ -0,0 +1,373 @@
fs = require "fs"
spawn = require("child_process").spawn
exec = require("child_process").exec
rimraf = require "rimraf"
Path = require "path"
semver = require "semver"
knox = require "knox"
SERVICES = [{
name: "web"
repo: "https://github.com/sharelatex/web-sharelatex.git"
}, {
name: "document-updater"
repo: "https://github.com/sharelatex/document-updater-sharelatex.git"
}, {
name: "clsi"
repo: "https://github.com/sharelatex/clsi-sharelatex.git"
}, {
name: "filestore"
repo: "https://github.com/sharelatex/filestore-sharelatex.git"
}, {
name: "track-changes"
repo: "https://github.com/sharelatex/track-changes-sharelatex.git"
}, {
name: "docstore"
repo: "https://github.com/sharelatex/docstore-sharelatex.git"
}]
module.exports = (grunt) ->
grunt.loadNpmTasks 'grunt-bunyan'
grunt.loadNpmTasks 'grunt-execute'
grunt.loadNpmTasks 'grunt-available-tasks'
grunt.loadNpmTasks 'grunt-concurrent'
execute = {}
for service in SERVICES
execute[service.name] =
src: "#{service.name}/app.js"
grunt.initConfig
execute: execute
concurrent:
all:
tasks: ("run:#{service.name}" for service in SERVICES)
options:
limit: SERVICES.length
logConcurrentOutput: true
availabletasks:
tasks:
options:
filter: 'exclude',
tasks: [
'concurrent'
'execute'
'bunyan'
'availabletasks'
]
groups:
"Run tasks": [
"run"
"run:all"
"default"
].concat ("run:#{service.name}" for service in SERVICES)
"Misc": [
"help"
]
"Install tasks": ("install:#{service.name}" for service in SERVICES).concat(["install:all", "install", "install:config"])
"Update tasks": ("update:#{service.name}" for service in SERVICES).concat(["update:all", "update"])
"Config tasks": ["install:config"]
"Checks": ["check", "check:redis", "check:latexmk", "check:s3", "check:make"]
for service in SERVICES
do (service) ->
grunt.registerTask "install:#{service.name}", "Download and set up the #{service.name} service", () ->
done = @async()
Helpers.installService(service.repo, service.name, done)
grunt.registerTask "update:#{service.name}", "Checkout and update the #{service.name} service", () ->
done = @async()
Helpers.updateService(service.name, done)
grunt.registerTask "run:#{service.name}", "Run the ShareLaTeX #{service.name} service", ["bunyan", "execute:#{service.name}"]
grunt.registerTask 'install:config', "Copy the example config into the real config", () ->
Helpers.installConfig @async()
grunt.registerTask 'install:all', "Download and set up all ShareLaTeX services",
["check:make"].concat(
("install:#{service.name}" for service in SERVICES)
).concat(["install:config"])
grunt.registerTask 'install', 'install:all'
grunt.registerTask 'update:all', "Checkout and update all ShareLaTeX services",
["check:make"].concat(
("update:#{service.name}" for service in SERVICES)
)
grunt.registerTask 'update', 'update:all'
grunt.registerTask 'run', "Run all of the sharelatex processes", ['concurrent:all']
grunt.registerTask 'run:all', 'run'
grunt.registerTask 'help', 'Display this help list', 'availabletasks'
grunt.registerTask 'default', 'run'
grunt.registerTask "check:redis", "Check that redis is installed and running", () ->
Helpers.checkRedis @async()
grunt.registerTask "check:latexmk", "Check that latexmk is installed", () ->
Helpers.checkLatexmk @async()
grunt.registerTask "check:s3", "Check that Amazon S3 credentials are configured", () ->
Helpers.checkS3 @async()
grunt.registerTask "check:fs", "Check that local filesystem options are configured", () ->
Helpers.checkFS @async()
grunt.registerTask "check:make", "Check that make is installed", () ->
Helpers.checkMake @async()
grunt.registerTask "check", "Check that you have the required dependencies installed", ["check:redis", "check:latexmk", "check:s3", "check:fs"]
grunt.registerTask "build_deb", "Build an installable .deb file from the current directory", () ->
Helpers.buildDeb @async()
Helpers =
installService: (repo_src, dir, callback = (error) ->) ->
Helpers.cloneGitRepo repo_src, dir, (error) ->
return callback(error) if error?
Helpers.installNpmModules dir, (error) ->
return callback(error) if error?
Helpers.runGruntInstall dir, (error) ->
return callback(error) if error?
callback()
updateService: (dir, callback = (error) ->) ->
Helpers.updateGitRepo dir, (error) ->
return callback(error) if error?
Helpers.installNpmModules dir, (error) ->
return callback(error) if error?
Helpers.runGruntInstall dir, (error) ->
return callback(error) if error?
callback()
cloneGitRepo: (repo_src, dir, callback = (error) ->) ->
if !fs.existsSync(dir)
proc = spawn "git", ["clone", repo_src, dir], stdio: "inherit"
proc.on "close", () ->
callback()
else
console.log "#{dir} already installed, skipping."
callback()
updateGitRepo: (dir, callback = (error) ->) ->
proc = spawn "git", ["checkout", "master"], cwd: dir, stdio: "inherit"
proc.on "close", () ->
proc = spawn "git", ["pull"], cwd: dir, stdio: "inherit"
proc.on "close", () ->
callback()
installNpmModules: (dir, callback = (error) ->) ->
proc = spawn "npm", ["install"], stdio: "inherit", cwd: dir
proc.on "close", () ->
callback()
installConfig: (callback = (error) ->) ->
if !fs.existsSync("config/settings.development.coffee")
grunt.log.writeln "Copying example config into config/settings.development.coffee"
exec "cp config/settings.development.coffee.example config/settings.development.coffee", (error, stdout, stderr) ->
callback(error)
else
grunt.log.writeln "Config file already exists. Skipping."
callback()
runGruntInstall: (dir, callback = (error) ->) ->
proc = spawn "grunt", ["install"], stdio: "inherit", cwd: dir
proc.on "close", () ->
callback()
checkRedis: (callback = (error) ->) ->
grunt.log.write "Checking Redis is running... "
exec "redis-cli info", (error, stdout, stderr) ->
if error? and error.message.match("Could not connect")
grunt.log.error "FAIL. Redis is not running"
return callback(error)
else if error?
return callback(error)
else
m = stdout.match(/redis_version:(.*)/)
if !m?
grunt.log.error "FAIL."
grunt.log.error "Unknown redis version"
error = new Error("Unknown redis version")
else
version = m[1]
if semver.gte(version, "2.6.12")
grunt.log.writeln "OK."
grunt.log.writeln "Running Redis version #{version}"
else
grunt.log.error "FAIL."
grunt.log.error "Redis version is too old (#{version}). Must be 2.6.12 or greater."
error = new Error("Redis version is too old (#{version}). Must be 2.6.12 or greater.")
callback(error)
checkLatexmk: (callback = (error) ->) ->
grunt.log.write "Checking latexmk is installed... "
exec "latexmk --version", (error, stdout, stderr) ->
if error? and error.message.match("command not found")
grunt.log.error "FAIL."
grunt.log.errorlns """
Either latexmk is not installed or is not in your PATH.
latexmk comes with TexLive 2013, and must be a version from 2013 or later.
This is a not a fatal error, but compiling will not work without latexmk
"""
return callback(error)
else if error?
return callback(error)
else
m = stdout.match(/Version (.*)/)
if !m?
grunt.log.error "FAIL."
grunt.log.error "Unknown latexmk version"
error = new Error("Unknown latexmk version")
else
version = m[1]
if semver.gte(version + ".0", "4.39.0")
grunt.log.writeln "OK."
grunt.log.writeln "Running latexmk version #{version}"
else
grunt.log.error "FAIL."
grunt.log.errorlns """
latexmk version is too old (#{version}). Must be 4.39 or greater.
This is a not a fatal error, but compiling will not work without latexmk
"""
error = new Error("latexmk is too old")
callback(error)
checkS3: (callback = (error) ->) ->
Settings = require "settings-sharelatex"
if Settings.filestore.backend==""
grunt.log.writeln "No backend specified. Assuming Amazon S3"
Settings.filestore.backend = "s3"
if Settings.filestore.backend=="s3"
grunt.log.write "Checking S3 credentials... "
try
client = knox.createClient({
key: Settings.filestore.s3.key
secret: Settings.filestore.s3.secret
bucket: Settings.filestore.stores.user_files
})
catch e
grunt.log.error "FAIL."
grunt.log.errorlns """
Please configure your Amazon S3 credentials in config/settings.development.coffee
Amazon S3 (Simple Storage Service) is a cloud storage service provided by
Amazon. ShareLaTeX uses S3 for storing binary files like images. You can
sign up for an account and find out more at:
http://aws.amazon.com/s3/
"""
return callback()
client.getFile "does-not-exist", (error, response) ->
unless response? and response.statusCode == 404
grunt.log.error "FAIL."
grunt.log.errorlns """
Could not connect to Amazon S3. Please check your credentials.
"""
else
grunt.log.write "OK."
callback()
else
grunt.log.writeln "Filestore other than S3 configured. Not checking S3."
callback()
checkFS: (callback = (error) ->) ->
Settings = require "settings-sharelatex"
if Settings.filestore.backend=="fs"
grunt.log.write "Checking FS configuration..."
fs = require("fs")
fs.exists Settings.filestore.stores.user_files, (exists) ->
if exists
grunt.log.write "OK."
else
grunt.log.error "FAIL."
grunt.log.errorlns """
Could not find directory "#{Settings.filestore.stores.user_files}".
Please check your configuration.
"""
else
grunt.log.writeln "Filestore other than FS configured. Not checking FS."
callback()
checkMake: (callback = (error) ->) ->
grunt.log.write "Checking make is installed... "
exec "make --version", (error, stdout, stderr) ->
if error? and error.message.match("command not found")
grunt.log.error "FAIL."
grunt.log.errorlns """
Either make is not installed or is not in your path.
On Ubuntu you can install make with:
sudo apt-get install build-essential
"""
return callback(error)
else if error?
return callback(error)
else
grunt.log.write "OK."
return callback()
buildDeb: (callback = (error) ->) ->
# TODO: filestore uses local 'uploads' directory, not configurable in settings
command = ["fpm", "-s", "dir", "-t", "deb", "-n", "sharelatex", "-v", "0.0.1", "--verbose"]
command.push(
"--maintainer", "'ShareLaTeX <team@sharelatex.com>'"
"--config-files", "/etc/sharelatex/settings.coffee",
"--directories", "/var/data/sharelatex"
"--directories", "/var/log/sharelatex"
)
command.push(
"--depends", "'redis-server > 2.6.12'"
"--depends", "'mongodb-10gen > 2.4.0'"
"--depends", "'nodejs > 0.10.0'"
)
template = fs.readFileSync("package/upstart/sharelatex-template").toString()
for service in SERVICES
fs.writeFileSync "package/upstart/sharelatex-#{service.name}", template.replace(/SERVICE/g, service.name)
command.push(
"--deb-upstart", "package/upstart/sharelatex-#{service.name}"
)
after_install_script = """
#!/bin/sh
sudo adduser --system --group --home /var/www/sharelatex --no-create-home sharelatex
mkdir -p /var/log/sharelatex
chown sharelatex:sharelatex /var/log/sharelatex
"""
for dir in ["user_files", "uploads", "compiles", "cache", "dump"]
after_install_script += """
mkdir -p /var/data/sharelatex/#{dir}
chown sharelatex:sharelatex /var/data/sharelatex/#{dir}
"""
for service in SERVICES
after_install_script += "service sharelatex-#{service.name} restart\n"
fs.writeFileSync "package/scripts/after_install.sh", after_install_script
command.push("--after-install", "package/scripts/after_install.sh")
command.push("--exclude", "'**/.git'")
for path in ["filestore/user_files", "filestore/uploads", "clsi/cache", "clsi/compiles"]
command.push "--exclude", path
for service in SERVICES
command.push "#{service.name}=/var/www/sharelatex/"
command.push(
"package/config/settings.coffee=/etc/sharelatex/settings.coffee"
)
console.log command.join(" ")
exec command.join(" "), (error, stdout, stderr) ->
return callback(error) if error?
console.log stdout
console.error stderr if stderr?
callback()
+93 -200
View File
@@ -1,233 +1,126 @@
<p align="center">
<img src="services/web/public/img/ol-brand/verso-logo.svg" alt="Verso" width="440">
</p>
ShareLaTeX
==========
**A collaborative, real-time editor for Quarto, LaTeX and Typst — documents and presentations.**
[ShareLaTeX](https://www.sharelatex.com) is now open source! ShareLaTeX is an online real-time collaborative LaTeX editor, and you can now run your own local version where you can host, edit, collaborate in real-time, and compile your LaTeX documents. Were still 100% focused on running the hosted version at http://www.sharelatex.com, but we want to be more flexible in how you can use ShareLaTeX, and give something back to our wonderful community.
Verso is a fork of [Overleaf](https://github.com/overleaf/overleaf) that adds
first-class [Quarto](https://quarto.org) and [Typst](https://typst.app) support
alongside Overleaf's LaTeX toolchain. It keeps Overleaf's real-time
collaboration infrastructure and runs **three compilers side by side**, chosen
automatically from the root file's extension:
**[Read more on our blog](https://www.sharelatex.com/blog/2014/02/21/sharelatex-is-now-open-source.html#.UwcnsEJ_ugc)**
| Root file | Compiler | Typical output |
|-----------|----------|----------------|
| `.qmd` | Quarto | PDF (via Typst or LaTeX), or an HTML/RevealJS deck |
| `.tex` | `latexmk` / TeX Live | PDF |
| `.typ` | Typst | PDF |
Installation
------------
All three coexist on one server; no per-project configuration is required to
pick the engine.
**[Please help us make ShareLaTeX as easy to install as possible by answering our quick survey about your system and needs](https://sharelatex.typeform.com/to/PLNits)**
---
We're still figuring out the easiest way to let you install ShareLaTeX and get up and running quickly. If you fill in the above survey in we will be eternally grateful and it will help us make this install process as smooth as possible. For now, here is the best ways:
## Features
### Manually
- **Real-time collaboration** — multiple people editing the same file at once,
powered by Overleaf's operational-transformation engine, with live cursors
and full project history.
- **Three compilers, auto-dispatched** — Quarto, LaTeX and Typst projects live
side by side; the runner is selected from the root file's extension.
- **Language-aware editor for all three**:
- *LaTeX* — syntax highlighting, command/environment/reference autocomplete,
linting (inherited from Overleaf).
- *Quarto (`.qmd`)* — Markdown highlighting plus Quarto-aware completions:
code chunks (```` ```{python} ````, `{r}`, `{julia}`, `{ojs}`…), callouts
and fenced divs (`::: {.callout-note}`, columns, tabsets) and
cross-references (`@fig-`, `@tbl-`, `@sec-`, `@eq-`).
- *Typst (`.typ`)* — syntax highlighting and completions for the common
functions and markup (`#import`, `#let`, `#set`, `#show`, `#figure`,
`#table`, `#cite`, …).
- **Document outline** — section headings are extracted into the sidebar
outline panel for LaTeX, Quarto (`#`, `##`, …) and Typst (`=`, `==`, …).
- **Format at a glance** — the project dashboard shows a per-project format
badge (Quarto / Typst / LaTeX), and the compiler dropdown greys out engines
that don't apply to the current root file.
- **Publish & share compiled output** — publish the compiled result as a
standalone page at `/p/:token`, with three independent access tiers (project
members / any logged-in user / public). Works for both HTML/RevealJS decks
(served live) and PDFs (embedded inline). HTML decks also get a one-click
**Present** button in the toolbar.
- **Quarto Python cells** — optional per-project virtual environment built from
the project's `requirements.txt`, so Python code chunks run during render
(gated to the project owner and invited collaborators).
- **Auto-compile** — the preview refreshes automatically shortly after you stop
typing.
First, check out a local copy of this repository:
## Output formats
In the YAML frontmatter of a `.qmd` file:
```yaml
format: typst # → PDF preview, rendered via Typst (no LaTeX required)
format: pdf # → PDF preview, rendered via LaTeX
format: revealjs # → interactive HTML slideshow preview
format: html # → a static HTML page
```bash
git clone https://github.com/sharelatex/sharelatex.git
cd sharelatex
```
Typst ships inside Quarto, so `format: typst` needs no separate installation.
Next install all the node modules and ShareLaTeX services:
> **Note on display math**: keep `$$ … $$` blocks on a single line. Multi-line
> display-math blocks can trigger YAML parse errors in some Quarto versions.
```bash
npm install
grunt install
```
## Quick start
This will create a config file in `config/settings.development.coffee`. You should open
this now and configure your AWS S3 credentials, and other custom settings.
Now check that your system is set up correctly to run ShareLaTeX (checks that you have
the required dependencies installed.) Watch out for any failures.
```bash
grunt check --force
```
When that has finished, run ShareLaTeX with
```bash
grunt run
```
ShareLaTeX should now be running at http://localhost:3000.
### With Vagrant
There is a Vagrant and Ansible backed VM installation script for ShareLaTeX, maintained by [@palkan](https://github/palkan), available here: https://github.com/palkan/sharelatex-vagrant-ansible
### With Docker
```bash
docker run -d \
-p 80:80 \
-v ~/verso_data:/var/lib/overleaf \
--name verso \
registry.alocoq.fr/verso:latest
```
An [automatic docker-based installer](https://github.com/tiagoboldt/sharelatex-docker) is available. It depends on docker and will build a production environment for running ShareLaTeX on any supported platform.
Open `http://localhost` in your browser, then visit `/launchpad` on first run to
create the admin account.
Dependencies
------------
### Build from source
ShareLaTeX should run on OS X and Linux. You need:
```bash
# Build the base image (system deps + Quarto + TeX Live)
cd server-ce
make build-base
* [Node.js](http://nodejs.org/) 0.10 or greater. We recommend that you use [nvm](https://github.com/creationix/nvm) to install it.
* The [grunt](http://gruntjs.com/) command line tools (Run `npm install -g grunt-cli` to install them)
* A local instance of [Redis](http://redis.io/topics/quickstart) (version 2.6.12 or later) and [MongoDB](http://docs.mongodb.org/manual/installation/) running on their standard ports.
* [TeXLive](https://www.tug.org/texlive/) 2013 or later with the `latexmk` program installed.
# Build the application image
make build-community
```
Config
------
| File | Purpose |
|------|---------|
| `server-ce/Dockerfile-base` | Base OS image — system deps, Quarto (with Typst) and a TeX Live (`latexmk`) toolchain |
| `server-ce/Dockerfile` | Application image — Node services and the compiled frontend |
ShareLaTeX should run out of the box, but if you want to adjust any settings you can do so by
editing the `config/settings.development.coffee` file. Available options are explained inline.
## Architecture
Other repositories
------------------
Verso is a microservices monorepo (Yarn workspaces). All services run inside a
single container managed by `runit`, with `nginx` as the front router.
ShareLaTeX consists of many separate services, each with their own Node.js process
and source code repository. These are all downloaded and set upwhen you run
`grunt install`
```
browser ──→ nginx:80
├── / ──────────────────→ web:4000 (main app, React UI)
├── /socket.io ──────────→ real-time:3026 (WebSocket, OT engine)
├── /p/:token ───────────→ web (published output)
└── /project/*/output/* → clsi-nginx:8080 (compiled output files)
The different services are:
web → document-updater → Redis pub/sub → real-time → browser
web → CLSI (quarto render / latexmk / typst) → output files → nginx → browser
```
### [web](https://github.com/sharelatex/web-sharelatex) [![Build Status](https://travis-ci.org/sharelatex/web-sharelatex.png?branch=master)](https://travis-ci.org/sharelatex/web-sharelatex)
| Service | Role |
|---------|------|
| `web` | HTTP API, React frontend, auth, project & sharing management |
| `real-time` | WebSocket layer, live cursors and edit sync |
| `document-updater` | Operational transformation, Redis pub/sub |
| `clsi` | Compiler — runs `quarto render` (`.qmd`), `latexmk` (`.tex`) or `typst` (`.typ`) and serves output |
| `docstore` | Document text storage (MongoDB) |
| `filestore` | Binary file storage (S3 or local) |
| `project-history` | Change history and version tracking |
The front facing web server that serves all the HTML pages, CSS and JavaScript
to the client. Also contains a lot of logic around creating and editing
projects, and account management.
## Writing documents
### [document-updater](https://github.com/sharelatex/document-updater-sharelatex) [![Build Status](https://travis-ci.org/sharelatex/document-updater-sharelatex.png?branch=master)](https://travis-ci.org/sharelatex/document-updater-sharelatex)
### Quarto (`main.qmd`)
Processes updates that come in from the editor when users modify documents. Ensures that
the updates are applied in the right order, and that only one operation is modifying
the document at a time. Also caches the documents in redis for very fast but persistent
modifications.
```markdown
---
title: My Presentation
author: Your Name
date: today
format: revealjs
### [CLSI](https://github.com/sharelatex/clsi-sharelatex) [![Build Status](https://travis-ci.org/sharelatex/clsi-sharelatex.png?branch=master)](https://travis-ci.org/sharelatex/clsi-sharelatex)
The Common LaTeX Service Interface (CLSI) which provides an API for compiling LaTeX
documents.
### [filestore](https://github.com/sharelatex/filestore-sharelatex) [![Build Status](https://travis-ci.org/sharelatex/filestore-sharelatex.png?branch=master)](https://travis-ci.org/sharelatex/filestore-sharelatex)
An API for performing CRUD (Create, Read, Update and Delete) operations on binary files
(like images) stored in ShareLaTeX.
### [track-changes](https://github.com/sharelatex/track-changes-sharelatex) [![Build Status](https://travis-ci.org/sharelatex/track-changes-sharelatex.png?branch=master)](https://travis-ci.org/sharelatex/track-changes-sharelatex)
An API for compressing and storing the updates applied to a document, and then rendering a diff of the changes
between any two time points. *Still in development and not hooked into the UI yet*.
Contributing
------------
Please see the [CONTRIBUTING](https://github.com/sharelatex/sharelatex/blob/master/CONTRIBUTING.md) file for information on contributing to the development of ShareLaTeX. See [our wiki](https://github.com/sharelatex/sharelatex/wiki/Developer-Guidelines) for information on setting up a development environment and how to recompile and run ShareLaTeX after modifications.
Authors
---
## Slide one
- [Henry Oswald](http://twitter.com/henryoswald)
- [James Allen](http://twitter.com/thejpallen)
Write **Markdown** here.
License
----
## Mathematics
The code in this repository is released under the GNU AFFERO GENERAL PUBLIC LICENSE, version 3. A copy can be found in the `LICENSE` file.
$$\int_0^\infty e^{-x^2}\,dx = \frac{\sqrt{\pi}}{2}$$
```
Switch `format: revealjs` to `format: typst` (or `pdf`) for a PDF preview.
### LaTeX (`main.tex`)
LaTeX works exactly as in Overleaf: a project whose root file is a `.tex` file
compiles with `latexmk`/TeX Live, no setting required. The **Example LaTeX
project** in the *New project* menu is a ready-made starting point.
> The bundled TeX Live is a minimal install. Documents that need extra packages
> may not build out of the box — see `server-ce/Dockerfile-base` for how to
> switch to a fuller TeX Live scheme.
### Typst (`main.typ`)
A project whose root file is a `.typ` file compiles directly to PDF with
[Typst](https://typst.app) — fast, modern markup with a real scripting
language. Verso drives the Typst bundled with Quarto, so no extra install is
needed. Use the **Blank Typst project** entry in the *New project* menu to get
started.
## Publishing compiled output
From **Share → Publish**, Verso compiles the project and snapshots the result to
a standalone page at `/p/:token`:
- **HTML / RevealJS** decks are served as a live page (the **Present** toolbar
button is a one-click shortcut to this).
- **PDF** output is embedded inline; the raw file stays reachable at
`/p/:token/output.pdf`.
Three stable links are issued, one per access tier — project members, any
logged-in user, or anyone — and each can be copied or independently reset.
## Environment variables
Verso inherits all of Overleaf's environment variables (prefixed `OVERLEAF_`).
The most commonly needed:
| Variable | Default | Description |
|----------|---------|-------------|
| `OVERLEAF_APP_NAME` | `Verso` | Name shown in the UI |
| `OVERLEAF_NAV_TITLE` | — | Instance name/version shown in the top bar |
| `OVERLEAF_MONGO_URL` | `mongodb://mongo/sharelatex` | MongoDB connection string |
| `OVERLEAF_REDIS_HOST` | `localhost` | Redis host |
| `OVERLEAF_SITE_URL` | — | Public URL (used in emails and published links) |
| `OVERLEAF_SITE_LANGUAGE` | `en` | Default UI language (e.g. `fr`) |
| `OVERLEAF_ENABLE_PROJECT_PYTHON_VENV` | `false` | Allow Quarto Python cells to use a project `requirements.txt` |
| `OVERLEAF_ADMIN_EMAIL` | — | Email for the first admin account |
See the [Overleaf Server documentation](https://github.com/overleaf/overleaf/wiki)
for the full list.
## Relation to Overleaf
Verso is a fork of [Overleaf Community Edition](https://github.com/overleaf/overleaf).
The main additions on top of upstream are:
- Quarto and Typst compilers running alongside LaTeX, dispatched by the root
file's extension.
- Editor language support (highlighting, autocomplete, outline) for Quarto and
Typst.
- A per-project format badge on the dashboard and a root-file-aware compiler
selector.
- Publishing/sharing of compiled output (HTML decks and PDFs) via `/p/:token`
with tiered access links, and a toolbar **Present** shortcut.
- Optional per-project Python virtual environments for Quarto code execution.
- Verso branding (name, logo, palette, loading animation).
All other infrastructure — real-time collaboration, history, auth, file
storage, project management — is unchanged from Overleaf.
## Contributing
Contributions are welcome — open an issue or pull request on the
[Verso repository](https://git.alocoq.fr/alois/verso). The upstream Overleaf
contribution guidelines are in [CONTRIBUTING.md](CONTRIBUTING.md).
## License
GNU Affero General Public License v3 — see [LICENSE](LICENSE).
Copyright © Overleaf, 20142026 (original code).
Verso modifications © Aloïs Coquillard, 2026.
Copyright (c) ShareLaTeX, 2014.
-41
View File
@@ -1,41 +0,0 @@
# Verso — Next Alpha Roadmap
Ideas and features deferred from the current alpha.
---
## Next alpha (post-current)
### Typst editing experience (inspired by Collabst)
- **typst.ts WASM preview** — Run the Typst compiler in the browser via
WebAssembly (typst.ts). This would give instant, sub-second preview
without a server round-trip, and would eliminate the entire class of
race conditions in the CLSI watcher (files written → typst compiles →
resolver missed). Could coexist with the CLSI watcher for PDF export
while using the WASM path for live preview.
- **Tinymist LSP integration** — Wire up
[Tinymist](https://github.com/Myriad-Dreamin/tinymist) (the Typst
language server) behind a WebSocket proxy. Would give Typst files
first-class autocomplete, hover docs, go-to-definition, and inline
error diagnostics — the main editing comfort gap vs. a native editor.
### Editor UX for non-LaTeX formats (.typ, .qmd, .md)
- **Visual/rich-text editing mode** — A toggle between raw source and a
rendered-in-place view for `.typ`, `.qmd`, and `.md` files (similar to
Overleaf's rich-text mode for LaTeX). Users who don't know Typst or
Markdown syntax should be able to edit content without seeing markup.
CodeMirror 6 already supports this pattern via a custom `NodeView` layer
or a separate Prosemirror bridge.
- **Toolbar / insertion shortcuts** — A formatting toolbar and keyboard
shortcuts for common operations, adapted per file type:
- **All formats**: bold, italic, underline, headings, bullet/numbered
lists, inline code, links.
- **Quarto / Markdown**: insert image, insert table, insert code block
with language tag.
- **Quarto RevealJS**: insert slide divider (`---`), insert speaker
notes (`::: notes`), insert columns layout, insert video embed
(using Quarto's `{{< video >}}` shortcode).
Vendored
+54
View File
@@ -0,0 +1,54 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu-12.04"
config.vm.box_url = "http://files.vagrantup.com/precise64.box"
config.vm.network :forwarded_port, guest: 3000, host: 3000
config.ssh.forward_agent = true
config.vm.provider "virtualbox" do |v|
v.memory = 1024
end
config.vm.provision :chef_solo do |chef|
chef.cookbooks_path = "chef/cookbooks"
chef.add_recipe 'apt'
chef.add_recipe 'redis-server'
chef.add_recipe 'mongodb'
chef.add_recipe 'nodejs'
chef.add_recipe 'texlive'
chef.add_recipe 'sharelatex'
# You may also specify custom JSON attributes:
chef.json = {}
end
# Enable provisioning with chef server, specifying the chef server URL,
# and the path to the validation key (relative to this Vagrantfile).
#
# The Opscode Platform uses HTTPS. Substitute your organization for
# ORGNAME in the URL and validation key.
#
# If you have your own Chef Server, use the appropriate URL, which may be
# HTTP instead of HTTPS depending on your configuration. Also change the
# validation key to validation.pem.
#
# config.vm.provision :chef_client do |chef|
# chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME"
# chef.validation_key_path = "ORGNAME-validator.pem"
# end
#
# If you're using the Opscode platform, your validator client is
# ORGNAME-validator, replacing ORGNAME with your organization name.
#
# If you have your own Chef Server, the default validation client name is
# chef-validator, unless you changed the configuration.
#
# chef.validation_client_name = "ORGNAME-validator"
end
-3
View File
@@ -1,3 +0,0 @@
/* eslint-disable no-undef */
rs.initiate({ _id: 'overleaf', members: [{ _id: 0, host: 'mongo:27017' }] })
+3
View File
@@ -0,0 +1,3 @@
current_dir = File.dirname(__FILE__)
cookbook_path ["#{current_dir}/../cookbooks"]
+173
View File
@@ -0,0 +1,173 @@
apt Cookbook CHANGELOG
======================
This file is used to list changes made in each version of the apt cookbook.
v2.3.8 (2014-02-14)
-------------------
### Bug
- **[COOK-4287](https://tickets.opscode.com/browse/COOK-4287)** - Cleanup the Kitchen
v2.3.6
------
* [COOK-4154] - Add chefspec matchers.rb file to apt cookbook
* [COOK-4102] - Only index created repository
v2.3.6
------
* [COOK-4154] - Add chefspec matchers.rb file to apt cookbook
* [COOK-4102] - Only index created repository
v2.3.4
------
No change. Version bump for toolchain sanity
v2.3.2
------
- [COOK-3905] apt-get-update-periodic: configuration for the update period
- Updating style for rubocops
- Updating test-kitchen harness
v2.3.0
------
### Bug
- **[COOK-3812](https://tickets.opscode.com/browse/COOK-3812)** - Add a way to bypass the apt existence check
### Improvement
- **[COOK-3567](https://tickets.opscode.com/browse/COOK-3567)** - Allow users to bypass apt-cache via attributes
v2.2.1
------
### Improvement
- **[COOK-664](https://tickets.opscode.com/browse/COOK-664)** - Check platform before running apt-specific commands
v2.2.0
------
### Bug
- **[COOK-3707](https://tickets.opscode.com/browse/COOK-3707)** - multiple nics confuse apt::cacher-client
v2.1.2
------
### Improvement
- **[COOK-3551](https://tickets.opscode.com/browse/COOK-3551)** - Allow user to set up a trusted APT repository
v2.1.1
------
### Bug
- **[COOK-1856](https://tickets.opscode.com/browse/COOK-1856)** - Match GPG keys without case sensitivity
v2.1.0
------
- [COOK-3426]: cacher-ng fails with restrict_environment set to true
- [COOK-2859]: cacher-client executes out of order
- [COOK-3052]: Long GPG keys are downloaded on every run
- [COOK-1856]: apt cookbook should match keys without case sensitivity
- [COOK-3255]: Attribute name incorrect in README
- [COOK-3225]: Call use_inline_resources only if defined
- [COOK-3386]: Cache dir for apt-cacher-ng
- [COOK-3291]: apt_repository: enable usage of a keyserver on port 80
- Greatly expanded test coverage with ChefSpec and Test-Kitchen
v2.0.0
------
### Bug
- [COOK-2258]: apt: LWRP results in error under why-run mode in apt 1.9.0 cookbook
v1.10.0
-------
### Improvement
- [COOK-2885]: Improvements for apt cache server search
### Bug
- [COOK-2441]: Apt recipe broken in new chef version
- [COOK-2660]: Create Debian 6.0 "squeeze" specific template for
apt-cacher-ng
v1.9.2
------
- [COOK-2631] - Create Ubuntu 10.04 specific template for apt-cacher-ng
v1.9.0
------
- [COOK-2185] - Proxy for apt-key
- [COOK-2338] - Support pinning by glob() or regexp
v1.8.4
------
- [COOK-2171] - Update README to clarify required Chef version: 10.18.0
or higher.
v1.8.2
------
- [COOK-2112] - need [] around "arch" in sources.list entries
- [COOK-2171] - fixes a regression in the notification
v1.8.0
------
- [COOK-2143] - Allow for a custom cacher-ng port
- [COOK-2171] - On `apt_repository.run_action(:add)` the source file
is not created.
- [COOK-2184] - apt::cacher-ng, use `cacher_port` attribute in
acng.conf
v1.7.0
------
- [COOK-2082] - add "arch" parameter to apt_repository LWRP
v1.6.0
------
- [COOK-1893] - `apt_preference` use "`package_name`" resource instead of "name"
- [COOK-1894] - change filename for sources.list.d files
- [COOK-1914] - Wrong dir permissions for /etc/apt/preferences.d/
- [COOK-1942] - README.md has wrong name for the keyserver attribute
- [COOK-2019] - create 01proxy before any other apt-get updates get executed
v1.5.2
------
- [COOK-1682] - use template instead of file resource in apt::cacher-client
- [COOK-1875] - cacher-client should be Environment-aware
V1.5.0
------
- [COOK-1500] - Avoid triggering apt-get update
- [COOK-1548] - Add execute commands for autoclean and autoremove
- [COOK-1591] - Setting up the apt proxy should leave https
connections direct
- [COOK-1596] - execute[apt-get-update-periodic] never runs
- [COOK-1762] - create /etc/apt/preferences.d directory
- [COOK-1776] - apt key check isn't idempotent
v1.4.8
------
* Adds test-kitchen support
- [COOK-1435] - repository lwrp is not idempotent with http key
v1.4.6
------
- [COOK-1530] - apt_repository isn't aware of update-success-stamp
file (also reverts COOK-1382 patch).
v1.4.4
------
- [COOK-1229] - Allow cacher IP to be set manually in non-Chef Solo
environments
- [COOK-1530] - Immediately update apt-cache when sources.list file is dropped off
v1.4.2
------
- [COOK-1155] - LWRP for apt pinning
v1.4.0
------
- [COOK-889] - overwrite existing repo source files
- [COOK-921] - optionally use cookbook\_file or remote\_file for key
- [COOK-1032] - fixes problem with apt repository key installation
+248
View File
@@ -0,0 +1,248 @@
apt Cookbook
============
This cookbook includes recipes to execute apt-get update to ensure the local APT package cache is up to date. There are recipes for managing the apt-cacher-ng caching proxy and proxy clients. It also includes a LWRP for managing APT repositories in /etc/apt/sources.list.d as well as an LWRP for pinning packages via /etc/apt/preferences.d.
Requirements
------------
**Version 2.0.0+ of this cookbook requires Chef 11.0.0 or later**. If your Chef version is earlier than 11.0.0, use version 1.10.0 of this cookbook.
Version 1.8.2 to 1.10.0 of this cookbook requires **Chef 10.16.4** or later.
If your Chef version is earlier than 10.16.4, use version 1.7.0 of this cookbook.
### Platform
Please refer to the [TESTING file](TESTING.md) to see the currently (and passing) tested platforms. The release was tested on:
* Ubuntu 10.04
* Ubuntu 12.04
* Ubuntu 13.04
* Debian 7.1
* Debian 6.0 (have with manual testing)
May work with or without modification on other Debian derivatives.
-------
### default
This recipe installs the `update-notifier-common` package to provide the timestamp file used to only run `apt-get update` if the cache is more than one day old.
This recipe should appear first in the run list of Debian or Ubuntu nodes to ensure that the package cache is up to date before managing any `package` resources with Chef.
This recipe also sets up a local cache directory for preseeding packages.
**Including the default recipe on a node that does not support apt (such as Windows) results in a noop.**
### cacher-client
Configures the node to use the `apt-cacher-ng` server as a client.
#### Bypassing the cache
Occasionally you may come across repositories that do not play nicely when the node is using an `apt-cacher-ng` server. You can configure `cacher-client` to bypass the server and connect directly to the repository with the `cache_bypass` attribute.
To do this, you need to override the `cache_bypass` attribute with an array of repositories, with each array key as the repository URL and value as the protocol to use:
```json
{
...,
'apt': {
...,
'cache_bypass': {
URL: PROTOCOL
}
}
}
```
For example, to prevent caching and directly connect to the repository at `download.oracle.com` via http:
```json
{
'apt': {
'cache_bypass': {
'download.oracle.com': 'http'
}
}
}
```
### cacher-ng
Installs the `apt-cacher-ng` package and service so the system can provide APT caching. You can check the usage report at http://{hostname}:3142/acng-report.html.
If you wish to help the `cacher-ng` recipe seed itself, you must now explicitly include the `cacher-client` recipe in your run list **after** `cacher-ng` or you will block your ability to install any packages (ie. `apt-cacher-ng`).
Attributes
----------
* `['apt']['cacher_ipaddress']` - use a cacher server (or standard proxy server) not available via search
* `['apt']['cacher_interface]` - interface to connect to the cacher-ng service, no default.
* `['apt']['cacher_port']` - port for the cacher-ng service (either client or server), default is '3142'
* `['apt']['cacher_dir']` - directory used by cacher-ng service, default is '/var/cache/apt-cacher-ng'
* `['apt']['cacher-client']['restrict_environment']` - restrict your node to using the `apt-cacher-ng` server in your Environment, default is 'false'
* `['apt']['compiletime']` - force the `cacher-client` recipe to run before other recipes. It forces apt to use the proxy before other recipes run. Useful if your nodes have limited access to public apt repositories. This is overridden if the `cacher-ng` recipe is in your run list. Default is 'false'
* `['apt']['cache_bypass']` - array of URLs to bypass the cache. Accepts the URL and protocol to fetch directly from the remote repository and not attempt to cache
* `['apt']['periodic_update_min_delay']` - minimum delay (in seconds) beetween two actual executions of `apt-get update` by the `execute[apt-get-update-periodic]` resource, default is '86400' (24 hours)
Libraries
---------
There is an `interface_ipaddress` method that returns the IP address for a particular host and interface, used by the `cacher-client` recipe. To enable it on the server use the `['apt']['cacher_interface']` attribute.
Resources/Providers
-------------------
### `apt_repository`
This LWRP provides an easy way to manage additional APT repositories. Adding a new repository will notify running the `execute[apt-get-update]` resource immediately.
#### Actions
- :add: creates a repository file and builds the repository listing
- :remove: removes the repository file
#### Attribute Parameters
- repo_name: name attribute. The name of the channel to discover
- uri: the base of the Debian distribution
- distribution: this is usually your release's codename...ie something like `karmic`, `lucid` or `maverick`
- components: package groupings..when it doubt use `main`
- arch: constrain package to a particular arch like `i386`, `amd64` or even `armhf` or `powerpc`. Defaults to nil.
- trusted: treat all packages from this repository as authenticated regardless of signature
- deb_src: whether or not to add the repository as a source repo as well - value can be `true` or `false`, default `false`.
- keyserver: the GPG keyserver where the key for the repo should be retrieved
- key: if a `keyserver` is provided, this is assumed to be the fingerprint, otherwise it can be either the URI to the GPG key for the repo, or a cookbook_file.
- key_proxy: if set, pass the specified proxy via `http-proxy=` to GPG.
- cookbook: if key should be a cookbook_file, specify a cookbook where the key is located for files/default. Defaults to nil, so it will use the cookbook where the resource is used.
#### Examples
Add the Zenoss repo:
```ruby
apt_repository 'zenoss' do
uri 'http://dev.zenoss.org/deb'
components ['main', 'stable']
end
```
Add the Nginx PPA, grabbing the key from keyserver:
```ruby
apt_repository 'nginx-php' do
uri 'http://ppa.launchpad.net/nginx/php5/ubuntu'
distribution node['lsb']['codename']
components ['main']
keyserver 'keyserver.ubuntu.com'
key 'C300EE8C'
end
```
Add the Nginx PPA, grab the key from the keyserver, and add source repo:
```ruby
apt_repository 'nginx-php' do
uri 'http://ppa.launchpad.net/nginx/php5/ubuntu'
distribution node['lsb']['codename']
components ['main']
keyserver 'keyserver.ubuntu.com'
key 'C300EE8C'
deb_src true
end
```
Add the Cloudera Repo of CDH4 packages for Ubuntu 12.04 on AMD64:
```ruby
apt_repository 'cloudera' do
uri 'http://archive.cloudera.com/cdh4/ubuntu/precise/amd64/cdh'
arch 'amd64'
distribution 'precise-cdh4'
components ['contrib']
key 'http://archive.cloudera.com/debian/archive.key'
end
```
Remove Zenoss repo:
```ruby
apt_repository 'zenoss' do
action :remove
end
```
### `apt_preference`
This LWRP provides an easy way to pin packages in /etc/apt/preferences.d. Although apt-pinning is quite helpful from time to time please note that Debian does not encourage its use without thorough consideration.
Further information regarding apt-pinning is available via http://wiki.debian.org/AptPreferences.
#### Actions
- :add: creates a preferences file under /etc/apt/preferences.d
- :remove: Removes the file, therefore unpin the package
#### Attribute Parameters
- package_name: name attribute. The name of the package
- glob: Pin by glob() expression or regexp surrounded by /.
- pin: The package version/repository to pin
- pin_priority: The pinning priority aka "the highest package version wins"
#### Examples
Pin libmysqlclient16 to version 5.1.49-3:
```ruby
apt_preference 'libmysqlclient16' do
pin 'version 5.1.49-3'
pin_priority '700'
end
```
Unpin libmysqlclient16:
```ruby
apt_preference 'libmysqlclient16' do
action :remove
end
```
Pin all packages from dotdeb.org:
```ruby
apt_preference 'dotdeb' do
glob '*'
pin 'origin packages.dotdeb.org'
pin_priority '700'
end
```
Usage
-----
Put `recipe[apt]` first in the run list. If you have other recipes that you want to use to configure how apt behaves, like new sources, notify the execute resource to run, e.g.:
```ruby
template '/etc/apt/sources.list.d/my_apt_sources.list' do
notifies :run, 'execute[apt-get update]', :immediately
end
```
The above will run during execution phase since it is a normal template resource, and should appear before other package resources that need the sources in the template.
Put `recipe[apt::cacher-ng]` in the run_list for a server to provide APT caching and add `recipe[apt::cacher-client]` on the rest of the Debian-based nodes to take advantage of the caching server.
If you want to cleanup unused packages, there is also the `apt-get autoclean` and `apt-get autoremove` resources provided for automated cleanup.
License & Authors
-----------------
- Author:: Joshua Timberman (joshua@opscode.com)
- Author:: Matt Ray (matt@opscode.com)
- Author:: Seth Chisamore (schisamo@opscode.com)
```text
Copyright 2009-2013, Opscode, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
+28
View File
@@ -0,0 +1,28 @@
#
# Cookbook Name:: apt
# Attributes:: default
#
# Copyright 2009-2013, Opscode, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
default['apt']['cacher-client']['restrict_environment'] = false
default['apt']['cacher_dir'] = '/var/cache/apt-cacher-ng'
default['apt']['cacher_interface'] = nil
default['apt']['cacher_port'] = 3142
default['apt']['caching_server'] = false
default['apt']['compiletime'] = false
default['apt']['key_proxy'] = ''
default['apt']['cache_bypass'] = {}
default['apt']['periodic_update_min_delay'] = 86_400
@@ -0,0 +1,50 @@
[DEFAULT]
;; All times are in seconds, but you can add a suffix
;; for minutes(m), hours(h) or days(d)
;; commented out address so apt-proxy will listen on all IPs
;; address = 127.0.0.1
port = 9999
cache_dir = /var/cache/apt-proxy
;; Control files (Packages/Sources/Contents) refresh rate
min_refresh_delay = 1s
complete_clientless_downloads = 1
;; Debugging settings.
debug = all:4 db:0
time = 30
passive_ftp = on
;;--------------------------------------------------------------
;; Cache housekeeping
cleanup_freq = 1d
max_age = 120d
max_versions = 3
;;---------------------------------------------------------------
;; Backend servers
;;
;; Place each server in its own [section]
[ubuntu]
; Ubuntu archive
backends =
http://us.archive.ubuntu.com/ubuntu
[ubuntu-security]
; Ubuntu security updates
backends = http://security.ubuntu.com/ubuntu
[debian]
;; Backend servers, in order of preference
backends =
http://debian.osuosl.org/debian/
[security]
;; Debian security archive
backends =
http://security.debian.org/debian-security
http://ftp2.de.debian.org/debian-security
+48
View File
@@ -0,0 +1,48 @@
#
# Cookbook Name:: apt
# Library:: helpers
#
# Copyright 2013 Opscode, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
module Apt
# Helpers for apt
module Helpers
# Determines if apt is installed on a system.
#
# @return [Boolean]
def apt_installed?
!which('apt-get').nil?
end
# Finds a command in $PATH
#
# @return [String, nil]
def which(cmd)
paths = (ENV['PATH'].split(::File::PATH_SEPARATOR) + %w(/bin /usr/bin /sbin /usr/sbin))
paths.each do |path|
possible = File.join(path, cmd)
return possible if File.executable?(possible)
end
nil
end
end
end
Chef::Recipe.send(:include, ::Apt::Helpers)
Chef::Resource.send(:include, ::Apt::Helpers)
Chef::Provider.send(:include, ::Apt::Helpers)
+17
View File
@@ -0,0 +1,17 @@
if defined?(ChefSpec)
def add_apt_preference(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:apt_preference, :add, resource_name)
end
def remove_apt_preference(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:apt_preference, :remove, resource_name)
end
def add_apt_repository(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:apt_repository, :add, resource_name)
end
def remove_apt_repository(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:apt_repository, :remove, resource_name)
end
end
+31
View File
@@ -0,0 +1,31 @@
#
# Cookbook Name:: apt
# library:: network
#
# Copyright 2013, Opscode, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
module ::Apt
def interface_ipaddress(host, interface)
if interface
addresses = host['network']['interfaces'][interface]['addresses']
addresses.select do |ip, data|
return ip if data['family'].eql?('inet')
end
else
return host.ipaddress
end
end
end
File diff suppressed because one or more lines are too long
+34
View File
@@ -0,0 +1,34 @@
name 'apt'
maintainer 'Opscode, Inc.'
maintainer_email 'cookbooks@opscode.com'
license 'Apache 2.0'
description 'Configures apt and apt services and LWRPs for managing apt repositories and preferences'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version '2.3.8'
recipe 'apt', 'Runs apt-get update during compile phase and sets up preseed directories'
recipe 'apt::cacher-ng', 'Set up an apt-cacher-ng caching proxy'
recipe 'apt::cacher-client', 'Client for the apt::cacher-ng caching proxy'
%w{ ubuntu debian }.each do |os|
supports os
end
attribute 'apt/cacher-client/restrict_environment',
:description => 'Whether to restrict the search for the caching server to the same environment as this node',
:default => 'false'
attribute 'apt/cacher_port',
:description => 'Default listen port for the caching server',
:default => '3142'
attribute 'apt/cacher_interface',
:description => 'Default listen interface for the caching server',
:default => nil
attribute 'apt/key_proxy',
:description => 'Passed as the proxy passed to GPG for the apt_repository resource',
:default => ''
attribute 'apt/caching_server',
:description => 'Set this to true if the node is a caching server',
:default => 'false'
@@ -0,0 +1,63 @@
#
# Cookbook Name:: apt
# Provider:: preference
#
# Copyright 2010-2011, Opscode, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Build preferences.d file contents
def build_pref(package_name, pin, pin_priority)
"Package: #{package_name}\nPin: #{pin}\nPin-Priority: #{pin_priority}\n"
end
action :add do
new_resource.updated_by_last_action(false)
preference = build_pref(
new_resource.glob || new_resource.package_name,
new_resource.pin,
new_resource.pin_priority
)
preference_dir = directory '/etc/apt/preferences.d' do
owner 'root'
group 'root'
mode 00755
recursive true
action :nothing
end
preference_file = file "/etc/apt/preferences.d/#{new_resource.name}" do
owner 'root'
group 'root'
mode 00644
content preference
action :nothing
end
preference_dir.run_action(:create)
# write out the preference file, replace it if it already exists
preference_file.run_action(:create)
end
action :remove do
if ::File.exists?("/etc/apt/preferences.d/#{new_resource.name}")
Chef::Log.info "Un-pinning #{new_resource.name} from /etc/apt/preferences.d/"
file "/etc/apt/preferences.d/#{new_resource.name}" do
action :delete
end
new_resource.updated_by_last_action(true)
end
end
+150
View File
@@ -0,0 +1,150 @@
#
# Cookbook Name:: apt
# Provider:: repository
#
# Copyright 2010-2011, Opscode, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use_inline_resources if defined?(use_inline_resources)
def whyrun_supported?
true
end
# install apt key from keyserver
def install_key_from_keyserver(key, keyserver)
execute "install-key #{key}" do
if !node['apt']['key_proxy'].empty?
command "apt-key adv --keyserver-options http-proxy=#{node['apt']['key_proxy']} --keyserver hkp://#{keyserver}:80 --recv #{key}"
else
command "apt-key adv --keyserver #{keyserver} --recv #{key}"
end
action :run
not_if do
extract_fingerprints_from_cmd('apt-key finger').any? do |fingerprint|
fingerprint.end_with?(key.upcase)
end
end
end
end
# run command and extract gpg ids
def extract_fingerprints_from_cmd(cmd)
so = Mixlib::ShellOut.new(cmd)
so.run_command
so.stdout.split(/\n/).map do |t|
if z = t.match(/^ +Key fingerprint = ([0-9A-F ]+)/)
z[1].split.join
end
end.compact
end
# install apt key from URI
def install_key_from_uri(uri)
key_name = uri.split(/\//).last
cached_keyfile = "#{Chef::Config[:file_cache_path]}/#{key_name}"
if new_resource.key =~ /http/
remote_file cached_keyfile do
source new_resource.key
mode 00644
action :create
end
else
cookbook_file cached_keyfile do
source new_resource.key
cookbook new_resource.cookbook
mode 00644
action :create
end
end
execute "install-key #{key_name}" do
command "apt-key add #{cached_keyfile}"
action :run
not_if do
installed_keys = extract_fingerprints_from_cmd('apt-key finger')
proposed_keys = extract_fingerprints_from_cmd("gpg --with-fingerprint #{cached_keyfile}")
(installed_keys & proposed_keys).sort == proposed_keys.sort
end
end
end
# build repo file contents
def build_repo(uri, distribution, components, trusted, arch, add_deb_src)
components = components.join(' ') if components.respond_to?(:join)
repo_options = []
repo_options << "arch=#{arch}" if arch
repo_options << 'trusted=yes' if trusted
repo_options = '[' + repo_options.join(' ') + ']' unless repo_options.empty?
repo_info = "#{uri} #{distribution} #{components}\n"
repo_info = "#{repo_options} #{repo_info}" unless repo_options.empty?
repo = "deb #{repo_info}"
repo << "deb-src #{repo_info}" if add_deb_src
repo
end
action :add do
# add key
if new_resource.keyserver && new_resource.key
install_key_from_keyserver(new_resource.key, new_resource.keyserver)
elsif new_resource.key
install_key_from_uri(new_resource.key)
end
file '/var/lib/apt/periodic/update-success-stamp' do
action :nothing
end
execute 'apt-cache gencaches' do
ignore_failure true
action :nothing
end
execute 'apt-get update' do
command "apt-get update -o Dir::Etc::sourcelist='sources.list.d/#{new_resource.name}.list' -o Dir::Etc::sourceparts='-' -o APT::Get::List-Cleanup='0'"
ignore_failure true
action :nothing
notifies :run, 'execute[apt-cache gencaches]', :immediately
end
# build repo file
repository = build_repo(
new_resource.uri,
new_resource.distribution,
new_resource.components,
new_resource.trusted,
new_resource.arch,
new_resource.deb_src
)
file "/etc/apt/sources.list.d/#{new_resource.name}.list" do
owner 'root'
group 'root'
mode 00644
content repository
action :create
notifies :delete, 'file[/var/lib/apt/periodic/update-success-stamp]', :immediately
notifies :run, 'execute[apt-get update]', :immediately if new_resource.cache_rebuild
end
end
action :remove do
if ::File.exists?("/etc/apt/sources.list.d/#{new_resource.name}.list")
Chef::Log.info "Removing #{new_resource.name} repository from /etc/apt/sources.list.d/"
file "/etc/apt/sources.list.d/#{new_resource.name}.list" do
action :delete
end
end
end
@@ -0,0 +1,81 @@
#
# Cookbook Name:: apt
# Recipe:: cacher-client
#
# Copyright 2011-2013 Opscode, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
class ::Chef::Recipe
include ::Apt
end
# remove Acquire::http::Proxy lines from /etc/apt/apt.conf since we use 01proxy
# these are leftover from preseed installs
execute 'Remove proxy from /etc/apt/apt.conf' do
command "sed --in-place '/^Acquire::http::Proxy/d' /etc/apt/apt.conf"
only_if 'grep Acquire::http::Proxy /etc/apt/apt.conf'
end
servers = []
if node['apt']
if node['apt']['cacher_ipaddress']
cacher = Chef::Node.new
cacher.default.name = node['apt']['cacher_ipaddress']
cacher.default.ipaddress = node['apt']['cacher_ipaddress']
cacher.default.apt.cacher_port = node['apt']['cacher_port']
cacher.default.apt_cacher_interface = node['apt']['cacher_interface']
servers << cacher
elsif node['apt']['caching_server']
node.override['apt']['compiletime'] = false
servers << node
end
end
unless Chef::Config[:solo] || servers.length > 0
query = 'apt_caching_server:true'
query += " AND chef_environment:#{node.chef_environment}" if node['apt']['cacher-client']['restrict_environment']
Chef::Log.debug("apt::cacher-client searching for '#{query}'")
servers += search(:node, query)
end
if servers.length > 0
Chef::Log.info("apt-cacher-ng server found on #{servers[0]}.")
if servers[0]['apt']['cacher_interface']
cacher_ipaddress = interface_ipaddress(servers[0], servers[0]['apt']['cacher_interface'])
else
cacher_ipaddress = servers[0].ipaddress
end
t = template '/etc/apt/apt.conf.d/01proxy' do
source '01proxy.erb'
owner 'root'
group 'root'
mode 00644
variables(
:proxy => cacher_ipaddress,
:port => servers[0]['apt']['cacher_port'],
:bypass => node['apt']['cache_bypass']
)
action(node['apt']['compiletime'] ? :nothing : :create)
notifies :run, 'execute[apt-get update]', :immediately
end
t.run_action(:create) if node['apt']['compiletime']
else
Chef::Log.info('No apt-cacher-ng server found.')
file '/etc/apt/apt.conf.d/01proxy' do
action :delete
end
end
include_recipe 'apt::default'
+43
View File
@@ -0,0 +1,43 @@
#
# Cookbook Name:: apt
# Recipe:: cacher-ng
#
# Copyright 2008-2013, Opscode, Inc.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
node.set['apt']['caching_server'] = true
package 'apt-cacher-ng' do
action :install
end
directory node['apt']['cacher_dir'] do
owner 'apt-cacher-ng'
group 'apt-cacher-ng'
mode 0755
end
template '/etc/apt-cacher-ng/acng.conf' do
source 'acng.conf.erb'
owner 'root'
group 'root'
mode 00644
notifies :restart, 'service[apt-cacher-ng]', :immediately
end
service 'apt-cacher-ng' do
supports :restart => true, :status => false
action [:enable, :start]
end
+82
View File
@@ -0,0 +1,82 @@
#
# Cookbook Name:: apt
# Recipe:: default
#
# Copyright 2008-2013, Opscode, Inc.
# Copyright 2009, Bryan McLellan <btm@loftninjas.org>
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# On systems where apt is not installed, the resources in this recipe are not
# executed. However, they _must_ still be present in the resource collection
# or other cookbooks which notify these resources will fail on non-apt-enabled
# systems.
Chef::Log.debug 'apt is not installed. Apt-specific resources will not be executed.' unless apt_installed?
# Run apt-get update to create the stamp file
execute 'apt-get-update' do
command 'apt-get update'
ignore_failure true
only_if { apt_installed? }
not_if { ::File.exists?('/var/lib/apt/periodic/update-success-stamp') }
end
# For other recipes to call to force an update
execute 'apt-get update' do
command 'apt-get update'
ignore_failure true
only_if { apt_installed? }
action :nothing
end
# Automatically remove packages that are no longer needed for dependencies
execute 'apt-get autoremove' do
command 'apt-get -y autoremove'
only_if { apt_installed? }
action :nothing
end
# Automatically remove .deb files for packages no longer on your system
execute 'apt-get autoclean' do
command 'apt-get -y autoclean'
only_if { apt_installed? }
action :nothing
end
# provides /var/lib/apt/periodic/update-success-stamp on apt-get update
package 'update-notifier-common' do
notifies :run, 'execute[apt-get-update]', :immediately
only_if { apt_installed? }
end
execute 'apt-get-update-periodic' do
command 'apt-get update'
ignore_failure true
only_if do
apt_installed? &&
::File.exists?('/var/lib/apt/periodic/update-success-stamp') &&
::File.mtime('/var/lib/apt/periodic/update-success-stamp') < Time.now - node['apt']['periodic_update_min_delay']
end
end
%w{/var/cache/local /var/cache/local/preseeding}.each do |dirname|
directory dirname do
owner 'root'
group 'root'
mode 00755
action :create
only_if { apt_installed? }
end
end
@@ -0,0 +1,32 @@
#
# Cookbook Name:: apt
# Resource:: preference
#
# Copyright 2010-2013, Opscode, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
actions :add, :remove
default_action :add if defined?(default_action) # Chef > 10.8
# Needed for Chef versions < 0.10.10
def initialize(*args)
super
@action = :add
end
attribute :package_name, :kind_of => String, :name_attribute => true
attribute :glob, :kind_of => String
attribute :pin, :kind_of => String
attribute :pin_priority, :kind_of => String
@@ -0,0 +1,43 @@
#
# Cookbook Name:: apt
# Resource:: repository
#
# Copyright 2010-2013, Opscode, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
actions :add, :remove
default_action :add if defined?(default_action) # Chef > 10.8
# Needed for Chef versions < 0.10.10
def initialize(*args)
super
@action = :add
end
# name of the repo, used for source.list filename
attribute :repo_name, :kind_of => String, :name_attribute => true
attribute :uri, :kind_of => String
attribute :distribution, :kind_of => String
attribute :components, :kind_of => Array, :default => []
attribute :arch, :kind_of => String, :default => nil
attribute :trusted, :kind_of => [TrueClass, FalseClass], :default => false
# whether or not to add the repository as a source repo as well
attribute :deb_src, :default => false
attribute :keyserver, :kind_of => String, :default => nil
attribute :key, :kind_of => String, :default => nil
attribute :cookbook, :kind_of => String, :default => nil
# trigger cache rebuild
# If not you can trigger in the recipe itself after checking the status of resource.updated{_by_last_action}?
attribute :cache_rebuild, :kind_of => [TrueClass, FalseClass], :default => true
@@ -0,0 +1,173 @@
# Letter case in directive names does not matter. Must be separated with colons.
# Valid boolean values are a zero number for false, non-zero numbers for true.
CacheDir: <%= node['apt']['cacher_dir'] %>
# set empty to disable logging
LogDir: /var/log/apt-cacher-ng
# TCP (http) port
# Set to 9999 to emulate apt-proxy
Port:<%= node['apt']['cacher_port'] %>
# Addresses or hostnames to listen on. Multiple addresses must be separated by
# spaces. Each entry must be associated with a local interface. DNS resolution
# is performed using getaddrinfo(3) for all available protocols (i.e. IPv4 and
# IPv6 if available).
#
# Default: not set, will listen on all interfaces.
#
# BindAddress: localhost 192.168.7.254 publicNameOnMainInterface
#Proxy: http://www-proxy.example.net:80
#proxy: http://username:proxypassword@proxy.example.net:3128
# Repository remapping. See manual for details.
# In this example, backends file is generated during package installation.
Remap-debrep: file:deb_mirror*.gz /debian ; file:backends_debian
Remap-uburep: file:ubuntu_mirrors /ubuntu ; file:backends_ubuntu
Remap-debvol: file:debvol_mirror*.gz /debian-volatile ; file:backends_debvol
Remap-cygwin: file:cygwin_mirrors /cygwin # ; file:backends_cygwin # incomplete, please create this file
# Virtual page accessible in a web browser to see statistics and status
# information, i.e. under http://localhost:3142/acng-report.html
ReportPage: acng-report.html
# Socket file for accessing through local UNIX socket instead of TCP/IP. Can be
# used with inetd bridge or cron client.
# SocketPath:/var/run/apt-cacher-ng/socket
# Forces log file to be written to disk after every line when set to 1. Default
# is 0, buffer flush happens after client disconnects.
#
# (technically, this is an alias to the Debug option provided for convenience)
#
# UnbufferLogs: 0
# Set to 0 to store only type, time and transfer sizes.
# 1 -> client IP and relative local path are logged too
# VerboseLog: 1
# Don't detach from the console
# ForeGround: 0
# Store the pid of the daemon process therein
# PidFile: /var/run/apt-cacher-ng/pid
# Forbid outgoing connections, work around them or respond with 503 error
# offlinemode:0
# Forbid all downloads that don't run through preconfigured backends (.where)
#ForceManaged: 0
# Days before considering an unreferenced file expired (to be deleted).
# Warning: if the value is set too low and particular index files are not
# available for some days (mirror downtime) there is a risk of deletion of
# still usefull package files.
ExTreshold: 4
# Stop expiration when a critical problem appeared. Currently only failed
# refresh of an index file is considered as critical.
#
# WARNING: don't touch this option or set to a non-zero number.
# Anything else is DANGEROUS and may cause data loss.
#
# ExAbortOnProblems: 1
# Replace some Windows/DOS-FS incompatible chars when storing
# StupidFs: 0
# Experimental feature for apt-listbugs: pass-through SOAP requests and
# responses to/from bugs.debian.org. If not set, default is true if
# ForceManaged is enabled and false otherwise.
# ForwardBtsSoap: 1
# The daemon has a small cache for DNS data, to speed up resolution. The
# expiration time of the DNS entries can be configured in seconds.
# DnsCacheSeconds: 3600
# Don't touch the following values without good consideration!
#
# Max. count of connection threads kept ready (for faster response in the
# future). Should be a sane value between 0 and average number of connections,
# and depend on the amount of spare RAM.
# MaxStandbyConThreads: 8
#
# Hard limit of active thread count for incomming connections, i.e. operation
# is refused when this value is reached (below zero = unlimited).
# MaxConThreads: -1
#
#VfilePattern = (^|.*?/)(Index|Packages\.bz2|Packages\.gz|Packages|Release|Release\.gpg|Sources\.bz2|Sources\.gz|Sources|release|index\.db-.*\.gz|Contents-[^/]*\.gz|pkglist[^/]*\.bz2|rclist[^/]*\.bz2|/meta-release[^/]*|Translation[^/]*\.bz2)$
#PfilePattern = .*(\.deb|\.rpm|\.dsc|\.tar\.gz\.gpg|\.tar\.gz|\.diff\.gz|\.diff\.bz2|\.jigdo|\.template|changelog|copyright|\.udeb|\.diff/.*\.gz|vmlinuz|initrd\.gz|(Devel)?ReleaseAnnouncement(\\?.*)?)$
# Whitelist for expiration, file types not to be removed even when being
# unreferenced. Default: same as VfilePattern which is a safe bed. When and
# only when the only used mirrors are official repositories (with working
# Release files) then it might be set to something more restrictive, like
# (^|.*?/)(Release|Release\.gpg|release|meta-release|Translation[^/]*\.bz2)$
#WfilePattern = (^|.*?/)(Index|Packages\.bz2|Packages\.gz|Packages|Release|Release\.gpg|Sources\.bz2|Sources\.gz|Sources|release|index\.db-.*\.gz|Contents-[^/]*\.gz|pkglist[^/]*\.bz2|rclist[^/]*\.bz2|/meta-release[^/]*|Translation[^/]*\.bz2)$
# Higher modes only working with the debug version
# Warning, writes a lot into apt-cacher.err logfile
# Value overwrites UnbufferLogs setting (aliased)
# Debug:3
# Usually, general purpose proxies like Squid expose the IP adress of the
# client user to the remote server using the X-Forwarded-For HTTP header. This
# behaviour can be optionally turned on with the Expose-Origin option.
# ExposeOrigin: 0
# When logging the originating IP address, trust the information supplied by
# the client in the X-Forwarded-For header.
# LogSubmittedOrigin: 0
# The version string reported to the peer, to be displayed as HTTP client (and
# version) in the logs of the mirror.
# WARNING: some archives use this header to detect/guess capabilities of the
# client (i.e. redirection support) and change the behaviour accordingly, while
# ACNG might not support the expected features. Expect side effects.
#
# UserAgent: Yet Another HTTP Client/1.2.3p4
# In some cases the Import and Expiration tasks might create fresh volatile
# data for internal use by reconstructing them using patch files. This
# by-product might be recompressed with bzip2 and with some luck the resulting
# file becomes identical to the *.bz2 file on the server, usable for APT
# clients trying to fetch the full .bz2 compressed version. Injection of the
# generated files into the cache has however a disadvantage on underpowered
# servers: bzip2 compession can create high load on the server system and the
# visible download of the busy .bz2 files also becomes slower.
#
# RecompBz2: 0
# Network timeout for outgoing connections.
# NetworkTimeout: 60
# Sometimes it makes sense to not store the data in cache and just return the
# package data to client as it comes in. DontCache parameters can enable this
# behaviour for certain URL types. The tokens are extended regular expressions
# that URLs are matched against.
#
# DontCacheRequested is applied to the URL as it comes in from the client.
# Example: exclude packages built with kernel-package for x86
# DontCacheRequested: linux-.*_10\...\.Custo._i386
# Example usecase: exclude popular private IP ranges from caching
# DontCacheRequested: 192.168.0 ^10\..* 172.30
#
# DontCacheResolved is applied to URLs after mapping to the target server. If
# multiple backend servers are specified then it's only matched against the
# download link for the FIRST possible source (due to implementation limits).
# Example usecase: all Ubuntu stuff comes from a local mirror (specified as
# backend), don't cache it again:
# DontCacheResolved: ubuntumirror.local.net
#
# DontCache directive sets (overrides) both, DontCacheResolved and
# DontCacheRequested. Provided for convenience, see those directives for
# details.
#
# Default permission set of freshly created files and directories, as octal
# numbers (see chmod(1) for details).
# Can by limited by the umask value (see umask(2) for details) if it's set in
# the environment of the starting shell, e.g. in apt-cacher-ng init script or
# in its configuration file.
# DirPerms: 00755
# FilePerms: 00664
@@ -0,0 +1,5 @@
Acquire::http::Proxy "http://<%= @proxy %>:<%= @port %>";
Acquire::https::Proxy "DIRECT";
<% @bypass.each do |bypass, type| %>
Acquire::<%= type %>::Proxy::<%= bypass %> "DIRECT";
<% end %>
@@ -0,0 +1,275 @@
# Letter case in directive names does not matter. Must be separated with colons.
# Valid boolean values are a zero number for false, non-zero numbers for true.
CacheDir: <%= node['apt']['cacher_dir'] %>
# set empty to disable logging
LogDir: /var/log/apt-cacher-ng
# place to look for additional configuration and resource files if they are not
# found in the configuration directory
# SupportDir: /usr/lib/apt-cacher-ng
# TCP (http) port
# Set to 9999 to emulate apt-proxy
Port:<%= node['apt']['cacher_port'] %>
# Addresses or hostnames to listen on. Multiple addresses must be separated by
# spaces. Each entry must be an exact local address which is associated with a
# local interface. DNS resolution is performed using getaddrinfo(3) for all
# available protocols (IPv4, IPv6, ...). Using a protocol specific format will
# create binding(s) only on protocol specific socket(s) (e.g. 0.0.0.0 will listen
# only to IPv4).
#
# Default: not set, will listen on all interfaces and protocols
#
# BindAddress: localhost 192.168.7.254 publicNameOnMainInterface
# The specification of another proxy which shall be used for downloads.
# Username and password are, and see manual for limitations.
#
#Proxy: http://www-proxy.example.net:80
#proxy: username:proxypassword@proxy.example.net:3128
# Repository remapping. See manual for details.
# In this example, some backends files might be generated during package
# installation using information collected on the system.
Remap-debrep: file:deb_mirror*.gz /debian ; file:backends_debian # Debian Archives
Remap-uburep: file:ubuntu_mirrors /ubuntu ; file:backends_ubuntu # Ubuntu Archives
Remap-debvol: file:debvol_mirror*.gz /debian-volatile ; file:backends_debvol # Debian Volatile Archives
Remap-cygwin: file:cygwin_mirrors /cygwin # ; file:backends_cygwin # incomplete, please create this file or specify preferred mirrors here
Remap-sfnet: file:sfnet_mirrors # ; file:backends_sfnet # incomplete, please create this file or specify preferred mirrors here
Remap-alxrep: file:archlx_mirrors /archlinux # ; file:backend_archlx # Arch Linux
Remap-fedora: file:fedora_mirrors # Fedora Linux
Remap-epel: file:epel_mirrors # Fedora EPEL
Remap-slrep: file:sl_mirrors # Scientific Linux
# This is usually not needed for security.debian.org because it's always the
# same DNS hostname. However, it might be enabled in order to use hooks,
# ForceManaged mode or special flags in this context.
# Remap-secdeb: security.debian.org
# Virtual page accessible in a web browser to see statistics and status
# information, i.e. under http://localhost:3142/acng-report.html
ReportPage: acng-report.html
# Socket file for accessing through local UNIX socket instead of TCP/IP. Can be
# used with inetd bridge or cron client.
# SocketPath:/var/run/apt-cacher-ng/socket
# Forces log file to be written to disk after every line when set to 1. Default
# is 0, buffers are flushed when the client disconnects.
#
# (technically, alias to the Debug option, see its documentation for details)
#
# UnbufferLogs: 0
# Set to 0 to store only type, time and transfer sizes.
# 1 -> client IP and relative local path are logged too
# VerboseLog: 1
# Don't detach from the console
# ForeGround: 0
# Store the pid of the daemon process therein
# PidFile: /var/run/apt-cacher-ng/pid
# Forbid outgoing connections, work around them or respond with 503 error
# offlinemode:0
# Forbid all downloads that don't run through preconfigured backends (.where)
#ForceManaged: 0
# Days before considering an unreferenced file expired (to be deleted).
# Warning: if the value is set too low and particular index files are not
# available for some days (mirror downtime) there is a risk of deletion of
# still useful package files.
ExTreshold: 4
# Stop expiration when a critical problem appeared. Currently only failed
# refresh of an index file is considered as critical.
#
# WARNING: don't touch this option or set to zero.
# Anything else is DANGEROUS and may cause data loss.
#
# ExAbortOnProblems: 1
# Replace some Windows/DOS-FS incompatible chars when storing
# StupidFs: 0
# Experimental feature for apt-listbugs: pass-through SOAP requests and
# responses to/from bugs.debian.org. If not set, default is true if
# ForceManaged is enabled and false otherwise.
# ForwardBtsSoap: 1
# The daemon has a small cache for DNS data, to speed up resolution. The
# expiration time of the DNS entries can be configured in seconds.
# DnsCacheSeconds: 3600
# Don't touch the following values without good consideration!
#
# Max. count of connection threads kept ready (for faster response in the
# future). Should be a sane value between 0 and average number of connections,
# and depend on the amount of spare RAM.
# MaxStandbyConThreads: 8
#
# Hard limit of active thread count for incoming connections, i.e. operation
# is refused when this value is reached (below zero = unlimited).
# MaxConThreads: -1
#
# Pigeonholing files with regular expressions (static/volatile). Can be
# overriden here but not should not be done permanently because future update
# of default settings would not be applied later.
# VfilePattern = (^|.*?/)(Index|Packages(\.gz|\.bz2|\.lzma|\.xz)?|InRelease|Release|Release\.gpg|Sources(\.gz|\.bz2|\.lzma|\.xz)?|release|index\.db-.*\.gz|Contents-[^/]*(\.gz|\.bz2|\.lzma|\.xz)?|pkglist[^/]*\.bz2|rclist[^/]*\.bz2|/meta-release[^/]*|Translation[^/]*(\.gz|\.bz2|\.lzma|\.xz)?|MD5SUMS|SHA1SUMS|((setup|setup-legacy)(\.ini|\.bz2|\.hint)(\.sig)?)|mirrors\.lst|repo(index|md)\.xml(\.asc|\.key)?|directory\.yast|products|content(\.asc|\.key)?|media|filelists\.xml\.gz|filelists\.sqlite\.bz2|repomd\.xml|packages\.[a-zA-Z][a-zA-Z]\.gz|info\.txt|license\.tar\.gz|license\.zip|.*\.db(\.tar\.gz)?|.*\.files\.tar\.gz|.*\.abs\.tar\.gz|metalink\?repo|.*prestodelta\.xml\.gz)$|/dists/.*/installer-[^/]+/[^0-9][^/]+/images/.*
# PfilePattern = .*(\.d?deb|\.rpm|\.dsc|\.tar(\.gz|\.bz2|\.lzma|\.xz)(\.gpg)?|\.diff(\.gz|\.bz2|\.lzma|\.xz)|\.jigdo|\.template|changelog|copyright|\.udeb|\.debdelta|\.diff/.*\.gz|(Devel)?ReleaseAnnouncement(\?.*)?|[a-f0-9]+-(susedata|updateinfo|primary|deltainfo).xml.gz|fonts/(final/)?[a-z]+32.exe(\?download.*)?|/dists/.*/installer-[^/]+/[0-9][^/]+/images/.*)$
# Whitelist for expiration, file types not to be removed even when being
# unreferenced. Default: many parts from VfilePattern where no parent index
# exists or might be unknown.
# WfilePattern = (^|.*?/)(Release|InRelease|Release\.gpg|(Packages|Sources)(\.gz|\.bz2|\.lzma|\.xz)?|Translation[^/]*(\.gz|\.bz2|\.lzma|\.xz)?|MD5SUMS|SHA1SUMS|.*\.xml|.*\.db\.tar\.gz|.*\.files\.tar\.gz|.*\.abs\.tar\.gz|[a-z]+32.exe)$|/dists/.*/installer-.*/images/.*
# Higher modes only working with the debug version
# Warning, writes a lot into apt-cacher.err logfile
# Value overwrites UnbufferLogs setting (aliased)
# Debug:3
# Usually, general purpose proxies like Squid expose the IP address of the
# client user to the remote server using the X-Forwarded-For HTTP header. This
# behaviour can be optionally turned on with the Expose-Origin option.
# ExposeOrigin: 0
# When logging the originating IP address, trust the information supplied by
# the client in the X-Forwarded-For header.
# LogSubmittedOrigin: 0
# The version string reported to the peer, to be displayed as HTTP client (and
# version) in the logs of the mirror.
# WARNING: some archives use this header to detect/guess capabilities of the
# client (i.e. redirection support) and change the behaviour accordingly, while
# ACNG might not support the expected features. Expect side effects.
#
# UserAgent: Yet Another HTTP Client/1.2.3p4
# In some cases the Import and Expiration tasks might create fresh volatile
# data for internal use by reconstructing them using patch files. This
# by-product might be recompressed with bzip2 and with some luck the resulting
# file becomes identical to the *.bz2 file on the server, usable for APT
# clients trying to fetch the full .bz2 compressed version. Injection of the
# generated files into the cache has however a disadvantage on underpowered
# servers: bzip2 compression can create high load on the server system and the
# visible download of the busy .bz2 files also becomes slower.
#
# RecompBz2: 0
# Network timeout for outgoing connections.
# NetworkTimeout: 60
# Sometimes it makes sense to not store the data in cache and just return the
# package data to client as it comes in. DontCache parameters can enable this
# behaviour for certain URL types. The tokens are extended regular expressions
# that URLs are matched against.
#
# DontCacheRequested is applied to the URL as it comes in from the client.
# Example: exclude packages built with kernel-package for x86
# DontCacheRequested: linux-.*_10\...\.Custo._i386
# Example usecase: exclude popular private IP ranges from caching
# DontCacheRequested: 192.168.0 ^10\..* 172.30
#
# DontCacheResolved is applied to URLs after mapping to the target server. If
# multiple backend servers are specified then it's only matched against the
# download link for the FIRST possible source (due to implementation limits).
# Example usecase: all Ubuntu stuff comes from a local mirror (specified as
# backend), don't cache it again:
# DontCacheResolved: ubuntumirror.local.net
#
# DontCache directive sets (overrides) both, DontCacheResolved and
# DontCacheRequested. Provided for convenience, see those directives for
# details.
#
# Default permission set of freshly created files and directories, as octal
# numbers (see chmod(1) for details).
# Can by limited by the umask value (see umask(2) for details) if it's set in
# the environment of the starting shell, e.g. in apt-cacher-ng init script or
# in its configuration file.
# DirPerms: 00755
# FilePerms: 00664
#
#
# It's possible to use use apt-cacher-ng as a regular web server with limited
# feature set, i.e.
# including directory browsing and download of any file;
# excluding sorting, mime types/encodings, CGI execution, index page
# redirection and other funny things.
# To get this behavior, mappings between virtual directories and real
# directories on the server must be defined with the LocalDirs directive.
# Virtual and real dirs are separated by spaces, multiple pairs are separated
# by semi-colons. Real directories must be absolute paths.
# NOTE: Since the names of that key directories share the same namespace as
# repository names (see Remap-...) it's administrators job to avoid such
# collisions on them (unless created deliberately).
#
# LocalDirs: woo /data/debarchive/woody ; hamm /data/debarchive/hamm
# Precache a set of files referenced by specified index files. This can be used
# to create a partial mirror usable for offline work. There are certain limits
# and restrictions on the path specification, see manual for details. A list of
# (maybe) relevant index files could be retrieved via
# "apt-get --print-uris update" on a client machine.
#
# PrecacheFor: debrep/dists/unstable/*/source/Sources* debrep/dists/unstable/*/binary-amd64/Packages*
# Arbitrary set of data to append to request headers sent over the wire. Should
# be a well formated HTTP headers part including newlines (DOS style) which
# can be entered as escape sequences (\r\n).
# RequestAppendix: X-Tracking-Choice: do-not-track\r\n
# Specifies the IP protocol families to use for remote connections. Order does
# matter, first specified are considered first. Possible combinations:
# v6 v4
# v4 v6
# v6
# v4
# (empty or not set: use system default)
#
# ConnectProto: v6 v4
# Regular expiration algorithm finds package files which are no longer listed
# in any index file and removes them of them after a safety period.
# This option allows to keep more versions of a package in the cache after
# safety period is over.
# KeepExtraVersions: 1
# Optionally uses TCP access control provided by libwrap, see hosts_access(5)
# for details. Daemon name is apt-cacher-ng. Default if not set: decided on
# startup by looking for explicit mentioning of apt-cacher-ng in
# /etc/hosts.allow or /etc/hosts.deny files.
# UseWrap: 0
# If many machines from the same local network attempt to update index files
# (apt-get update) at nearly the same time, the known state of these index file
# is temporarily frozen and multiple requests receive the cached response
# without contacting the server. This parameter (in seconds) specifies the
# length of this period before the files are considered outdated.
# Setting it too low transfers more data and increases remote server load,
# setting it too high (more than a couple of minutes) increases the risk of
# delivering inconsistent responses to the clients.
# FreshIndexMaxAge: 27
# Usually the users are not allowed to specify custom TCP ports of remote
# mirrors in the requests, only the default HTTP port can be used (instead,
# proxy administrator can create Remap- rules with custom ports). This
# restriction can be disabled by specifying a list of allowed ports or 0 for
# any port.
#
# AllowUserPorts: 80
# Normally the HTTP redirection responses are forwarded to the original caller
# (i.e. APT) which starts a new download attempt from the new URL. This
# solution is ok for client configurations with proxy mode but doesn't work
# well with configurations using URL prefixes. To work around this the server
# can restart its own download with another URL. However, this might be used to
# circumvent download source policies by malicious users.
# The RedirMax option specifies how many such redirects the server should
# follow per request, 0 disables the internal redirection. If not set,
# default value is 0 if ForceManaged is used and 5 otherwise.
#
# RedirMax: 5
@@ -0,0 +1,269 @@
# Letter case in directive names does not matter. Must be separated with colons.
# Valid boolean values are a zero number for false, non-zero numbers for true.
CacheDir: <%= node['apt']['cacher_dir'] %>
# set empty to disable logging
LogDir: /var/log/apt-cacher-ng
# place to look for additional configuration and resource files if they are not
# found in the configuration directory
# SupportDir: /usr/lib/apt-cacher-ng
# TCP (http) port
# Set to 9999 to emulate apt-proxy
Port:<%= node['apt']['cacher_port'] %>
# Addresses or hostnames to listen on. Multiple addresses must be separated by
# spaces. Each entry must be an exact local address which is associated with a
# local interface. DNS resolution is performed using getaddrinfo(3) for all
# available protocols (IPv4, IPv6, ...). Using a protocol specific format will
# create binding(s) only on protocol specific socket(s) (e.g. 0.0.0.0 will listen
# only to IPv4).
#
# Default: not set, will listen on all interfaces and protocols
#
# BindAddress: localhost 192.168.7.254 publicNameOnMainInterface
# The specification of another proxy which shall be used for downloads.
# Username and password are, and see manual for limitations.
#
#Proxy: http://www-proxy.example.net:80
#proxy: username:proxypassword@proxy.example.net:3128
# Repository remapping. See manual for details.
# In this example, some backends files might be generated during package
# installation using information collected on the system.
Remap-debrep: file:deb_mirror*.gz /debian ; file:backends_debian # Debian Archives
Remap-uburep: file:ubuntu_mirrors /ubuntu ; file:backends_ubuntu # Ubuntu Archives
Remap-debvol: file:debvol_mirror*.gz /debian-volatile ; file:backends_debvol # Debian Volatile Archives
# This is usually not needed for security.debian.org because it's always the
# same DNS hostname. However, it might be enabled in order to use hooks,
# ForceManaged mode or special flags in this context.
# Remap-secdeb: security.debian.org
# Virtual page accessible in a web browser to see statistics and status
# information, i.e. under http://localhost:3142/acng-report.html
ReportPage: acng-report.html
# Socket file for accessing through local UNIX socket instead of TCP/IP. Can be
# used with inetd bridge or cron client.
# SocketPath:/var/run/apt-cacher-ng/socket
# Forces log file to be written to disk after every line when set to 1. Default
# is 0, buffers are flushed when the client disconnects.
#
# (technically, alias to the Debug option, see its documentation for details)
#
# UnbufferLogs: 0
# Set to 0 to store only type, time and transfer sizes.
# 1 -> client IP and relative local path are logged too
# VerboseLog: 1
# Don't detach from the console
# ForeGround: 0
# Store the pid of the daemon process therein
# PidFile: /var/run/apt-cacher-ng/pid
# Forbid outgoing connections, work around them or respond with 503 error
# offlinemode:0
# Forbid all downloads that don't run through preconfigured backends (.where)
#ForceManaged: 0
# Days before considering an unreferenced file expired (to be deleted).
# Warning: if the value is set too low and particular index files are not
# available for some days (mirror downtime) there is a risk of deletion of
# still useful package files.
ExTreshold: 4
# Stop expiration when a critical problem appeared. Currently only failed
# refresh of an index file is considered as critical.
#
# WARNING: don't touch this option or set to zero.
# Anything else is DANGEROUS and may cause data loss.
#
# ExAbortOnProblems: 1
# Replace some Windows/DOS-FS incompatible chars when storing
# StupidFs: 0
# Experimental feature for apt-listbugs: pass-through SOAP requests and
# responses to/from bugs.debian.org. If not set, default is true if
# ForceManaged is enabled and false otherwise.
# ForwardBtsSoap: 1
# The daemon has a small cache for DNS data, to speed up resolution. The
# expiration time of the DNS entries can be configured in seconds.
# DnsCacheSeconds: 3600
# Don't touch the following values without good consideration!
#
# Max. count of connection threads kept ready (for faster response in the
# future). Should be a sane value between 0 and average number of connections,
# and depend on the amount of spare RAM.
# MaxStandbyConThreads: 8
#
# Hard limit of active thread count for incoming connections, i.e. operation
# is refused when this value is reached (below zero = unlimited).
# MaxConThreads: -1
#
# Pigeonholing files with regular expressions (static/volatile). Can be
# overriden here but not should not be done permanently because future update
# of default settings would not be applied later.
# VfilePattern = (^|.*?/)(Index|Packages(\.gz|\.bz2|\.lzma|\.xz)?|InRelease|Release|Release\.gpg|Sources(\.gz|\.bz2|\.lzma|\.xz)?|release|index\.db-.*\.gz|Contents-[^/]*(\.gz|\.bz2|\.lzma|\.xz)?|pkglist[^/]*\.bz2|rclist[^/]*\.bz2|/meta-release[^/]*|Translation[^/]*(\.gz|\.bz2|\.lzma|\.xz)?|MD5SUMS|SHA1SUMS|((setup|setup-legacy)(\.ini|\.bz2|\.hint)(\.sig)?)|mirrors\.lst|repo(index|md)\.xml(\.asc|\.key)?|directory\.yast|products|content(\.asc|\.key)?|media|filelists\.xml\.gz|filelists\.sqlite\.bz2|repomd\.xml|packages\.[a-zA-Z][a-zA-Z]\.gz|info\.txt|license\.tar\.gz|license\.zip|.*\.db(\.tar\.gz)?|.*\.files\.tar\.gz|.*\.abs\.tar\.gz|metalink\?repo|.*prestodelta\.xml\.gz)$|/dists/.*/installer-[^/]+/[^0-9][^/]+/images/.*
# PfilePattern = .*(\.d?deb|\.rpm|\.dsc|\.tar(\.gz|\.bz2|\.lzma|\.xz)(\.gpg)?|\.diff(\.gz|\.bz2|\.lzma|\.xz)|\.jigdo|\.template|changelog|copyright|\.udeb|\.debdelta|\.diff/.*\.gz|(Devel)?ReleaseAnnouncement(\?.*)?|[a-f0-9]+-(susedata|updateinfo|primary|deltainfo).xml.gz|fonts/(final/)?[a-z]+32.exe(\?download.*)?|/dists/.*/installer-[^/]+/[0-9][^/]+/images/.*)$
# Whitelist for expiration, file types not to be removed even when being
# unreferenced. Default: many parts from VfilePattern where no parent index
# exists or might be unknown.
# WfilePattern = (^|.*?/)(Release|InRelease|Release\.gpg|(Packages|Sources)(\.gz|\.bz2|\.lzma|\.xz)?|Translation[^/]*(\.gz|\.bz2|\.lzma|\.xz)?|MD5SUMS|SHA1SUMS|.*\.xml|.*\.db\.tar\.gz|.*\.files\.tar\.gz|.*\.abs\.tar\.gz|[a-z]+32.exe)$|/dists/.*/installer-.*/images/.*
# Higher modes only working with the debug version
# Warning, writes a lot into apt-cacher.err logfile
# Value overwrites UnbufferLogs setting (aliased)
# Debug:3
# Usually, general purpose proxies like Squid expose the IP address of the
# client user to the remote server using the X-Forwarded-For HTTP header. This
# behaviour can be optionally turned on with the Expose-Origin option.
# ExposeOrigin: 0
# When logging the originating IP address, trust the information supplied by
# the client in the X-Forwarded-For header.
# LogSubmittedOrigin: 0
# The version string reported to the peer, to be displayed as HTTP client (and
# version) in the logs of the mirror.
# WARNING: some archives use this header to detect/guess capabilities of the
# client (i.e. redirection support) and change the behaviour accordingly, while
# ACNG might not support the expected features. Expect side effects.
#
# UserAgent: Yet Another HTTP Client/1.2.3p4
# In some cases the Import and Expiration tasks might create fresh volatile
# data for internal use by reconstructing them using patch files. This
# by-product might be recompressed with bzip2 and with some luck the resulting
# file becomes identical to the *.bz2 file on the server, usable for APT
# clients trying to fetch the full .bz2 compressed version. Injection of the
# generated files into the cache has however a disadvantage on underpowered
# servers: bzip2 compression can create high load on the server system and the
# visible download of the busy .bz2 files also becomes slower.
#
# RecompBz2: 0
# Network timeout for outgoing connections.
# NetworkTimeout: 60
# Sometimes it makes sense to not store the data in cache and just return the
# package data to client as it comes in. DontCache parameters can enable this
# behaviour for certain URL types. The tokens are extended regular expressions
# that URLs are matched against.
#
# DontCacheRequested is applied to the URL as it comes in from the client.
# Example: exclude packages built with kernel-package for x86
# DontCacheRequested: linux-.*_10\...\.Custo._i386
# Example usecase: exclude popular private IP ranges from caching
# DontCacheRequested: 192.168.0 ^10\..* 172.30
#
# DontCacheResolved is applied to URLs after mapping to the target server. If
# multiple backend servers are specified then it's only matched against the
# download link for the FIRST possible source (due to implementation limits).
# Example usecase: all Ubuntu stuff comes from a local mirror (specified as
# backend), don't cache it again:
# DontCacheResolved: ubuntumirror.local.net
#
# DontCache directive sets (overrides) both, DontCacheResolved and
# DontCacheRequested. Provided for convenience, see those directives for
# details.
#
# Default permission set of freshly created files and directories, as octal
# numbers (see chmod(1) for details).
# Can by limited by the umask value (see umask(2) for details) if it's set in
# the environment of the starting shell, e.g. in apt-cacher-ng init script or
# in its configuration file.
# DirPerms: 00755
# FilePerms: 00664
#
#
# It's possible to use use apt-cacher-ng as a regular web server with limited
# feature set, i.e.
# including directory browsing and download of any file;
# excluding sorting, mime types/encodings, CGI execution, index page
# redirection and other funny things.
# To get this behavior, mappings between virtual directories and real
# directories on the server must be defined with the LocalDirs directive.
# Virtual and real dirs are separated by spaces, multiple pairs are separated
# by semi-colons. Real directories must be absolute paths.
# NOTE: Since the names of that key directories share the same namespace as
# repository names (see Remap-...) it's administrators job to avoid such
# collisions on them (unless created deliberately).
#
# LocalDirs: woo /data/debarchive/woody ; hamm /data/debarchive/hamm
# Precache a set of files referenced by specified index files. This can be used
# to create a partial mirror usable for offline work. There are certain limits
# and restrictions on the path specification, see manual for details. A list of
# (maybe) relevant index files could be retrieved via
# "apt-get --print-uris update" on a client machine.
#
# PrecacheFor: debrep/dists/unstable/*/source/Sources* debrep/dists/unstable/*/binary-amd64/Packages*
# Arbitrary set of data to append to request headers sent over the wire. Should
# be a well formated HTTP headers part including newlines (DOS style) which
# can be entered as escape sequences (\r\n).
# RequestAppendix: X-Tracking-Choice: do-not-track\r\n
# Specifies the IP protocol families to use for remote connections. Order does
# matter, first specified are considered first. Possible combinations:
# v6 v4
# v4 v6
# v6
# v4
# (empty or not set: use system default)
#
# ConnectProto: v6 v4
# Regular expiration algorithm finds package files which are no longer listed
# in any index file and removes them of them after a safety period.
# This option allows to keep more versions of a package in the cache after
# safety period is over.
# KeepExtraVersions: 1
# Optionally uses TCP access control provided by libwrap, see hosts_access(5)
# for details. Daemon name is apt-cacher-ng. Default if not set: decided on
# startup by looking for explicit mentioning of apt-cacher-ng in
# /etc/hosts.allow or /etc/hosts.deny files.
# UseWrap: 0
# If many machines from the same local network attempt to update index files
# (apt-get update) at nearly the same time, the known state of these index file
# is temporarily frozen and multiple requests receive the cached response
# without contacting the server. This parameter (in seconds) specifies the
# length of this period before the files are considered outdated.
# Setting it too low transfers more data and increases remote server load,
# setting it too high (more than a couple of minutes) increases the risk of
# delivering inconsistent responses to the clients.
# FreshIndexMaxAge: 27
# Usually the users are not allowed to specify custom TCP ports of remote
# mirrors in the requests, only the default HTTP port can be used (instead,
# proxy administrator can create Remap- rules with custom ports). This
# restriction can be disabled by specifying a list of allowed ports or 0 for
# any port.
#
# AllowUserPorts: 80
# Normally the HTTP redirection responses are forwarded to the original caller
# (i.e. APT) which starts a new download attempt from the new URL. This
# solution is ok for client configurations with proxy mode but doesn't work
# well with configurations using URL prefixes. To work around this the server
# can restart its own download with another URL. However, this might be used to
# circumvent download source policies by malicious users.
# The RedirMax option specifies how many such redirects the server should
# follow per request, 0 disables the internal redirection. If not set,
# default value is 0 if ForceManaged is used and 5 otherwise.
#
# RedirMax: 5
+12
View File
@@ -0,0 +1,12 @@
# CHANGELOG for mongodb
This file is used to list changes made in each version of mongodb.
## 0.1.0:
* Initial release of mongodb
- - -
Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) for help with Markdown.
The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/) describes the differences between markdown on github and standard markdown.
+68
View File
@@ -0,0 +1,68 @@
mongodb Cookbook
================
TODO: Enter the cookbook description here.
e.g.
This cookbook makes your favorite breakfast sandwhich.
Requirements
------------
TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc.
e.g.
#### packages
- `toaster` - mongodb needs toaster to brown your bagel.
Attributes
----------
TODO: List you cookbook attributes here.
e.g.
#### mongodb::default
<table>
<tr>
<th>Key</th>
<th>Type</th>
<th>Description</th>
<th>Default</th>
</tr>
<tr>
<td><tt>['mongodb']['bacon']</tt></td>
<td>Boolean</td>
<td>whether to include bacon</td>
<td><tt>true</tt></td>
</tr>
</table>
Usage
-----
#### mongodb::default
TODO: Write usage instructions for each cookbook.
e.g.
Just include `mongodb` in your node's `run_list`:
```json
{
"name":"my_node",
"run_list": [
"recipe[mongodb]"
]
}
```
Contributing
------------
TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section.
e.g.
1. Fork the repository on Github
2. Create a named feature branch (like `add_component_x`)
3. Write you change
4. Write tests for your change (if applicable)
5. Run the tests, ensuring they all pass
6. Submit a Pull Request using Github
License and Authors
-------------------
Authors: TODO: List authors
+8
View File
@@ -0,0 +1,8 @@
name 'mongodb'
maintainer 'ShareLaTeX'
maintainer_email 'team@sharelatex.com'
license 'AGPLv3'
description 'Installs/Configures mongodb'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version '0.1.0'
depends 'apt'
+19
View File
@@ -0,0 +1,19 @@
#
# Cookbook Name:: mongodb
# Recipe:: default
#
# Copyright 2014, ShareLaTeX
#
# See http://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/
apt_repository 'mongodb-10gen' do
uri 'http://downloads-distro.mongodb.org/repo/ubuntu-upstart'
distribution 'dist'
components ['10gen']
keyserver 'keyserver.ubuntu.com'
key '7F0CEB10'
end
package 'mongodb-10gen' do
action :install
end
+12
View File
@@ -0,0 +1,12 @@
# CHANGELOG for nodejs
This file is used to list changes made in each version of nodejs.
## 0.1.0:
* Initial release of nodejs
- - -
Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) for help with Markdown.
The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/) describes the differences between markdown on github and standard markdown.
+68
View File
@@ -0,0 +1,68 @@
nodejs Cookbook
===============
TODO: Enter the cookbook description here.
e.g.
This cookbook makes your favorite breakfast sandwhich.
Requirements
------------
TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc.
e.g.
#### packages
- `toaster` - nodejs needs toaster to brown your bagel.
Attributes
----------
TODO: List you cookbook attributes here.
e.g.
#### nodejs::default
<table>
<tr>
<th>Key</th>
<th>Type</th>
<th>Description</th>
<th>Default</th>
</tr>
<tr>
<td><tt>['nodejs']['bacon']</tt></td>
<td>Boolean</td>
<td>whether to include bacon</td>
<td><tt>true</tt></td>
</tr>
</table>
Usage
-----
#### nodejs::default
TODO: Write usage instructions for each cookbook.
e.g.
Just include `nodejs` in your node's `run_list`:
```json
{
"name":"my_node",
"run_list": [
"recipe[nodejs]"
]
}
```
Contributing
------------
TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section.
e.g.
1. Fork the repository on Github
2. Create a named feature branch (like `add_component_x`)
3. Write you change
4. Write tests for your change (if applicable)
5. Run the tests, ensuring they all pass
6. Submit a Pull Request using Github
License and Authors
-------------------
Authors: TODO: List authors
+8
View File
@@ -0,0 +1,8 @@
name 'nodejs'
maintainer 'YOUR_COMPANY_NAME'
maintainer_email 'YOUR_EMAIL'
license 'AGPLv3'
description 'Installs/Configures nodejs'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version '0.1.0'
depends 'apt'
+24
View File
@@ -0,0 +1,24 @@
#
# Cookbook Name:: nodejs
# Recipe:: default
#
# Copyright 2014, ShareLaTeX
#
# See https://launchpad.net/~chris-lea/+archive/nodejs
apt_repository 'node.js' do
uri 'http://ppa.launchpad.net/chris-lea/node.js/ubuntu'
distribution node['lsb']['codename']
components ['main']
keyserver 'keyserver.ubuntu.com'
key 'C7917B12'
end
package 'nodejs' do
action :install
end
execute 'install grunt' do
command "npm install -g grunt-cli"
not_if "npm --no-color -g ls 'grunt-cli' 2> /dev/null | grep 'grunt-cli'"
end
+12
View File
@@ -0,0 +1,12 @@
# CHANGELOG for redis
This file is used to list changes made in each version of redis.
## 0.1.0:
* Initial release of redis
- - -
Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) for help with Markdown.
The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/) describes the differences between markdown on github and standard markdown.
+68
View File
@@ -0,0 +1,68 @@
redis Cookbook
==============
TODO: Enter the cookbook description here.
e.g.
This cookbook makes your favorite breakfast sandwhich.
Requirements
------------
TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc.
e.g.
#### packages
- `toaster` - redis needs toaster to brown your bagel.
Attributes
----------
TODO: List you cookbook attributes here.
e.g.
#### redis::default
<table>
<tr>
<th>Key</th>
<th>Type</th>
<th>Description</th>
<th>Default</th>
</tr>
<tr>
<td><tt>['redis']['bacon']</tt></td>
<td>Boolean</td>
<td>whether to include bacon</td>
<td><tt>true</tt></td>
</tr>
</table>
Usage
-----
#### redis::default
TODO: Write usage instructions for each cookbook.
e.g.
Just include `redis` in your node's `run_list`:
```json
{
"name":"my_node",
"run_list": [
"recipe[redis]"
]
}
```
Contributing
------------
TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section.
e.g.
1. Fork the repository on Github
2. Create a named feature branch (like `add_component_x`)
3. Write you change
4. Write tests for your change (if applicable)
5. Run the tests, ensuring they all pass
6. Submit a Pull Request using Github
License and Authors
-------------------
Authors: TODO: List authors
+8
View File
@@ -0,0 +1,8 @@
name 'redis-server'
maintainer 'ShareLaTeX'
maintainer_email 'team@sharelatex.com'
license 'AGPLv3'
description 'Installs/Configures redis-server'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version '0.1.0'
depends 'apt'
@@ -0,0 +1,20 @@
#
# Cookbook Name:: redis
# Recipe:: default
#
# Copyright 2014, ShareLaTeX
#
# See https://launchpad.net/~chris-lea/+archive/redis-server
apt_repository 'redis-server' do
uri 'http://ppa.launchpad.net/chris-lea/redis-server/ubuntu'
distribution node['lsb']['codename']
components ['main']
keyserver 'keyserver.ubuntu.com'
key 'C7917B12'
end
package 'redis-server' do
action :upgrade
options "--force-yes"
end
+12
View File
@@ -0,0 +1,12 @@
# CHANGELOG for sharelatex
This file is used to list changes made in each version of sharelatex.
## 0.1.0:
* Initial release of sharelatex
- - -
Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) for help with Markdown.
The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/) describes the differences between markdown on github and standard markdown.
+68
View File
@@ -0,0 +1,68 @@
sharelatex Cookbook
===================
TODO: Enter the cookbook description here.
e.g.
This cookbook makes your favorite breakfast sandwhich.
Requirements
------------
TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc.
e.g.
#### packages
- `toaster` - sharelatex needs toaster to brown your bagel.
Attributes
----------
TODO: List you cookbook attributes here.
e.g.
#### sharelatex::default
<table>
<tr>
<th>Key</th>
<th>Type</th>
<th>Description</th>
<th>Default</th>
</tr>
<tr>
<td><tt>['sharelatex']['bacon']</tt></td>
<td>Boolean</td>
<td>whether to include bacon</td>
<td><tt>true</tt></td>
</tr>
</table>
Usage
-----
#### sharelatex::default
TODO: Write usage instructions for each cookbook.
e.g.
Just include `sharelatex` in your node's `run_list`:
```json
{
"name":"my_node",
"run_list": [
"recipe[sharelatex]"
]
}
```
Contributing
------------
TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section.
e.g.
1. Fork the repository on Github
2. Create a named feature branch (like `add_component_x`)
3. Write you change
4. Write tests for your change (if applicable)
5. Run the tests, ensuring they all pass
6. Submit a Pull Request using Github
License and Authors
-------------------
Authors: TODO: List authors
+8
View File
@@ -0,0 +1,8 @@
name 'sharelatex'
maintainer 'YOUR_COMPANY_NAME'
maintainer_email 'YOUR_EMAIL'
license 'All rights reserved'
description 'Installs/Configures sharelatex'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version '0.1.0'
depends 'texlive'
+121
View File
@@ -0,0 +1,121 @@
action :start do
package "git"
package "build-essential"
r = new_resource
deploy_to = "/var/www/" + r.name
node_environment = "production"
directory deploy_to do
user r.user if r.user
recursive true
end
env = {
"HOME" => deploy_to
}
directory "#{deploy_to}/releases" do
user r.user if r.user
recursive true
end
shared_dir = "#{deploy_to}/shared"
directory shared_dir do
user r.user if r.user
recursive true
end
directory "#{shared_dir}/config" do
user r.user if r.user
recursive true
end
directory "#{shared_dir}/log" do
user r.user if r.user
recursive true
end
deploy_revision deploy_to do
repository r.repository
revision r.revision
user r.user if r.user
purge_before_symlink [
"log", "config", "node_modules"
]
create_dirs_before_symlink []
symlinks({
"log" => "log",
"config" => "config"
})
symlink_before_migrate({
"node_modules" => "node_modules"
})
environment env
migrate true
migration_command "npm install; grunt install"
before_migrate do
directory "#{deploy_to}/shared/node_modules" do
user r.user if r.user
recursive true
end
end
notifies :restart, "service[#{r.name}]"
end
env = ""
r.environment.each do |key, value|
env += "#{key}=#{value} "
end
file "/etc/init/#{r.name}.conf" do
content <<-EOS
description "#{r.name}"
author "ShareLaTeX <team@sharelatex.com>"
start on started mountall
stop on shutdown
respawn
limit nofile 8192 8192
script
echo $$ > /var/run/#{r.name}.pid
chdir #{deploy_to}/current
exec sudo -u #{r.user} env NODE_ENV=#{node_environment} SHARELATEX_CONFIG=/etc/sharelatex/settings.coffee #{env} node app.js >> log/production.log 2>&1
end script
EOS
notifies :restart, "service[#{r.name}]"
end
directory "/etc/sharelatex"
template "/etc/sharelatex/settings.coffee" do
mode 0400
user "www-data"
notifies :restart, "service[#{r.name}]"
end
service "#{r.name}" do
provider Chef::Provider::Service::Upstart
action :start
end
file "/etc/logrotate.d/#{r.name}" do
content <<-EOS
#{deploy_to}/shared/log/*.log {
rotate 7
size 5M
missingok
compress
copytruncate
}
EOS
end
end
@@ -0,0 +1,47 @@
#
# Cookbook Name:: sharelatex
# Recipe:: default
#
# Copyright 2014, ShareLaTeX
#
# For filestore conversions
package "imagemagick"
package "optipng"
for dir in ["", "compiles", "clsi-cache", "user_files"] do
directory "/var/lib/sharelatex/#{dir}" do
user "www-data"
group "www-data"
recursive true
end
end
sharelatex_app "web-sharelatex" do
repository "https://github.com/sharelatex/web-sharelatex.git"
revision "master"
end
sharelatex_app "document-updater-sharelatex" do
repository "https://github.com/sharelatex/document-updater-sharelatex.git"
revision "master"
end
sharelatex_app "filestore-sharelatex" do
repository "https://github.com/sharelatex/filestore-sharelatex.git"
revision "master"
end
sharelatex_app "track-changes-sharelatex" do
repository "https://github.com/sharelatex/track-changes-sharelatex.git"
revision "master"
end
sharelatex_app "clsi-sharelatex" do
repository "https://github.com/sharelatex/clsi-sharelatex.git"
revision "master"
environment({
"PATH" => "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:#{node[:texlive][:bin_dir]}"
})
end
@@ -0,0 +1,13 @@
actions :start
attribute :revision, :kind_of => String, :default => "master"
attribute :repository, :kind_of => String
attribute :user, :kind_of => String, :default => "www-data"
attribute :group, :kind_of => String, :default => "www-data"
attribute :environment, :kind_of => Hash, :default => {}
def initialize(*args)
super
@action = :start
end
@@ -0,0 +1,268 @@
Path = require('path')
http = require('http')
http.globalAgent.maxSockets = 300
# Make time interval config easier.
seconds = 1000
minutes = 60 * seconds
# These credentials are used for authenticating api requests
# between services that may need to go over public channels
httpAuthUser = "sharelatex"
httpAuthPass = "password"
httpAuthUsers = {}
httpAuthUsers[httpAuthUser] = httpAuthPass
sessionSecret = "secret-please-change"
module.exports =
# File storage
# ------------
#
# ShareLaTeX needs somewhere to store binary files like images.
# There are currently two options:
# Your local filesystem (the default)
# Amazon S3
filestore:
# which backend persistor to use.
# choices are
# s3 - Amazon S3
# fs - local filesystem
backend: "fs"
stores:
# where to store user and template binary files
#
# For Amazon S3 this is the bucket name to store binary files
#
# For local filesystem this is the directory to store the files in.
# This path must exist, not be tmpfs and be writable to by the user sharelatex is run as.
user_files: "/var/lib/sharelatex/user_files"
# Uncomment if you need to configure your S3 credentials
# s3:
# # if you are using S3, then fill in your S3 details below
# key: ""
# secret: ""
# Databases
# ---------
mongo:
url : 'mongodb://127.0.0.1/sharelatex'
redis:
web:
host: "localhost"
port: "6379"
password: ""
api:
host: "localhost"
port: "6379"
password: ""
mysql:
clsi:
database: "clsi"
username: "clsi"
password: ""
dialect: "sqlite"
storage: "/var/lib/sharelatex/clsi.sqlite"
# Service locations
# -----------------
# Configure which ports to run each service on. Generally you
# can leave these as they are unless you have some other services
# running which conflict, or want to run the web process on port 80.
internal:
web:
port: webPort = 3000
host: "localhost"
documentupdater:
port: docUpdaterPort = 3003
host: "localhost"
clsi:
port: clsiPort = 3013
host: "localhost"
filestore:
port: filestorePort = 3009
host: "localhost"
trackchanges:
port: trackchangesPort = 3015
host: "localhost"
# Tell each service where to find the other services. If everything
# is running locally then this is easy, but they exist as separate config
# options incase you want to run some services on remote hosts.
apis:
web:
url: "http://localhost:#{webPort}"
user: httpAuthUser
pass: httpAuthPass
documentupdater:
url : "http://localhost:#{docUpdaterPort}"
clsi:
url: "http://localhost:#{clsiPort}"
filestore:
url: "http://localhost:#{filestorePort}"
trackchanges:
url: "http://localhost:#{trackchangesPort}"
thirdPartyDataStore:
url : "http://localhost:3002"
emptyProjectFlushDelayMiliseconds: 5 * seconds
tags:
url :"http://localhost:3012"
spelling:
url : "http://localhost:3005"
versioning:
snapshotwaitms:3000
url: "http://localhost:4000"
username: httpAuthUser
password: httpAuthPass
recurly:
privateKey: ""
apiKey: ""
subdomain: ""
chat:
url: "http://localhost:3010"
templates:
port: 3007
blog:
port: 3008
templates_api:
url: "http://localhost:3007"
# Where your instance of ShareLaTeX can be found publically. Used in emails
# that are sent out, generated links, etc.
siteUrl : 'http://localhost:3000'
# Same, but with http auth credentials.
httpAuthSiteUrl: 'http://#{httpAuthUser}:#{httpAuthPass}@localhost:3000'
# Security
# --------
security:
sessionSecret: sessionSecret
httpAuthUsers: httpAuthUsers
# Default features
# ----------------
#
# You can select the features that are enabled by default for new
# new users.
defaultFeatures: defaultFeatures =
collaborators: -1
dropbox: true
versioning: true
plans: plans = [{
planCode: "personal"
name: "Personal"
price: 0
features: defaultFeatures
}]
# Spelling languages
# ------------------
#
# You must have the corresponding aspell package installed to
# be able to use a language.
languages: [
{name: "English", code: "en"}
]
# Email support
# -------------
#
# ShareLaTeX uses nodemailer (http://www.nodemailer.com/) to send transactional emails.
# To see the range of transport and options they support, see http://www.nodemailer.com/docs/transports
#email:
# Who should emails be from by default?
# fromAddress: ""
# The default replyTo field, if it should be set
# replyTo: ""
# lifecycle: false
## Example transport and parameter settings for Amazon SES
# transport: "SES"
# parameters:
# AWSAccessKeyID: ""
# AWSSecretKey: ""
# Third party services
# --------------------
#
# ShareLaTeX's regular newsletter is managed by Markdown mail. Add your
# credentials here to integrate with this.
# markdownmail:
# secret: ""
# list_id: ""
#
# Fill in your unique token from various analytics services to enable
# them.
# analytics:
# mixpanel:
# token: ""
# ga:
# token: ""
# heap:
# token: ""
#
# ShareLaTeX's help desk is provided by tenderapp.com
# tenderUrl: ""
#
# Production Settings
# -------------------
# Should javascript assets be served minified or not. Note that you will
# need to run `grunt compile:minify` within the web-sharelatex directory
# to generate these.
useMinifiedJs: false
# Should static assets be sent with a header to tell the browser to cache
# them.
cacheStaticAssets: false
# If you are running ShareLaTeX over https, set this to true to send the
# cookie with a secure flag (recommended).
secureCookie: false
# Internal configs
# ----------------
path:
# If we ever need to write something to disk (e.g. incoming requests
# that need processing but may be too big for memory, then write
# them to disk here).
dumpFolder: Path.resolve "data/dumpFolder"
# Where to write the project to disk before running LaTeX on it
compilesDir: "/var/lib/sharelatex/compiles"
# Where to cache downloaded URLs for the CLSI
clsiCacheDir: "/var/lib/sharelatex/clsi-cache"
# Automatic Snapshots
# -------------------
automaticSnapshots:
# How long should we wait after the user last edited to
# take a snapshot?
waitTimeAfterLastEdit: 5 * minutes
# Even if edits are still taking place, this is maximum
# time to wait before taking another snapshot.
maxTimeBetweenSnapshots: 30 * minutes
# Smoke test
# ----------
# Provide log in credentials and a project to be able to run
# some basic smoke tests to check the core functionality.
#
# smokeTest:
# user: ""
# password: ""
# projectId: ""
# Filestore health check
# ----------------------
# Project and file details to check in filestore when calling /health_check
# health_check:
# project_id: ""
# file_id: ""
+12
View File
@@ -0,0 +1,12 @@
# CHANGELOG for latex
This file is used to list changes made in each version of latex.
## 0.1.0:
* Initial release of latex
- - -
Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) for help with Markdown.
The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/) describes the differences between markdown on github and standard markdown.
+68
View File
@@ -0,0 +1,68 @@
latex Cookbook
==============
TODO: Enter the cookbook description here.
e.g.
This cookbook makes your favorite breakfast sandwhich.
Requirements
------------
TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc.
e.g.
#### packages
- `toaster` - latex needs toaster to brown your bagel.
Attributes
----------
TODO: List you cookbook attributes here.
e.g.
#### latex::default
<table>
<tr>
<th>Key</th>
<th>Type</th>
<th>Description</th>
<th>Default</th>
</tr>
<tr>
<td><tt>['latex']['bacon']</tt></td>
<td>Boolean</td>
<td>whether to include bacon</td>
<td><tt>true</tt></td>
</tr>
</table>
Usage
-----
#### latex::default
TODO: Write usage instructions for each cookbook.
e.g.
Just include `latex` in your node's `run_list`:
```json
{
"name":"my_node",
"run_list": [
"recipe[latex]"
]
}
```
Contributing
------------
TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section.
e.g.
1. Fork the repository on Github
2. Create a named feature branch (like `add_component_x`)
3. Write you change
4. Write tests for your change (if applicable)
5. Run the tests, ensuring they all pass
6. Submit a Pull Request using Github
License and Authors
-------------------
Authors: TODO: List authors
@@ -0,0 +1,2 @@
default[:texlive][:schema] = "small"
default[:texlive][:bin_dir] = "/usr/local/texlive/2013/bin/x86_64-linux"
+7
View File
@@ -0,0 +1,7 @@
name 'texlive'
maintainer 'ShareLaTeX'
maintainer_email 'team@sharelatex.com'
license 'All rights reserved'
description 'Installs/Configures texlive'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version '0.1.0'
+42
View File
@@ -0,0 +1,42 @@
#
# Cookbook Name:: texlive
# Recipe:: default
#
# Copyright 2014, YOUR_COMPANY_NAME
#
# All rights reserved - Do Not Redistribute
#
remote_file "#{Chef::Config[:file_cache_path]}/install-tl-unx.tar.gz" do
source "http://mirror.ctan.org/systems/texlive/tlnet/install-tl-unx.tar.gz"
action :create_if_missing
end
directory "/install-tl-unx"
bash "extract install-tl" do
cwd Chef::Config[:file_cache_path]
code <<-EOH
tar -xvf install-tl-unx.tar.gz -C /install-tl-unx --strip-components=1
EOH
creates "/install-tl-unx/install-tl"
end
file "/install-tl-unx/texlive.profile" do
content "selected_scheme scheme-#{node[:texlive][:schema]}"
end
bash "install texlive" do
cwd "/install-tl-unx"
code <<-EOH
/install-tl-unx/install-tl -profile /install-tl-unx/texlive.profile
EOH
creates "#{node[:texlive][:bin_dir]}/pdflatex"
end
bash "install latexmk" do
environment({
"PATH" => "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:#{node[:texlive][:bin_dir]}"
})
code "tlmgr install latexmk"
creates "#{node[:texlive][:bin_dir]}/latexmk"
end
+275
View File
@@ -0,0 +1,275 @@
Path = require('path')
http = require('http')
http.globalAgent.maxSockets = 300
# Make time interval config easier.
seconds = 1000
minutes = 60 * seconds
# These credentials are used for authenticating api requests
# between services that may need to go over public channels
httpAuthUser = "sharelatex"
httpAuthPass = "password"
httpAuthUsers = {}
httpAuthUsers[httpAuthUser] = httpAuthPass
sessionSecret = "secret-please-change"
module.exports =
# File storage
# ------------
#
# ShareLaTeX needs somewhere to store binary files like images.
# There are currently two options:
# Your local filesystem (the default)
# Amazon S3
filestore:
# which backend persistor to use.
# choices are
# s3 - Amazon S3
# fs - local filesystem
backend: "fs"
stores:
# where to store user and template binary files
#
# For Amazon S3 this is the bucket name to store binary files
#
# For local filesystem this is the directory to store the files in.
# This path must exist, not be tmpfs and be writable to by the user sharelatex is run as.
user_files: Path.resolve(__dirname + "/../user_files")
# Uncomment if you need to configure your S3 credentials
# s3:
# # if you are using S3, then fill in your S3 details below
# key: ""
# secret: ""
# Databases
# ---------
mongo:
url : 'mongodb://127.0.0.1/sharelatex'
redis:
web:
host: "localhost"
port: "6379"
password: ""
api:
host: "localhost"
port: "6379"
password: ""
fairy:
host: "localhost"
port: "6379"
password: ""
mysql:
clsi:
database: "clsi"
username: "clsi"
password: ""
dialect: "sqlite"
storage: Path.resolve(__dirname + "/../db.sqlite")
# Service locations
# -----------------
# Configure which ports to run each service on. Generally you
# can leave these as they are unless you have some other services
# running which conflict, or want to run the web process on port 80.
internal:
web:
port: webPort = 3000
host: "localhost"
documentupdater:
port: docUpdaterPort = 3003
host: "localhost"
clsi:
port: clsiPort = 3013
host: "localhost"
filestore:
port: filestorePort = 3009
host: "localhost"
trackchanges:
port: trackchangesPort = 3015
host: "localhost"
docstore:
port: docstorePort = 3016
host: "localhost"
# Tell each service where to find the other services. If everything
# is running locally then this is easy, but they exist as separate config
# options incase you want to run some services on remote hosts.
apis:
web:
url: "http://localhost:#{webPort}"
user: httpAuthUser
pass: httpAuthPass
documentupdater:
url : "http://localhost:#{docUpdaterPort}"
clsi:
url: "http://localhost:#{clsiPort}"
filestore:
url: "http://localhost:#{filestorePort}"
trackchanges:
url: "http://localhost:#{trackchangesPort}"
docstore:
url: "http://localhost:#{docstorePort}"
thirdPartyDataStore:
url : "http://localhost:3002"
emptyProjectFlushDelayMiliseconds: 5 * seconds
tags:
url :"http://localhost:3012"
spelling:
url : "http://localhost:3005"
versioning:
snapshotwaitms:3000
url: "http://localhost:4000"
username: httpAuthUser
password: httpAuthPass
recurly:
privateKey: ""
apiKey: ""
subdomain: ""
chat:
url: "http://localhost:3010"
templates:
port: 3007
blog:
port: 3008
templates_api:
url: "http://localhost:3007"
# Where your instance of ShareLaTeX can be found publically. Used in emails
# that are sent out, generated links, etc.
siteUrl : 'http://localhost:3000'
# Same, but with http auth credentials.
httpAuthSiteUrl: 'http://#{httpAuthUser}:#{httpAuthPass}@localhost:3000'
# Security
# --------
security:
sessionSecret: sessionSecret
httpAuthUsers: httpAuthUsers
# Default features
# ----------------
#
# You can select the features that are enabled by default for new
# new users.
defaultFeatures: defaultFeatures =
collaborators: -1
dropbox: true
versioning: true
plans: plans = [{
planCode: "personal"
name: "Personal"
price: 0
features: defaultFeatures
}]
# Spelling languages
# ------------------
#
# You must have the corresponding aspell package installed to
# be able to use a language.
languages: [
{name: "English", code: "en"}
]
# Email support
# -------------
#
# ShareLaTeX uses nodemailer (http://www.nodemailer.com/) to send transactional emails.
# To see the range of transport and options they support, see http://www.nodemailer.com/docs/transports
# email:
# fromAddress: ""
# replyTo: ""
# lifecycle: false
# transport: "SES"
# parameters:
# AWSAccessKeyID: ""
# AWSSecretKey: ""
# Third party services
# --------------------
#
# ShareLaTeX's regular newsletter is managed by Markdown mail. Add your
# credentials here to integrate with this.
# markdownmail:
# secret: ""
# list_id: ""
#
# Fill in your unique token from various analytics services to enable
# them.
# analytics:
# mixpanel:
# token: ""
# ga:
# token: ""
# heap:
# token: ""
#
# ShareLaTeX's help desk is provided by tenderapp.com
# tenderUrl: ""
#
# Production Settings
# -------------------
# Should javascript assets be served minified or not. Note that you will
# need to run `grunt compile:minify` within the web-sharelatex directory
# to generate these.
useMinifiedJs: false
# Should static assets be sent with a header to tell the browser to cache
# them.
cacheStaticAssets: false
# If you are running ShareLaTeX over https, set this to true to send the
# cookie with a secure flag (recommended).
secureCookie: false
# Internal configs
# ----------------
path:
# If we ever need to write something to disk (e.g. incoming requests
# that need processing but may be too big for memory, then write
# them to disk here).
dumpFolder: Path.resolve "data/dumpFolder"
# Where to write the project to disk before running LaTeX on it
compilesDir: Path.resolve(__dirname + "/../compiles")
# Where to cache downloaded URLs for the CLSI
clsiCacheDir: Path.resolve(__dirname + "/../cache")
# Automatic Snapshots
# -------------------
automaticSnapshots:
# How long should we wait after the user last edited to
# take a snapshot?
waitTimeAfterLastEdit: 5 * minutes
# Even if edits are still taking place, this is maximum
# time to wait before taking another snapshot.
maxTimeBetweenSnapshots: 30 * minutes
# Smoke test
# ----------
# Provide log in credentials and a project to be able to run
# some basic smoke tests to check the core functionality.
#
# smokeTest:
# user: ""
# password: ""
# projectId: ""
# Filestore health check
# ----------------------
# Project and file details to check in filestore when calling /health_check
# health_check:
# project_id: ""
# file_id: ""
-3
View File
@@ -1,3 +0,0 @@
/compiles/*
!.gitkeep
.env
-76
View File
@@ -1,76 +0,0 @@
# Overleaf Community Edition, development environment
## Building and running
In this `develop` directory, build the services:
```shell
bin/build
```
> [!NOTE]
> If Docker is running out of RAM while building the services in parallel, create a `.env` file in this directory containing `COMPOSE_PARALLEL_LIMIT=1`.
Then start the services:
```shell
bin/up
```
Once the services are running, open <http://localhost/launchpad> to create the first admin account.
## Development
To avoid running `bin/build && bin/up` after every code change, you can run Overleaf
Community Edition in _development mode_, where services will automatically update on code changes.
To do this, use the included `bin/dev` script:
```shell
bin/dev
```
This will start all services using `node --watch`, which will automatically monitor the code and restart the services as necessary.
To improve performance, you can start only a subset of the services in development mode by providing a space-separated list to the `bin/dev` script:
```shell
bin/dev [service1] [service2] ... [serviceN]
```
> [!NOTE]
> Starting the `web` service in _development mode_ will only update the `web`
> service when backend code changes. In order to automatically update frontend
> code as well, make sure to start the `webpack` service in _development mode_
> as well.
If no services are named, all services will start in development mode.
## Debugging
When run in _development mode_ most services expose a debugging port to which
you can attach a debugger such as
[the inspector in Chrome's Dev Tools](chrome://inspect/) or one integrated into
an IDE. The following table shows the port exposed on the **host machine** for
each service:
| Service | Port |
| ------------------ | ---- |
| `web` | 9229 |
| `clsi` | 9230 |
| `chat` | 9231 |
| `docstore` | 9233 |
| `document-updater` | 9234 |
| `filestore` | 9235 |
| `notifications` | 9236 |
| `real-time` | 9237 |
| `history-v1` | 9239 |
| `project-history` | 9240 |
To attach to a service using Chrome's _remote debugging_, go to
<chrome://inspect/> and make sure _Discover network targets_ is checked. Next
click _Configure..._ and add an entry `localhost:[service port]` for each of the
services you want to attach a debugger to.
After adding an entry, the service will show up as a _Remote Target_ that you
can inspect and debug.
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
docker compose build --pull "$@"
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up --no-deps --detach "$@"
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
docker compose down "$@"
-9
View File
@@ -1,9 +0,0 @@
#!/usr/bin/env bash
docker compose logs --follow --tail 10 --no-color "$@" \
| ggrep --line-buffered --invert-match "global.gc" \
| ggrep --line-buffered --invert-match "health.check" \
| ggrep --line-buffered --invert-match "slow event loop" \
| ggrep --line-buffered --invert-match "process.memoryUsage" \
| ggrep --line-buffered --only-matching "[{].*" \
| bunyan --output short
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
docker compose exec -it "$@" /bin/bash
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
docker compose up --detach "$@"
View File
-24
View File
@@ -1,24 +0,0 @@
CHAT_HOST=chat
CLSI_HOST=clsi
DOWNLOAD_HOST=clsi-nginx
DOCSTORE_HOST=docstore
DOCUMENT_UPDATER_HOST=document-updater
FILESTORE_HOST=filestore
GRACEFUL_SHUTDOWN_DELAY_SECONDS=0
HISTORY_V1_HOST=history-v1
HISTORY_REDIS_HOST=redis
LISTEN_ADDRESS=0.0.0.0
MONGO_HOST=mongo
MONGO_URL=mongodb://mongo/sharelatex?directConnection=true
NOTIFICATIONS_HOST=notifications
PROJECT_HISTORY_HOST=project-history
QUEUES_REDIS_HOST=redis
DSMP_REDIS_HOST=redis
REALTIME_HOST=real-time
REDIS_HOST=redis
SESSION_SECRET=foo
V1_HISTORY_HOST=history-v1
WEBPACK_HOST=webpack
WEB_API_PASSWORD=overleaf
WEB_API_USER=overleaf
WEB_HOST=web
-128
View File
@@ -1,128 +0,0 @@
services:
clsi:
command: ["node", "--watch", "app.js"]
environment:
- NODE_OPTIONS=--inspect=0.0.0.0:9229
ports:
- "127.0.0.1:9230:9229"
volumes:
- ../services/clsi/app:/overleaf/services/clsi/app
- ../services/clsi/app.js:/overleaf/services/clsi/app.js
- ../services/clsi/config:/overleaf/services/clsi/config
chat:
command: ["node", "--watch", "app.js"]
environment:
- NODE_OPTIONS=--inspect=0.0.0.0:9229
ports:
- "127.0.0.1:9231:9229"
volumes:
- ../services/chat/app:/overleaf/services/chat/app
- ../services/chat/app.js:/overleaf/services/chat/app.js
- ../services/chat/config:/overleaf/services/chat/config
docstore:
command: ["node", "--watch", "app.js"]
environment:
- NODE_OPTIONS=--inspect=0.0.0.0:9229
ports:
- "127.0.0.1:9233:9229"
volumes:
- ../services/docstore/app:/overleaf/services/docstore/app
- ../services/docstore/app.js:/overleaf/services/docstore/app.js
- ../services/docstore/config:/overleaf/services/docstore/config
document-updater:
command: ["node", "--watch", "app.js"]
environment:
- NODE_OPTIONS=--inspect=0.0.0.0:9229
ports:
- "127.0.0.1:9234:9229"
volumes:
- ../services/document-updater/app:/overleaf/services/document-updater/app
- ../services/document-updater/app.js:/overleaf/services/document-updater/app.js
- ../services/document-updater/config:/overleaf/services/document-updater/config
filestore:
command: ["node", "--watch", "app.js"]
environment:
- NODE_OPTIONS=--inspect=0.0.0.0:9229
ports:
- "127.0.0.1:9235:9229"
volumes:
- ../services/filestore/app:/overleaf/services/filestore/app
- ../services/filestore/app.js:/overleaf/services/filestore/app.js
- ../services/filestore/config:/overleaf/services/filestore/config
history-v1:
command: ["node", "--watch", "app.js"]
environment:
- NODE_OPTIONS=--inspect=0.0.0.0:9229
ports:
- "127.0.0.1:9239:9229"
volumes:
- ../services/history-v1/api:/overleaf/services/history-v1/api
- ../services/history-v1/app.js:/overleaf/services/history-v1/app.js
- ../services/history-v1/config:/overleaf/services/history-v1/config
- ../services/history-v1/storage:/overleaf/services/history-v1/storage
- ../services/history-v1/knexfile.js:/overleaf/services/history-v1/knexfile.js
- ../services/history-v1/migrations:/overleaf/services/history-v1/migrations
notifications:
command: ["node", "--watch", "app.ts"]
environment:
- NODE_OPTIONS=--inspect=0.0.0.0:9229
ports:
- "127.0.0.1:9236:9229"
volumes:
- ../services/notifications/app:/overleaf/services/notifications/app
- ../services/notifications/app.ts:/overleaf/services/notifications/app.ts
- ../services/notifications/config:/overleaf/services/notifications/config
project-history:
command: ["node", "--watch", "app.js"]
environment:
- NODE_OPTIONS=--inspect=0.0.0.0:9229
ports:
- "127.0.0.1:9240:9229"
volumes:
- ../services/project-history/app:/overleaf/services/project-history/app
- ../services/project-history/app.js:/overleaf/services/project-history/app.js
- ../services/project-history/config:/overleaf/services/project-history/config
real-time:
command: ["node", "--watch", "app.js"]
environment:
- NODE_OPTIONS=--inspect=0.0.0.0:9229
ports:
- "127.0.0.1:9237:9229"
volumes:
- ../services/real-time/app:/overleaf/services/real-time/app
- ../services/real-time/app.js:/overleaf/services/real-time/app.js
- ../services/real-time/config:/overleaf/services/real-time/config
web:
command: ["node", "--watch", "app.mjs", "--watch-locales"]
environment:
- NODE_OPTIONS=--inspect=0.0.0.0:9229
ports:
- "127.0.0.1:9229:9229"
volumes:
- ../services/web/app:/overleaf/services/web/app
- ../services/web/app.mjs:/overleaf/services/web/app.mjs
- ../services/web/config:/overleaf/services/web/config
- ../services/web/locales:/overleaf/services/web/locales
- ../services/web/modules:/overleaf/services/web/modules
- ../services/web/public:/overleaf/services/web/public
webpack:
volumes:
- ../services/web/app:/overleaf/services/web/app
- ../services/web/config:/overleaf/services/web/config
- ../services/web/frontend:/overleaf/services/web/frontend
- ../services/web/locales:/overleaf/services/web/locales
- ../services/web/modules:/overleaf/services/web/modules
- ../services/web/public:/overleaf/services/web/public
- ../services/web/transform:/overleaf/services/web/transform
- ../services/web/types:/overleaf/services/web/types
- ../services/web/webpack-plugins:/overleaf/services/web/webpack-plugins
-175
View File
@@ -1,175 +0,0 @@
volumes:
clsi-cache:
filestore-public-files:
filestore-template-files:
filestore-uploads:
filestore-user-files:
mongo-data:
redis-data:
sharelatex-data:
web-data:
history-v1-buckets:
services:
chat:
build:
context: ..
dockerfile: services/chat/Dockerfile
env_file:
- dev.env
clsi:
build:
context: ..
dockerfile: services/clsi/Dockerfile
target: with-texlive
env_file:
- dev.env
environment:
- SANDBOXED_COMPILES=false
user: root
volumes:
- ${PWD}/compiles:/overleaf/services/clsi/compiles
- ${PWD}/output:/overleaf/services/clsi/output
- ${DOCKER_SOCKET_PATH:-/var/run/docker.sock}:/var/run/docker.sock
- clsi-cache:/overleaf/services/clsi/cache
clsi-nginx:
image: nginx:1.28
read_only: true
tmpfs:
- /tmp
- /var/cache/nginx
- /run
volumes:
- ${PWD}/output:/output:ro
- ../services/clsi/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro
docstore:
build:
context: ..
dockerfile: services/docstore/Dockerfile
env_file:
- dev.env
document-updater:
build:
context: ..
dockerfile: services/document-updater/Dockerfile
env_file:
- dev.env
filestore:
build:
context: ..
dockerfile: services/filestore/Dockerfile
env_file:
- dev.env
# environment:
# - ENABLE_CONVERSIONS=true
volumes:
- filestore-public-files:/overleaf/services/filestore/public_files
- filestore-template-files:/overleaf/services/filestore/template_files
- filestore-uploads:/overleaf/services/filestore/uploads
history-v1:
build:
context: ..
dockerfile: services/history-v1/Dockerfile
env_file:
- dev.env
environment:
OVERLEAF_EDITOR_ANALYTICS_BUCKET: "/buckets/analytics"
OVERLEAF_EDITOR_BLOBS_BUCKET: "/buckets/blobs"
OVERLEAF_EDITOR_CHUNKS_BUCKET: "/buckets/chunks"
OVERLEAF_EDITOR_PROJECT_BLOBS_BUCKET: "/buckets/project_blobs"
OVERLEAF_EDITOR_ZIPS_BUCKET: "/buckets/zips"
PERSISTOR_BACKEND: fs
volumes:
- history-v1-buckets:/buckets
mongo:
image: mongo:8
command: --replSet overleaf
ports:
- "127.0.0.1:27017:27017" # for debugging
volumes:
- mongo-data:/data/db
- ../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
environment:
MONGO_INITDB_DATABASE: sharelatex
extra_hosts:
# Required when using the automatic database setup for initializing the
# replica set. This override is not needed when running the setup after
# starting up mongo.
- mongo:127.0.0.1
notifications:
build:
context: ..
dockerfile: services/notifications/Dockerfile
env_file:
- dev.env
project-history:
build:
context: ..
dockerfile: services/project-history/Dockerfile
env_file:
- dev.env
real-time:
build:
context: ..
dockerfile: services/real-time/Dockerfile
env_file:
- dev.env
redis:
image: redis:7
ports:
- "127.0.0.1:6379:6379" # for debugging
volumes:
- redis-data:/data
web:
build:
context: ..
dockerfile: services/web/Dockerfile
target: dev
env_file:
- dev.env
environment:
- APP_NAME=Overleaf Community Edition
- ENABLED_LINKED_FILE_TYPES=project_file,project_output_file
- EMAIL_CONFIRMATION_DISABLED=true
- NODE_ENV=development
- OVERLEAF_ALLOW_PUBLIC_ACCESS=true
command: ["node", "app.mjs"]
volumes:
- sharelatex-data:/var/lib/overleaf
- web-data:/overleaf/services/web/data
depends_on:
- mongo
- redis
- chat
- clsi
- docstore
- document-updater
- filestore
- history-v1
- notifications
- project-history
- real-time
webpack:
build:
context: ..
dockerfile: services/web/Dockerfile
target: webpack
command:
["npx", "webpack", "serve", "--config", "webpack.config.dev-env.js"]
ports:
- "127.0.0.1:80:3808"
volumes:
- ./webpack.config.dev-env.js:/overleaf/services/web/webpack.config.dev-env.js
-23
View File
@@ -1,23 +0,0 @@
const { merge } = require('webpack-merge')
const base = require('./webpack.config.dev')
module.exports = merge(base, {
devServer: {
allowedHosts: 'auto',
devMiddleware: {
index: false,
},
proxy: [
{
context: '/socket.io/**',
target: 'http://real-time:3026',
ws: true,
},
{
context: ['!**/*.js', '!**/*.css', '!**/*.json'],
target: 'http://web:3000',
},
],
},
})
-43
View File
@@ -1,43 +0,0 @@
/**
* Typst syntax highlighting diagnostics.
* Paste into browser dev tools console with a Typst file open.
*/
// ── Part 1: CSS token counts (no view needed) ────────────────────────────
// If all are 0, the language mode is not being applied at all.
console.log('=== Token CSS class counts ===')
;['heading','comment','keyword','string','number',
'variableName','function','emphasis','strong'].forEach(t => {
const n = document.querySelectorAll('.tok-' + t).length
console.log(` .tok-${t}: ${n}`)
})
// ── Part 2: Try to get the parse tree ────────────────────────────────────
// CodeMirror 6 stores DocView on .cm-content; DocView.view = EditorView
const content = document.querySelector('.cm-content')
const view = content?.cmView?.view
if (!view?.state) {
console.warn('Could not find EditorView — parse tree unavailable')
console.log('Keys on .cm-content:', Object.keys(content ?? {}).join(', '))
} else {
console.log('\n=== Parse tree (top 600 chars) ===')
console.log(view.state.tree.toString().slice(0, 600))
// First heading line
const doc = view.state.doc
for (let ln = 1; ln <= Math.min(doc.lines, 25); ln++) {
const line = doc.line(ln)
if (line.text.trimStart().startsWith('=')) {
console.log(`\n=== Nodes on heading line ${ln}: "${line.text}" ===`)
view.state.tree.iterate({
from: line.from, to: line.to,
enter(node) {
const t = doc.sliceString(node.from, node.to)
console.log(` ${node.name}: ${JSON.stringify(t.slice(0, 50))}`)
}
})
break
}
}
}
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 KiB

-23
View File
@@ -1,23 +0,0 @@
version: "2.2"
services:
sharelatex:
ports:
- 30000:30000
- 30150:30150
- 30120:30120
- 30050:30050
- 30420:30420
- 30030:30030
- 30160:30160
- 30360:30360
- 30130:30130
- 30100:30100
- 30540:30540
- 30640:30640
- 40000:40000
# Server Pro
- 30070:30070
- 30400:30400
environment:
DEBUG_NODE: "true"
-149
View File
@@ -1,149 +0,0 @@
services:
sharelatex:
restart: always
# Server Pro users:
# image: quay.io/sharelatex/sharelatex-pro
image: sharelatex/sharelatex
container_name: sharelatex
depends_on:
mongo:
condition: service_healthy
redis:
condition: service_started
ports:
- 80:80
stop_grace_period: 60s
volumes:
- ~/sharelatex_data:/var/lib/overleaf
########################################################################
#### Server Pro: Uncomment the following line to mount the docker ####
#### socket, required for Sibling Containers to work ####
########################################################################
# - /var/run/docker.sock:/var/run/docker.sock
environment:
OVERLEAF_APP_NAME: Overleaf Community Edition
OVERLEAF_MONGO_URL: mongodb://mongo/sharelatex
# Same property, unfortunately with different names in
# different locations
OVERLEAF_REDIS_HOST: redis
REDIS_HOST: redis
ENABLED_LINKED_FILE_TYPES: "project_file,project_output_file"
# Enables Thumbnail generation using ImageMagick
ENABLE_CONVERSIONS: "true"
# Disables email confirmation requirement
EMAIL_CONFIRMATION_DISABLED: "true"
## Set for SSL via nginx-proxy
#VIRTUAL_HOST: 103.112.212.22
# OVERLEAF_SITE_URL: http://overleaf.example.com
# OVERLEAF_NAV_TITLE: Overleaf Community Edition
# OVERLEAF_HEADER_IMAGE_URL: http://example.com/mylogo.png
# OVERLEAF_ADMIN_EMAIL: support@it.com
# OVERLEAF_LEFT_FOOTER: '[{"text": "Another page I want to link to can be found <a href=\"here\">here</a>"} ]'
# OVERLEAF_RIGHT_FOOTER: '[{"text": "Hello I am on the Right"} ]'
# OVERLEAF_EMAIL_FROM_ADDRESS: "hello@example.com"
# OVERLEAF_EMAIL_AWS_SES_ACCESS_KEY_ID:
# OVERLEAF_EMAIL_AWS_SES_SECRET_KEY:
# OVERLEAF_EMAIL_SMTP_HOST: smtp.example.com
# OVERLEAF_EMAIL_SMTP_PORT: 587
# OVERLEAF_EMAIL_SMTP_SECURE: false
# OVERLEAF_EMAIL_SMTP_USER:
# OVERLEAF_EMAIL_SMTP_PASS:
# OVERLEAF_EMAIL_SMTP_TLS_REJECT_UNAUTH: true
# OVERLEAF_EMAIL_SMTP_IGNORE_TLS: false
# OVERLEAF_EMAIL_SMTP_NAME: '127.0.0.1'
# OVERLEAF_EMAIL_SMTP_LOGGER: true
# OVERLEAF_CUSTOM_EMAIL_FOOTER: "This system is run by department x"
# ENABLE_CRON_RESOURCE_DELETION: true
################
## Server Pro ##
################
## The Community Edition is intended for use in environments where all users are trusted and is not appropriate for
## scenarios where isolation of users is required. Sandboxed Compiles are not available in the Community Edition,
## so the following environment variables must be commented out to avoid compile issues.
##
## Sandboxed Compiles: https://docs.overleaf.com/on-premises/configuration/overleaf-toolkit/server-pro-only-configuration/sandboxed-compiles
SANDBOXED_COMPILES: "true"
### Bind-mount source for /var/lib/overleaf/data/compiles inside the container.
SANDBOXED_COMPILES_HOST_DIR_COMPILES: "/home/user/sharelatex_data/data/compiles"
### Bind-mount source for /var/lib/overleaf/data/output inside the container.
SANDBOXED_COMPILES_HOST_DIR_OUTPUT: "/home/user/sharelatex_data/data/output"
### Backwards compatibility (before Server Pro 5.5)
DOCKER_RUNNER: "true"
SANDBOXED_COMPILES_SIBLING_CONTAINERS: "true"
## Works with test LDAP server shown at bottom of docker compose
# OVERLEAF_LDAP_URL: 'ldap://ldap:389'
# OVERLEAF_LDAP_SEARCH_BASE: 'ou=people,dc=planetexpress,dc=com'
# OVERLEAF_LDAP_SEARCH_FILTER: '(uid={{username}})'
# OVERLEAF_LDAP_BIND_DN: 'cn=admin,dc=planetexpress,dc=com'
# OVERLEAF_LDAP_BIND_CREDENTIALS: 'GoodNewsEveryone'
# OVERLEAF_LDAP_EMAIL_ATT: 'mail'
# OVERLEAF_LDAP_NAME_ATT: 'cn'
# OVERLEAF_LDAP_LAST_NAME_ATT: 'sn'
# OVERLEAF_LDAP_UPDATE_USER_DETAILS_ON_LOGIN: 'true'
# OVERLEAF_TEMPLATES_USER_ID: "578773160210479700917ee5"
# OVERLEAF_NEW_PROJECT_TEMPLATE_LINKS: '[ {"name":"All Templates","url":"/templates/all"}]'
# OVERLEAF_PROXY_LEARN: "true"
mongo:
restart: always
image: mongo:8.0
container_name: mongo
command: "--replSet overleaf"
volumes:
- ~/mongo_data:/data/db
- ./bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
environment:
MONGO_INITDB_DATABASE: sharelatex
extra_hosts:
# Required when using the automatic database setup for initializing the replica set.
# This override is not needed when running the setup after starting up mongo.
- mongo:127.0.0.1
healthcheck:
test: echo 'db.stats().ok' | mongosh localhost:27017/test --quiet
interval: 10s
timeout: 10s
retries: 5
redis:
restart: always
image: redis:6.2
container_name: redis
volumes:
- ~/redis_data:/data
# ldap:
# restart: always
# image: rroemhild/test-openldap
# container_name: ldap
# See https://github.com/jwilder/nginx-proxy for documentation on how to configure the nginx-proxy container,
# and https://github.com/overleaf/overleaf/wiki/HTTPS-reverse-proxy-using-Nginx for an example of some recommended
# settings. We recommend using a properly managed nginx instance outside of the Overleaf Server Pro setup,
# but the example here can be used if you'd prefer to run everything with docker-compose
# nginx-proxy:
# image: jwilder/nginx-proxy
# container_name: nginx-proxy
# ports:
# - "80:80"
# - "443:443"
# volumes:
# - /var/run/docker.sock:/tmp/docker.sock:ro
# - /home/overleaf/tmp:/etc/nginx/certs
-18
View File
@@ -1,18 +0,0 @@
FROM cypress/included:15.12.0
ARG USER_UID=1000
ARG USER_GID=1000
# Corepack setup, shared between all the images.
ENV PATH="/overleaf/node_modules/.bin:$PATH"
ENV COREPACK_HOME=/opt/corepack
RUN corepack enable && corepack install -g yarn@4.14.1
ENV COREPACK_ENABLE_NETWORK=0
WORKDIR /overleaf
RUN sed -i s/node:x:1000:/node:x:${USER_GID}:/ /etc/group \
&& sed -i s_node:x:1000:1000::/home/node:/bin/bash_node:x:${USER_UID}:${USER_GID}::/home/node:/bin/bash_ /etc/passwd \
&& chown -R node:node /home/node \
&& chown node:node /overleaf
USER node
-100
View File
@@ -1,100 +0,0 @@
# Design: per-project Python dependencies (cached virtualenv)
Status: **Phase 1 implemented** (gated behind `OVERLEAF_ENABLE_PROJECT_PYTHON_VENV`,
on in the deployment). Network egress policy and venv eviction (Phases 23)
remain. Captures the plan for letting Quarto `{python}` cells use libraries
beyond the curated base set.
## What ships in Phase 1
- A project root `requirements.vrf` is installed into a venv cached by its
sha256, created with `python3 -m venv --system-site-packages`; `QuartoRunner`
points Quarto at it via `QUARTO_PYTHON`. A per-hash `flock` serialises
concurrent builds; pip output is merged into `output.log`; on failure the
render falls back to the base interpreter (and the missing-package message
surfaces). Venvs live under `PYTHON_VENVS_DIR`
(default `/var/lib/overleaf/data/python-venvs`).
- Gated by `userCanInstallPython` (`PythonVenvGate.mjs`) to the project owner +
invited collaborators (any role) — never anonymous / link-sharing users —
threaded to CLSI as `allowPythonInstall` on the editor compile, presentation
export, and publish paths.
### Known Phase-1 limitations
- The first build of a heavy `requirements.vrf` runs within the compile
timeout; a very large install can be killed and retried next compile (the
venv is only marked complete on success).
- No egress restriction yet (Phase 2) — installs reach PyPI directly.
- No eviction yet (Phase 3) — venvs accumulate under `PYTHON_VENVS_DIR`.
## Background
Quarto executes `` ```{python} `` cells through a Jupyter kernel. The base image
([`server-ce/Dockerfile-base`](../server-ce/Dockerfile-base)) bundles a curated
scientific stack (numpy, pandas, scipy, matplotlib, seaborn, scikit-learn,
sympy, plotly, tabulate). Anything outside that set currently fails the render
with `ModuleNotFoundError`.
As a first step that already shipped, the Quarto log parser
([`quarto-log-parser.ts`](../services/web/frontend/js/ide/log-parser/quarto-log-parser.ts))
turns a missing-package traceback into an actionable message. This document is
the *next* step: letting a project declare and install its own dependencies.
**Key constraint:** the instance runs with anonymous read+write enabled
(`OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true`), so compiles can be
triggered by untrusted users. Installing arbitrary packages is therefore a
security decision, not just a convenience.
## Mechanism
1. **Declaration.** A standard `requirements.vrf` at the project root opts the
project in (familiar, Quarto-agnostic, supports version pinning).
2. **Keying.** CLSI hashes `sha256(requirements.vrf + python version)`. The hash
names a venv directory on a **persistent volume**, e.g.
`…/data/python-venvs/<hash>/`. Identical dependency sets share one venv across
projects and compiles.
3. **Build-if-missing.** `python3 -m venv --system-site-packages <dir>` (so the
bundled stack stays visible and only the *extra* deps are installed — smaller
and faster), then `<dir>/bin/pip install -r requirements.vrf`. Guard with a
per-hash `flock` so concurrent compiles don't build the same venv twice.
4. **Point Quarto at it.** Set `QUARTO_PYTHON=<dir>/bin/python3` in the render
environment (threaded web → CLSI exactly like `exportMode`). With
`--system-site-packages`, `ipykernel` from the base is importable, so the
kernel runs in that interpreter with base + project packages.
## Guard rails
- **Auth gating.** Only run the install path for **logged-in owner/collaborator**
compiles. Anonymous-link compiles use the plain base interpreter and never
trigger installs. Web decides and passes a boolean to CLSI; default-deny.
- **Network egress.** The compile environment must reach PyPI to install.
Restrict egress to PyPI / an internal mirror only (k8s NetworkPolicy + pip
`--index-url`), not arbitrary hosts.
- **Resource caps.** Install timeout, venv size cap, max package count; surface
overruns as a clear log error.
- **Trust boundary.** Even gated, a trusted user installing packages is
arbitrary code execution in the sandbox. Containment stays the CLSI container
+ resource limits + egress policy. This is owner-trust-level by design.
## Lifecycle
- **Eviction.** `touch` the venv on use; an LRU cleanup job prunes the oldest
venvs when the volume exceeds a size budget.
- **Failure UX.** pip errors flow into the log panel (reusing the friendly-error
pattern) showing pip's output.
## Rollout
- **Phase 1.** Detection + `flock` venv build + `QUARTO_PYTHON`, behind a
settings flag (default **off**), gated to logged-in owner, dev volume.
- **Phase 2.** Egress NetworkPolicy + index pinning + eviction job.
- **Phase 3.** Nicer pip-error surfacing + a small project-settings UI
affordance.
## Open decisions
- `requirements.vrf` vs a frontmatter field vs both?
- Shared global venv volume vs per-user namespacing (sharing is cheaper;
per-user is stricter isolation)?
- Allow native/compiled wheels (broader support) vs wheels-only/no-build
(tighter security)?

Some files were not shown because too many files have changed in this diff Show More