Add personal Markdown mount CLI
This commit is contained in:
47
README.md
47
README.md
@@ -1,19 +1,17 @@
|
|||||||
# Section 0 Shared Docs -- 0.1.7
|
# Section 0 Shared Docs -- 0.1.8
|
||||||
|
|
||||||
These documents are available in two ways:
|
These documents are available in two ways:
|
||||||
|
|
||||||
1. Read the current source in Git.
|
1. Read the projected copy in AFFiNE.
|
||||||
This repo is the source of truth for the shared Markdown docs.
|
AFFiNE is the shared reading and whiteboard space. Markdown from this repo
|
||||||
|
appears there as a read-only projection.
|
||||||
|
|
||||||
2. Edit them in Git.
|
2. Edit them in Git.
|
||||||
The editable files live in this plain Markdown repo. Use your normal editor,
|
The editable files live in this plain Markdown repo. Use your normal editor,
|
||||||
then commit and push changes.
|
then commit and push changes.
|
||||||
|
|
||||||
If you want to change one of these docs, edit the Markdown file.
|
If you want to change one of these docs, edit the Markdown file. Do not edit the
|
||||||
|
AFFiNE copy; it will be refreshed from Git.
|
||||||
AFFiNE projection is paused while the native writer is rebuilt. Earlier AFFiNE
|
|
||||||
copies may exist, but they should be treated as stale research artifacts rather
|
|
||||||
than the current reading surface.
|
|
||||||
|
|
||||||
## What To Edit
|
## What To Edit
|
||||||
|
|
||||||
@@ -68,9 +66,34 @@ section0-docs push
|
|||||||
section0-docs wait
|
section0-docs wait
|
||||||
```
|
```
|
||||||
|
|
||||||
`section0-docs push` updates Git. `section0-docs wait` reports projection state;
|
`section0-docs push` updates Git. By default it also waits until the projection
|
||||||
for now it should say the AFFiNE writer is frozen. That is expected until the
|
status says the pushed commit has reached AFFiNE. `section0-docs wait` can be
|
||||||
native AFFiNE writer replaces the old direct database experiment.
|
run again any time to check convergence.
|
||||||
|
|
||||||
|
## Personal Markdown Mounts
|
||||||
|
|
||||||
|
Use this when you want to share a selected local Markdown or Obsidian folder
|
||||||
|
without moving custody into the shared Git repo.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
section0-docs auth login
|
||||||
|
section0-docs mount add research ~/Documents/Obsidian/Research
|
||||||
|
section0-docs mount list
|
||||||
|
section0-docs mount plan research
|
||||||
|
section0-docs mount sync research
|
||||||
|
section0-docs mount status research
|
||||||
|
```
|
||||||
|
|
||||||
|
The mount declaration is local to your machine. That is intentional: your
|
||||||
|
personal source files stay under your control. AFFiNE receives a read-only
|
||||||
|
derived copy.
|
||||||
|
|
||||||
|
To stop publishing that folder:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
section0-docs mount unmount research
|
||||||
|
section0-docs mount remove research
|
||||||
|
```
|
||||||
|
|
||||||
## Contributor Setup
|
## Contributor Setup
|
||||||
|
|
||||||
@@ -114,7 +137,7 @@ MESSAGE="MBP sync smoke test" section0-docs commit
|
|||||||
section0-docs push
|
section0-docs push
|
||||||
```
|
```
|
||||||
|
|
||||||
After the AFFiNE copies are refreshed, the smoke line should appear in:
|
After projection completes, the smoke line should appear in:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Agent Workspace / Section 0 / Git Projections / RTA Handbook / RTA Status
|
Agent Workspace / Section 0 / Git Projections / RTA Handbook / RTA Status
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ Flow:
|
|||||||
```text
|
```text
|
||||||
personal Markdown or Obsidian vault
|
personal Markdown or Obsidian vault
|
||||||
-> selected include paths
|
-> selected include paths
|
||||||
-> projection registry
|
-> local mount registry
|
||||||
|
-> authenticated projection request
|
||||||
-> read-only AFFiNE docs
|
-> read-only AFFiNE docs
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -62,6 +63,19 @@ Why this exists:
|
|||||||
- It avoids forcing everyone into one giant shared repo.
|
- It avoids forcing everyone into one giant shared repo.
|
||||||
- It lets people publish useful slices without adopting a new editor.
|
- It lets people publish useful slices without adopting a new editor.
|
||||||
|
|
||||||
|
CLI flow:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
section0-docs auth login
|
||||||
|
section0-docs mount add research ~/Documents/Obsidian/Research
|
||||||
|
section0-docs mount plan research
|
||||||
|
section0-docs mount sync research
|
||||||
|
```
|
||||||
|
|
||||||
|
`mount plan` prints the exact payload before it is sent. `mount status` shows
|
||||||
|
the local declaration and the last sync receipt. `mount unmount` removes the
|
||||||
|
projected AFFiNE docs for that namespace without touching the source folder.
|
||||||
|
|
||||||
## Choosing Between Them
|
## Choosing Between Them
|
||||||
|
|
||||||
Use Git-backed docs when the document itself is a shared artifact.
|
Use Git-backed docs when the document itself is a shared artifact.
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ STATUS_URL="$STATUS_URL"
|
|||||||
SESSION_DIR="\${SECTION0_SESSION_DIR:-\$HOME/.config/section0-docs}"
|
SESSION_DIR="\${SECTION0_SESSION_DIR:-\$HOME/.config/section0-docs}"
|
||||||
SESSION_PATH="\$SESSION_DIR/session.json"
|
SESSION_PATH="\$SESSION_DIR/session.json"
|
||||||
EVENTS_PATH="\$SESSION_DIR/events.jsonl"
|
EVENTS_PATH="\$SESSION_DIR/events.jsonl"
|
||||||
|
MOUNTS_PATH="\$SESSION_DIR/mounts.json"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<USAGE
|
cat <<USAGE
|
||||||
@@ -63,6 +64,20 @@ Commands:
|
|||||||
wait show AFFiNE projection state for a pushed commit
|
wait show AFFiNE projection state for a pushed commit
|
||||||
push-test prove write access with a temporary remote branch
|
push-test prove write access with a temporary remote branch
|
||||||
trace show local/origin state and recent helper events
|
trace show local/origin state and recent helper events
|
||||||
|
mount list
|
||||||
|
list local personal Markdown projections
|
||||||
|
mount add NAME PATH
|
||||||
|
remember a local Markdown folder as a personal read-only projection
|
||||||
|
mount plan NAME
|
||||||
|
print the projection plan that would be sent to the home-lab
|
||||||
|
mount sync NAME
|
||||||
|
send the projection plan to the authenticated home-lab endpoint
|
||||||
|
mount status NAME
|
||||||
|
show the local projection declaration and last sync receipt
|
||||||
|
mount unmount NAME
|
||||||
|
ask the home-lab to remove projected AFFiNE docs for this mount
|
||||||
|
mount remove NAME
|
||||||
|
remove the local projection declaration
|
||||||
help show this help
|
help show this help
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@@ -75,12 +90,13 @@ Examples:
|
|||||||
section0-docs status
|
section0-docs status
|
||||||
section0-docs push-test
|
section0-docs push-test
|
||||||
section0-docs trace
|
section0-docs trace
|
||||||
|
section0-docs mount add research ~/Documents/Obsidian/Research
|
||||||
|
section0-docs mount sync research
|
||||||
MESSAGE="Update concept notes" section0-docs commit
|
MESSAGE="Update concept notes" section0-docs commit
|
||||||
section0-docs push
|
section0-docs push
|
||||||
section0-docs wait
|
section0-docs wait
|
||||||
|
|
||||||
AFFiNE is read-only for these docs. Make lasting changes in Markdown.
|
AFFiNE is read-only for these docs. Make lasting changes in Markdown.
|
||||||
AFFiNE projection is currently paused until the native writer is ready.
|
|
||||||
USAGE
|
USAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,7 +351,9 @@ access=json.loads(sys.argv[2])
|
|||||||
print(json.dumps({
|
print(json.dumps({
|
||||||
"authenticated": True,
|
"authenticated": True,
|
||||||
"serverUrl": sys.argv[3],
|
"serverUrl": sys.argv[3],
|
||||||
|
"accessToken": token.get("accessToken", ""),
|
||||||
"authentik": token.get("authentik", {}),
|
"authentik": token.get("authentik", {}),
|
||||||
|
"affine": token.get("affine", {}),
|
||||||
"author": access.get("author", {}),
|
"author": access.get("author", {}),
|
||||||
"remote": access.get("remote", ""),
|
"remote": access.get("remote", ""),
|
||||||
"credentialUser": access.get("git", {}).get("username", "")
|
"credentialUser": access.get("git", {}).get("username", "")
|
||||||
@@ -348,6 +366,290 @@ print(json.dumps({
|
|||||||
json_log "auth.login.complete" "\$author_email"
|
json_log "auth.login.complete" "\$author_email"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
require_session() {
|
||||||
|
if [ ! -f "\$SESSION_PATH" ]; then
|
||||||
|
echo "not authenticated; run: section0-docs auth login" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
session_value() {
|
||||||
|
require_session
|
||||||
|
json_get "\$1" < "\$SESSION_PATH"
|
||||||
|
}
|
||||||
|
|
||||||
|
mounts_init() {
|
||||||
|
mkdir -p "\$SESSION_DIR"
|
||||||
|
if [ ! -f "\$MOUNTS_PATH" ]; then
|
||||||
|
printf '{\n "mounts": {}\n}\n' > "\$MOUNTS_PATH"
|
||||||
|
chmod 600 "\$MOUNTS_PATH"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
mount_add() {
|
||||||
|
need_python
|
||||||
|
require_session
|
||||||
|
mounts_init
|
||||||
|
name="\${1:-}"
|
||||||
|
source="\${2:-}"
|
||||||
|
[ -n "\$name" ] && [ -n "\$source" ] || {
|
||||||
|
echo "usage: section0-docs mount add NAME PATH" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if [ ! -d "\$source" ]; then
|
||||||
|
echo "mount source is not a directory: \$source" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
abs_source="\$(cd "\$source" && pwd)"
|
||||||
|
username="\$(session_value authentik.username)"
|
||||||
|
[ -n "\$username" ] || username="\$(session_value authentik.email | sed 's/@.*//')"
|
||||||
|
workspace_id="\$(session_value affine.workspaceId)"
|
||||||
|
namespace="mount/\$username/\$name"
|
||||||
|
python3 - "\$MOUNTS_PATH" "\$name" "\$abs_source" "\$namespace" "\$workspace_id" <<'PY'
|
||||||
|
import datetime, json, sys
|
||||||
|
path, name, source, namespace, workspace_id = sys.argv[1:6]
|
||||||
|
now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
||||||
|
with open(path) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
mounts = data.setdefault("mounts", {})
|
||||||
|
mounts[name] = {
|
||||||
|
"name": name,
|
||||||
|
"source": source,
|
||||||
|
"namespace": namespace,
|
||||||
|
"workspaceId": workspace_id,
|
||||||
|
"include": ["**/*.md"],
|
||||||
|
"exclude": [".git/**", ".obsidian/**", ".trash/**", "node_modules/**"],
|
||||||
|
"createdAt": mounts.get(name, {}).get("createdAt") or now,
|
||||||
|
"updatedAt": now,
|
||||||
|
}
|
||||||
|
with open(path, "w") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
f.write("\n")
|
||||||
|
PY
|
||||||
|
chmod 600 "\$MOUNTS_PATH"
|
||||||
|
echo "Mount saved: \$name"
|
||||||
|
echo "Source: \$abs_source"
|
||||||
|
echo "AFFiNE namespace: \$namespace"
|
||||||
|
json_log "mount.add" "\$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
mount_list() {
|
||||||
|
need_python
|
||||||
|
mounts_init
|
||||||
|
python3 - "\$MOUNTS_PATH" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
with open(sys.argv[1]) as f:
|
||||||
|
mounts = json.load(f).get("mounts", {})
|
||||||
|
if not mounts:
|
||||||
|
print("no personal Markdown mounts; add one with: section0-docs mount add NAME PATH")
|
||||||
|
raise SystemExit
|
||||||
|
for name, mount in sorted(mounts.items()):
|
||||||
|
print(f"{name}\t{mount.get('source','')}\t{mount.get('namespace','')}")
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
mount_status() {
|
||||||
|
need_python
|
||||||
|
mounts_init
|
||||||
|
name="\${1:-}"
|
||||||
|
[ -n "\$name" ] || {
|
||||||
|
echo "usage: section0-docs mount status NAME" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
python3 - "\$MOUNTS_PATH" "\$name" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
with open(sys.argv[1]) as f:
|
||||||
|
mount = json.load(f).get("mounts", {}).get(sys.argv[2])
|
||||||
|
if not mount:
|
||||||
|
print(f"mount not found: {sys.argv[2]}", file=sys.stderr)
|
||||||
|
raise SystemExit(1)
|
||||||
|
print(json.dumps(mount, indent=2))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
mount_remove() {
|
||||||
|
need_python
|
||||||
|
mounts_init
|
||||||
|
name="\${1:-}"
|
||||||
|
[ -n "\$name" ] || {
|
||||||
|
echo "usage: section0-docs mount remove NAME" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
python3 - "\$MOUNTS_PATH" "\$name" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
path, name = sys.argv[1:3]
|
||||||
|
with open(path) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
if name not in data.get("mounts", {}):
|
||||||
|
print(f"mount not found: {name}", file=sys.stderr)
|
||||||
|
raise SystemExit(1)
|
||||||
|
del data["mounts"][name]
|
||||||
|
with open(path, "w") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
f.write("\n")
|
||||||
|
PY
|
||||||
|
echo "Removed local mount declaration: \$name"
|
||||||
|
json_log "mount.remove" "\$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
mount_plan() {
|
||||||
|
need_python
|
||||||
|
require_session
|
||||||
|
mounts_init
|
||||||
|
name="\${1:-}"
|
||||||
|
[ -n "\$name" ] || {
|
||||||
|
echo "usage: section0-docs mount plan NAME" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
python3 - "\$MOUNTS_PATH" "\$SESSION_PATH" "\$name" <<'PY'
|
||||||
|
import fnmatch, hashlib, json, os, pathlib, sys
|
||||||
|
mounts_path, session_path, name = sys.argv[1:4]
|
||||||
|
with open(mounts_path) as f:
|
||||||
|
mount = json.load(f).get("mounts", {}).get(name)
|
||||||
|
if not mount:
|
||||||
|
print(f"mount not found: {name}", file=sys.stderr)
|
||||||
|
raise SystemExit(1)
|
||||||
|
with open(session_path) as f:
|
||||||
|
session = json.load(f)
|
||||||
|
source = pathlib.Path(mount["source"])
|
||||||
|
if not source.is_dir():
|
||||||
|
print(f"mount source is not a directory: {source}", file=sys.stderr)
|
||||||
|
raise SystemExit(1)
|
||||||
|
include = mount.get("include") or ["**/*.md"]
|
||||||
|
exclude = mount.get("exclude") or []
|
||||||
|
docs = []
|
||||||
|
def matches(patterns, rel):
|
||||||
|
for pattern in patterns:
|
||||||
|
if fnmatch.fnmatch(rel, pattern):
|
||||||
|
return True
|
||||||
|
if pattern.startswith("**/") and fnmatch.fnmatch(rel, pattern[3:]):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
for path in sorted(source.rglob("*.md")):
|
||||||
|
if not path.is_file():
|
||||||
|
continue
|
||||||
|
rel = path.relative_to(source).as_posix()
|
||||||
|
if not matches(include, rel):
|
||||||
|
continue
|
||||||
|
if any(fnmatch.fnmatch(rel, pattern) or rel.startswith(pattern.removesuffix("/**") + "/") for pattern in exclude):
|
||||||
|
continue
|
||||||
|
markdown = path.read_text(encoding="utf-8")
|
||||||
|
digest = hashlib.sha256(markdown.encode("utf-8")).hexdigest()
|
||||||
|
stable = hashlib.sha256(f"{session.get('authentik', {}).get('subject','')}:{mount['namespace']}:{rel}".encode("utf-8")).hexdigest()[:24]
|
||||||
|
title = path.stem.replace("_", " ").replace("-", " ").strip() or rel
|
||||||
|
docs.append({
|
||||||
|
"stableSourceId": stable,
|
||||||
|
"operation": "update",
|
||||||
|
"sourcePath": rel,
|
||||||
|
"sourceSha256": digest,
|
||||||
|
"title": title,
|
||||||
|
"markdown": markdown,
|
||||||
|
"affine": {
|
||||||
|
"docId": f"obs-{stable}",
|
||||||
|
"namespace": mount["namespace"],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
plan = {
|
||||||
|
"metadata": {
|
||||||
|
"name": name,
|
||||||
|
"sourceModel": "personal-local-markdown",
|
||||||
|
"custody": "local-user",
|
||||||
|
"readOnlyIntent": True,
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"authentik": session.get("authentik", {}),
|
||||||
|
"affine": session.get("affine", {}),
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"workspaceId": mount.get("workspaceId") or session.get("affine", {}).get("workspaceId"),
|
||||||
|
"namespace": mount["namespace"],
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"documents": len(docs),
|
||||||
|
"source": str(source),
|
||||||
|
},
|
||||||
|
"projectedDocuments": docs,
|
||||||
|
}
|
||||||
|
print(json.dumps({"plan": plan}, indent=2))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
mount_sync() {
|
||||||
|
need_python
|
||||||
|
require_session
|
||||||
|
name="\${1:-}"
|
||||||
|
[ -n "\$name" ] || {
|
||||||
|
echo "usage: section0-docs mount sync NAME" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
access_token="\$(session_value accessToken)"
|
||||||
|
[ -n "\$access_token" ] || {
|
||||||
|
echo "saved session has no access token; run: section0-docs auth login" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
tmp_plan="\$(mktemp)"
|
||||||
|
mount_plan "\$name" > "\$tmp_plan"
|
||||||
|
doc_count="\$(python3 -c 'import json,sys; print(len(json.load(open(sys.argv[1]))["plan"].get("projectedDocuments", [])))' "\$tmp_plan")"
|
||||||
|
echo "Submitting personal Markdown projection:"
|
||||||
|
echo " mount: \$name"
|
||||||
|
echo " docs: \$doc_count"
|
||||||
|
response="\$(curl -fsS -X POST -H "Authorization: Bearer \$access_token" -H "Content-Type: application/json" --data-binary "@\$tmp_plan" "\$SERVER_URL/sync")"
|
||||||
|
rm -f "\$tmp_plan"
|
||||||
|
printf "%s\n" "\$response"
|
||||||
|
python3 - "\$MOUNTS_PATH" "\$name" "\$response" <<'PY'
|
||||||
|
import datetime, json, sys
|
||||||
|
path, name, response = sys.argv[1:4]
|
||||||
|
now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
||||||
|
with open(path) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
mount = data.setdefault("mounts", {}).setdefault(name, {"name": name})
|
||||||
|
mount["lastSyncAt"] = now
|
||||||
|
try:
|
||||||
|
mount["lastSync"] = json.loads(response)
|
||||||
|
except Exception:
|
||||||
|
mount["lastSync"] = {"raw": response}
|
||||||
|
with open(path, "w") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
f.write("\n")
|
||||||
|
PY
|
||||||
|
json_log "mount.sync" "\$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
mount_unmount() {
|
||||||
|
need_python
|
||||||
|
require_session
|
||||||
|
mounts_init
|
||||||
|
name="\${1:-}"
|
||||||
|
[ -n "\$name" ] || {
|
||||||
|
echo "usage: section0-docs mount unmount NAME" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
access_token="\$(session_value accessToken)"
|
||||||
|
[ -n "\$access_token" ] || {
|
||||||
|
echo "saved session has no access token; run: section0-docs auth login" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
payload="\$(python3 - "\$MOUNTS_PATH" "\$SESSION_PATH" "\$name" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
with open(sys.argv[1]) as f:
|
||||||
|
mount = json.load(f).get("mounts", {}).get(sys.argv[3])
|
||||||
|
if not mount:
|
||||||
|
print(f"mount not found: {sys.argv[3]}", file=sys.stderr)
|
||||||
|
raise SystemExit(1)
|
||||||
|
with open(sys.argv[2]) as f:
|
||||||
|
session = json.load(f)
|
||||||
|
print(json.dumps({
|
||||||
|
"workspaceId": mount.get("workspaceId") or session.get("affine", {}).get("workspaceId"),
|
||||||
|
"namespace": mount["namespace"],
|
||||||
|
}))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
response="\$(printf "%s" "\$payload" | curl -fsS -X POST -H "Authorization: Bearer \$access_token" -H "Content-Type: application/json" --data-binary @- "\$SERVER_URL/unmount")"
|
||||||
|
printf "%s\n" "\$response"
|
||||||
|
json_log "mount.unmount" "\$name"
|
||||||
|
}
|
||||||
|
|
||||||
case "\${1:-help}" in
|
case "\${1:-help}" in
|
||||||
auth)
|
auth)
|
||||||
case "\${2:-}" in
|
case "\${2:-}" in
|
||||||
@@ -466,6 +768,35 @@ case "\${1:-help}" in
|
|||||||
echo "no events yet"
|
echo "no events yet"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
mount)
|
||||||
|
case "\${2:-}" in
|
||||||
|
list)
|
||||||
|
mount_list
|
||||||
|
;;
|
||||||
|
add)
|
||||||
|
mount_add "\${3:-}" "\${4:-}"
|
||||||
|
;;
|
||||||
|
plan)
|
||||||
|
mount_plan "\${3:-}"
|
||||||
|
;;
|
||||||
|
sync)
|
||||||
|
mount_sync "\${3:-}"
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
mount_status "\${3:-}"
|
||||||
|
;;
|
||||||
|
unmount)
|
||||||
|
mount_unmount "\${3:-}"
|
||||||
|
;;
|
||||||
|
remove)
|
||||||
|
mount_remove "\${3:-}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "usage: section0-docs mount <list|add|plan|sync|status|unmount|remove>" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
help|--help|-h)
|
help|--help|-h)
|
||||||
usage
|
usage
|
||||||
;;
|
;;
|
||||||
|
|||||||
Reference in New Issue
Block a user