diff --git a/book/.gitignore b/book/.gitignore
--- a/book/.gitignore
+++ b/book/.gitignore
diff --git a/book/diagrams/example_flow.excalidraw b/book/diagrams/example_flow.excalidraw
new file mode 100644
--- /dev/null
+++ b/book/diagrams/example_flow.excalidraw
@@ -0,0 +1,907 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "id": "BU7M0am1OF7YXOdJaMUyR",
+ "type": "rectangle",
+ "x": -39.26104726890509,
+ "y": 402.49999999999994,
+ "width": 219,
+ "height": 108.00000000000004,
+ "angle": 0,
+ "strokeColor": "#e03131",
+ "backgroundColor": "#ffc9c9",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1P",
+ "roundness": {
+ "type": 3
+ },
+ "seed": 1659367218,
+ "version": 277,
+ "versionNonce": 48311794,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390017964,
+ "link": null,
+ "locked": false
+ },
+ {
+ "id": "lIKguSTb5wDBx0ltvupax",
+ "type": "arrow",
+ "x": 210.37385725600365,
+ "y": 413.42719343189395,
+ "width": 289.14938459087983,
+ "height": 38.927193431893954,
+ "angle": 0,
+ "strokeColor": "#e03131",
+ "backgroundColor": "#a5d8ff",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1Q",
+ "roundness": {
+ "type": 2
+ },
+ "seed": 1581725938,
+ "version": 619,
+ "versionNonce": 247220466,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390195012,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -73.63490452490873,
+ -38.927193431893954
+ ],
+ [
+ -214.37385725600365,
+ -38.427193431893954
+ ],
+ [
+ -289.14938459087983,
+ -6.764589088379324
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": {
+ "elementId": "4pmmEwP4wj77hMkIB_fVb",
+ "focus": -0.360109088124097,
+ "gap": 10.764435989006659
+ },
+ "endBinding": {
+ "elementId": "zVCqnCRG6fib75dzvUL1I",
+ "focus": -0.042725826557456145,
+ "gap": 11.087884399589388
+ },
+ "startArrowhead": "arrow",
+ "endArrowhead": null,
+ "elbowed": false
+ },
+ {
+ "id": "7qO8V8e4TIHzr8yi6ECOf",
+ "type": "text",
+ "x": 54.763952826462344,
+ "y": 338.5,
+ "width": 11.949999809265137,
+ "height": 35,
+ "angle": 0,
+ "strokeColor": "#e03131",
+ "backgroundColor": "#b2f2bb",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1R",
+ "roundness": null,
+ "seed": 1251429042,
+ "version": 161,
+ "versionNonce": 87102898,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390191298,
+ "link": null,
+ "locked": false,
+ "text": "1",
+ "fontSize": 28,
+ "fontFamily": 5,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "1",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "id": "9Xfi-le1xJ-cd4WWcOUo0",
+ "type": "arrow",
+ "x": 213.2556344920415,
+ "y": 474.48950393245013,
+ "width": 291.0854249794058,
+ "height": 2.014432006777895,
+ "angle": 0,
+ "strokeColor": "#e03131",
+ "backgroundColor": "#ffc9c9",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1S",
+ "roundness": {
+ "type": 2
+ },
+ "seed": 1585390706,
+ "version": 277,
+ "versionNonce": 1511842098,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390070391,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -291.0854249794058,
+ 2.014432006777895
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": null,
+ "endBinding": null,
+ "startArrowhead": "arrow",
+ "endArrowhead": null,
+ "elbowed": false
+ },
+ {
+ "id": "XenjLZwtX7CEIW9DqpQ4T",
+ "type": "arrow",
+ "x": -75.11000655699667,
+ "y": 432.49702119548033,
+ "width": 287.54053966439216,
+ "height": 1.0089141742610082,
+ "angle": 0,
+ "strokeColor": "#e03131",
+ "backgroundColor": "#ffc9c9",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1T",
+ "roundness": {
+ "type": 2
+ },
+ "seed": 1673905714,
+ "version": 307,
+ "versionNonce": 200178734,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390066282,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 287.54053966439216,
+ 1.0089141742610082
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": null,
+ "endBinding": null,
+ "startArrowhead": "arrow",
+ "endArrowhead": null,
+ "elbowed": false
+ },
+ {
+ "id": "6CdD9GoSTA0eAMbVWEKDS",
+ "type": "text",
+ "x": 24.42228708168085,
+ "y": 513.5,
+ "width": 102.63333129882812,
+ "height": 25,
+ "angle": 0,
+ "strokeColor": "#e03131",
+ "backgroundColor": "#ffc9c9",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1U",
+ "roundness": null,
+ "seed": 1397895154,
+ "version": 209,
+ "versionNonce": 1307326130,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390017964,
+ "link": null,
+ "locked": false,
+ "text": "SSH tunnel",
+ "fontSize": 20,
+ "fontFamily": 5,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "SSH tunnel",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "id": "yCJVKg6Z9EnCmYLrmWj2J",
+ "type": "text",
+ "x": 57.54728612800653,
+ "y": 402,
+ "width": 16.383333206176758,
+ "height": 35,
+ "angle": 0,
+ "strokeColor": "#e03131",
+ "backgroundColor": "#b2f2bb",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1V",
+ "roundness": null,
+ "seed": 1898668466,
+ "version": 203,
+ "versionNonce": 5673074,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390017964,
+ "link": null,
+ "locked": false,
+ "text": "4",
+ "fontSize": 28,
+ "fontFamily": 5,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "4",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "id": "OYU8jjk1_iuZEK9q38nxW",
+ "type": "text",
+ "x": 59.08895311256464,
+ "y": 476.5,
+ "width": 17.299999237060547,
+ "height": 35,
+ "angle": 0,
+ "strokeColor": "#e03131",
+ "backgroundColor": "#ffc9c9",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1W",
+ "roundness": null,
+ "seed": 1752718194,
+ "version": 172,
+ "versionNonce": 802140722,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390017964,
+ "link": null,
+ "locked": false,
+ "text": "5",
+ "fontSize": 28,
+ "fontFamily": 5,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "5",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "id": "4pmmEwP4wj77hMkIB_fVb",
+ "type": "rectangle",
+ "x": 221,
+ "y": 387.5,
+ "width": 300,
+ "height": 120.99999999999999,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#ffec99",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1X",
+ "roundness": {
+ "type": 3
+ },
+ "seed": 1081315698,
+ "version": 816,
+ "versionNonce": 1844611954,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "ePp2-mH0FmeRkuvmobDM-"
+ },
+ {
+ "id": "jFGXYZSVS2_EDGOKNI_3l",
+ "type": "arrow"
+ },
+ {
+ "id": "lIKguSTb5wDBx0ltvupax",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1743390184048,
+ "link": null,
+ "locked": false
+ },
+ {
+ "id": "ePp2-mH0FmeRkuvmobDM-",
+ "type": "text",
+ "x": 311.3833351135254,
+ "y": 430.5,
+ "width": 119.23332977294922,
+ "height": 35,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#a5d8ff",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1Y",
+ "roundness": null,
+ "seed": 1185190706,
+ "version": 180,
+ "versionNonce": 224843314,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390024699,
+ "link": null,
+ "locked": false,
+ "text": "Sandhole",
+ "fontSize": 28,
+ "fontFamily": 5,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "4pmmEwP4wj77hMkIB_fVb",
+ "originalText": "Sandhole",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "id": "jFGXYZSVS2_EDGOKNI_3l",
+ "type": "arrow",
+ "x": 507.38272812109227,
+ "y": 409.022182853632,
+ "width": 281.9309852143188,
+ "height": 42.62535315050255,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#a5d8ff",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1Z",
+ "roundness": {
+ "type": 2
+ },
+ "seed": 1791974642,
+ "version": 1380,
+ "versionNonce": 1437682085,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390493976,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -71.38272812109224,
+ -36.388816054115324
+ ],
+ [
+ -190.38272812109224,
+ -36.388816054115324
+ ],
+ [
+ -281.9309852143188,
+ 6.236537096387224
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": {
+ "elementId": "a1fMvw6bGzKY6D2wEFjF3",
+ "focus": 0.5601990672951844,
+ "gap": 8.55128580648237
+ },
+ "endBinding": {
+ "elementId": "4pmmEwP4wj77hMkIB_fVb",
+ "focus": 0,
+ "gap": 4.393657620002837
+ },
+ "startArrowhead": "arrow",
+ "endArrowhead": null,
+ "elbowed": false
+ },
+ {
+ "id": "K_3S0B-Ui6GNkVo5AMm3Z",
+ "type": "text",
+ "x": 365.19999980926514,
+ "y": 334.5,
+ "width": 19.600000381469727,
+ "height": 35,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#a5d8ff",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1a",
+ "roundness": null,
+ "seed": 1518005938,
+ "version": 156,
+ "versionNonce": 1950207214,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390119289,
+ "link": null,
+ "locked": false,
+ "text": "2",
+ "fontSize": 28,
+ "fontFamily": 5,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "2",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "id": "a1fMvw6bGzKY6D2wEFjF3",
+ "type": "rectangle",
+ "x": 510.5,
+ "y": 400.5,
+ "width": 26.000000000000018,
+ "height": 93.00000000000004,
+ "angle": 0,
+ "strokeColor": "#1971c2",
+ "backgroundColor": "#a5d8ff",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1b",
+ "roundness": {
+ "type": 3
+ },
+ "seed": 178580850,
+ "version": 434,
+ "versionNonce": 2066287077,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "id": "jFGXYZSVS2_EDGOKNI_3l",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1743390497811,
+ "link": null,
+ "locked": false
+ },
+ {
+ "id": "uzT8vL-7-z9R2B4FOiRTx",
+ "type": "text",
+ "x": 493.1499996185303,
+ "y": 332.5,
+ "width": 59.70000076293945,
+ "height": 50,
+ "angle": 0,
+ "strokeColor": "#1971c2",
+ "backgroundColor": "#a5d8ff",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1c",
+ "roundness": null,
+ "seed": 2051698482,
+ "version": 166,
+ "versionNonce": 1865462507,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390497811,
+ "link": null,
+ "locked": false,
+ "text": "HTTP\nproxy",
+ "fontSize": 20,
+ "fontFamily": 5,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "HTTP\nproxy",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "id": "zVCqnCRG6fib75dzvUL1I",
+ "type": "rectangle",
+ "x": -269.5,
+ "y": 403.5,
+ "width": 185,
+ "height": 97.00000000000003,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1d",
+ "roundness": {
+ "type": 3
+ },
+ "seed": 1365163698,
+ "version": 1412,
+ "versionNonce": 1817695986,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "pRfrzrggFTJTz-BUO999s"
+ },
+ {
+ "id": "lIKguSTb5wDBx0ltvupax",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1743390184048,
+ "link": null,
+ "locked": false
+ },
+ {
+ "id": "pRfrzrggFTJTz-BUO999s",
+ "type": "text",
+ "x": -252.29166412353516,
+ "y": 427,
+ "width": 150.5833282470703,
+ "height": 50,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1e",
+ "roundness": null,
+ "seed": 70785138,
+ "version": 779,
+ "versionNonce": 1777534450,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390046660,
+ "link": null,
+ "locked": false,
+ "text": "Client A\n(HTTP service)",
+ "fontSize": 20,
+ "fontFamily": 5,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "zVCqnCRG6fib75dzvUL1I",
+ "originalText": "Client A\n(HTTP service)",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "id": "AzeVD0HCBmQ_YdzKkTAtj",
+ "type": "rectangle",
+ "x": 651.5,
+ "y": 399.5,
+ "width": 185,
+ "height": 97.00000000000003,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1l",
+ "roundness": {
+ "type": 3
+ },
+ "seed": 765278766,
+ "version": 1585,
+ "versionNonce": 9799022,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "T2yp443rMJNLEEU19eKLN"
+ }
+ ],
+ "updated": 1743390103391,
+ "link": null,
+ "locked": false
+ },
+ {
+ "id": "T2yp443rMJNLEEU19eKLN",
+ "type": "text",
+ "x": 675.5833358764648,
+ "y": 423,
+ "width": 136.8333282470703,
+ "height": 50,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1m",
+ "roundness": null,
+ "seed": 781304942,
+ "version": 954,
+ "versionNonce": 1792976814,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390103391,
+ "link": null,
+ "locked": false,
+ "text": "Client B\n(Web browser)",
+ "fontSize": 20,
+ "fontFamily": 5,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "AzeVD0HCBmQ_YdzKkTAtj",
+ "originalText": "Client B\n(Web browser)",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "id": "GHivxhyyZK0M12XKN36WS",
+ "type": "arrow",
+ "x": 546.4084386254126,
+ "y": 418.63127836235196,
+ "width": 99.40457544251265,
+ "height": 0.30912525563323356,
+ "angle": 0,
+ "strokeColor": "#1971c2",
+ "backgroundColor": "#a5d8ff",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1n",
+ "roundness": null,
+ "seed": 1545514098,
+ "version": 166,
+ "versionNonce": 2060483339,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390497401,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 99.40457544251265,
+ 0.30912525563323356
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": null,
+ "endBinding": null,
+ "startArrowhead": "arrow",
+ "endArrowhead": null,
+ "elbowed": false
+ },
+ {
+ "id": "1X6El9wlKn69JnUl407j3",
+ "type": "arrow",
+ "x": 643.1893286889688,
+ "y": 472.67548869085056,
+ "width": 97.00234275689405,
+ "height": 0.977916056804844,
+ "angle": 0,
+ "strokeColor": "#1971c2",
+ "backgroundColor": "#a5d8ff",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1o",
+ "roundness": null,
+ "seed": 338972210,
+ "version": 262,
+ "versionNonce": 886099749,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390497401,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -97.00234275689405,
+ 0.977916056804844
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": null,
+ "endBinding": null,
+ "startArrowhead": "arrow",
+ "endArrowhead": null,
+ "elbowed": false
+ },
+ {
+ "id": "c1J4xxMGPHjAKEOpav-iO",
+ "type": "text",
+ "x": 590.194000151021,
+ "y": 386.5,
+ "width": 17.016666412353516,
+ "height": 35,
+ "angle": 0,
+ "strokeColor": "#1971c2",
+ "backgroundColor": "#a5d8ff",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1p",
+ "roundness": null,
+ "seed": 78624754,
+ "version": 123,
+ "versionNonce": 1489166763,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390497401,
+ "link": null,
+ "locked": false,
+ "text": "3",
+ "fontSize": 28,
+ "fontFamily": 5,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "3",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "id": "K3tXRE3TkHFHVnTcFxbEk",
+ "type": "text",
+ "x": 589.7440003417559,
+ "y": 474.5,
+ "width": 17.91666603088379,
+ "height": 35,
+ "angle": 0,
+ "strokeColor": "#1971c2",
+ "backgroundColor": "#a5d8ff",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "b1q",
+ "roundness": null,
+ "seed": 764930482,
+ "version": 147,
+ "versionNonce": 848667269,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743390497401,
+ "link": null,
+ "locked": false,
+ "text": "6",
+ "fontSize": 28,
+ "fontFamily": 5,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "6",
+ "autoResize": true,
+ "lineHeight": 1.25
+ }
+ ],
+ "appState": {
+ "gridSize": 20,
+ "gridStep": 5,
+ "gridModeEnabled": false,
+ "viewBackgroundColor": "#ffffff"
+ },
+ "files": {}
+}
\ No newline at end of file
diff --git a/book/diagrams/how_it_works.excalidraw b/book/diagrams/how_it_works.excalidraw
--- a/book/diagrams/how_it_works.excalidraw
+++ b/book/diagrams/how_it_works.excalidraw
@@ -28,7 +28,7 @@
"version": 243,
"versionNonce": 539285067,
"isDeleted": false,
- "boundElements": null,
+ "boundElements": [],
"updated": 1743364407315,
"link": null,
"locked": false
@@ -36,8 +36,8 @@
{
"id": "XXkcphDBkS-7gjSOujTUm",
"type": "rectangle",
- "x": 222,
- "y": 119,
+ "x": 255,
+ "y": 124,
"width": 256,
"height": 246.00000000000003,
"angle": 0,
@@ -55,19 +55,19 @@
"type": 3
},
"seed": 1114775845,
- "version": 410,
- "versionNonce": 1916450635,
+ "version": 479,
+ "versionNonce": 1131296638,
"isDeleted": false,
"boundElements": [],
- "updated": 1743363899946,
+ "updated": 1743388038798,
"link": null,
"locked": false
},
{
"id": "fOMZ3bas6KENlE-dMH_59",
"type": "diamond",
- "x": 206,
- "y": 162,
+ "x": 239,
+ "y": 167,
"width": 29,
"height": 46,
"angle": 0,
@@ -85,8 +85,8 @@
"type": 2
},
"seed": 637289771,
- "version": 142,
- "versionNonce": 1444210539,
+ "version": 211,
+ "versionNonce": 569808830,
"isDeleted": false,
"boundElements": [
{
@@ -98,15 +98,15 @@
"type": "arrow"
}
],
- "updated": 1743364194943,
+ "updated": 1743388038798,
"link": null,
"locked": false
},
{
"id": "R2V0dILx8W4I1WDCDR0ML",
"type": "diamond",
- "x": 206.5,
- "y": 219,
+ "x": 239.5,
+ "y": 224,
"width": 29,
"height": 46,
"angle": 0,
@@ -124,19 +124,19 @@
"type": 2
},
"seed": 1034867275,
- "version": 161,
- "versionNonce": 663273157,
+ "version": 230,
+ "versionNonce": 1151746110,
"isDeleted": false,
"boundElements": [],
- "updated": 1743364329345,
+ "updated": 1743388038798,
"link": null,
"locked": false
},
{
"id": "qSW7cdA88CRCn0oq6Upcd",
"type": "diamond",
- "x": 206.5,
- "y": 277,
+ "x": 239.5,
+ "y": 282,
"width": 29,
"height": 46,
"angle": 0,
@@ -154,19 +154,24 @@
"type": 2
},
"seed": 1245198757,
- "version": 200,
- "versionNonce": 743841195,
+ "version": 270,
+ "versionNonce": 488186238,
"isDeleted": false,
- "boundElements": [],
- "updated": 1743364323546,
+ "boundElements": [
+ {
+ "id": "F8S5vF78PzGEI884_BgcW",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1743388072415,
"link": null,
"locked": false
},
{
"id": "XgpsNQlUwNjurkNauYGbk",
"type": "text",
- "x": 239.01666641235352,
- "y": 179.5,
+ "x": 272.0166664123535,
+ "y": 184.5,
"width": 97.96666717529297,
"height": 20,
"angle": 0,
@@ -182,11 +187,11 @@
"index": "b0e",
"roundness": null,
"seed": 1706703717,
- "version": 161,
- "versionNonce": 1564078597,
+ "version": 230,
+ "versionNonce": 619038910,
"isDeleted": false,
- "boundElements": null,
- "updated": 1743363740416,
+ "boundElements": [],
+ "updated": 1743388038798,
"link": null,
"locked": false,
"text": ":2222 (SSH)",
@@ -202,8 +207,8 @@
{
"id": "zv9gyzcfTx9jlQfuEy4vq",
"type": "text",
- "x": 238.66666793823242,
- "y": 231,
+ "x": 271.6666679382324,
+ "y": 236,
"width": 92.66666412353516,
"height": 20,
"angle": 0,
@@ -219,11 +224,11 @@
"index": "b0f",
"roundness": null,
"seed": 439688805,
- "version": 119,
- "versionNonce": 1593996133,
+ "version": 188,
+ "versionNonce": 686424318,
"isDeleted": false,
- "boundElements": null,
- "updated": 1743364328903,
+ "boundElements": [],
+ "updated": 1743388038798,
"link": null,
"locked": false,
"text": ":80 (HTTP)",
@@ -239,8 +244,8 @@
{
"id": "BGUs894bGmKV8xcDAt8Vk",
"type": "text",
- "x": 239.85833358764648,
- "y": 290,
+ "x": 272.8583335876465,
+ "y": 295,
"width": 110.28333282470703,
"height": 20,
"angle": 0,
@@ -256,11 +261,11 @@
"index": "b0g",
"roundness": null,
"seed": 553622213,
- "version": 163,
- "versionNonce": 1931336075,
+ "version": 232,
+ "versionNonce": 1211969854,
"isDeleted": false,
"boundElements": [],
- "updated": 1743364327101,
+ "updated": 1743388038798,
"link": null,
"locked": false,
"text": ":443 (HTTPS)",
@@ -319,8 +324,8 @@
{
"id": "UGmoxHwIZcIESL4nYDRZ0",
"type": "text",
- "x": 284.93333435058594,
- "y": 86.5,
+ "x": 317.93333435058594,
+ "y": 91.5,
"width": 119.23332977294922,
"height": 35,
"angle": 0,
@@ -336,11 +341,11 @@
"index": "b0i",
"roundness": null,
"seed": 594181029,
- "version": 127,
- "versionNonce": 44731621,
+ "version": 196,
+ "versionNonce": 775969150,
"isDeleted": false,
- "boundElements": null,
- "updated": 1743363902288,
+ "boundElements": [],
+ "updated": 1743388038798,
"link": null,
"locked": false,
"text": "Sandhole",
@@ -393,10 +398,10 @@
{
"id": "_K0Xwx0HT9zD0iH-JIfy7",
"type": "arrow",
- "x": 585.9410490072286,
- "y": 65.23493367044395,
- "width": 379.10066994380827,
- "height": 100.96548708305139,
+ "x": 583.9410490072286,
+ "y": 65.23493367044397,
+ "width": 391.10066994380827,
+ "height": 109.76506632955603,
"angle": 0,
"strokeColor": "#e03131",
"backgroundColor": "transparent",
@@ -410,11 +415,11 @@
"index": "b0k",
"roundness": null,
"seed": 1864090149,
- "version": 1882,
- "versionNonce": 1086209541,
+ "version": 2265,
+ "versionNonce": 9421950,
"isDeleted": false,
- "boundElements": null,
- "updated": 1743365411262,
+ "boundElements": [],
+ "updated": 1743388118195,
"link": null,
"locked": false,
"points": [
@@ -423,24 +428,28 @@
0
],
[
- -379.10066994380827,
- 2.7582426159375473
+ -391.10066994380827,
+ 3.758242615937533
],
[
- -369.79514971545984,
- 100.96548708305139
+ -390.94104900722857,
+ 109.76506632955603
+ ],
+ [
+ -343.10515506358695,
+ 109.67519508083019
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "WmSxpGHH-CA1ShvBKpsp7",
- "focus": 0.5487188693058546,
- "gap": 6.936945256178183
+ "focus": 0.5513666898717178,
+ "gap": 8.095957612813846
},
"endBinding": {
"elementId": "fOMZ3bas6KENlE-dMH_59",
- "focus": -0.17743221391829114,
- "gap": 3.4789792562721287
+ "focus": 0.6571158183600897,
+ "gap": 6.97077975607103
},
"startArrowhead": "arrow",
"endArrowhead": "arrow",
@@ -512,7 +521,7 @@
"version": 73,
"versionNonce": 1790515787,
"isDeleted": false,
- "boundElements": null,
+ "boundElements": [],
"updated": 1743364054746,
"link": null,
"locked": false,
@@ -672,10 +681,10 @@
{
"id": "ka8QAs1UibuFb3THMMsjX",
"type": "arrow",
- "x": 204.2503866850818,
- "y": 185.8400838233369,
- "width": 362.4719025774192,
- "height": 142.8400838233369,
+ "x": 242.2503866850818,
+ "y": 204.8400838233369,
+ "width": 395.4719025774192,
+ "height": 161.8400838233369,
"angle": 0,
"strokeColor": "#e03131",
"backgroundColor": "transparent",
@@ -689,11 +698,11 @@
"index": "b0r",
"roundness": null,
"seed": 1883731429,
- "version": 3252,
- "versionNonce": 1131211019,
+ "version": 3392,
+ "versionNonce": 475447486,
"isDeleted": false,
"boundElements": [],
- "updated": 1743365414429,
+ "updated": 1743388109592,
"link": null,
"locked": false,
"points": [
@@ -702,28 +711,28 @@
0
],
[
- -180.80072290276902,
- -0.599259518709431
+ -211.80072290276902,
+ -2.599259518709431
],
[
- -186.2503866850818,
- -142.8400838233369
+ -214.2503866850818,
+ -161.8400838233369
],
[
- -362.4719025774192,
- -142.24201710021367
+ -395.4719025774192,
+ -161.24201710021367
]
],
"lastCommittedPoint": null,
"startBinding": {
- "elementId": "EoBCO4fe_fWGHsIfZWNO9",
- "focus": 0.2649254680496929,
- "gap": 22.25038668508182
+ "elementId": "fOMZ3bas6KENlE-dMH_59",
+ "focus": -0.6512235329620341,
+ "gap": 5.103740945936721
},
"endBinding": {
"elementId": "VZ4fZvVEhGOkaDbGnAXOM",
- "focus": -0.5751816556792378,
- "gap": 3.457986333543673
+ "focus": -0.5751293954776895,
+ "gap": 8.439974601191137
},
"startArrowhead": "arrow",
"endArrowhead": "arrow",
@@ -769,10 +778,10 @@
{
"id": "F8S5vF78PzGEI884_BgcW",
"type": "arrow",
- "x": 204.37145245775514,
- "y": 297.6377991549125,
- "width": 352.7429049155103,
- "height": 126.37140200826508,
+ "x": 234.37145245775514,
+ "y": 303.6377991549125,
+ "width": 385.7429049155103,
+ "height": 119.37140200826508,
"angle": 0,
"strokeColor": "#2f9e44",
"backgroundColor": "#b2f2bb",
@@ -786,11 +795,11 @@
"index": "b0u",
"roundness": null,
"seed": 1944016805,
- "version": 3445,
- "versionNonce": 831061611,
+ "version": 3498,
+ "versionNonce": 1818690366,
"isDeleted": false,
"boundElements": [],
- "updated": 1743364363694,
+ "updated": 1743388104846,
"link": null,
"locked": false,
"points": [
@@ -799,28 +808,28 @@
0
],
[
- -172.79737489543066,
- -2.650344670519303
+ -204.79737489543066,
+ -1.6503446705193028
],
[
- -174.24703867774343,
- 123.1088310248532
+ -204.24703867774343,
+ 117.1088310248532
],
[
- -352.7429049155103,
- 123.72105733774578
+ -385.7429049155103,
+ 117.72105733774578
]
],
"lastCommittedPoint": null,
"startBinding": {
- "elementId": "EoBCO4fe_fWGHsIfZWNO9",
- "focus": -0.21924707175329883,
- "gap": 22.37145245775514
+ "elementId": "qSW7cdA88CRCn0oq6Upcd",
+ "focus": 0.05234894043415935,
+ "gap": 6.346420055914296
},
"endBinding": {
"elementId": "fl0fjOe0gMGOmEH3Qej7N",
- "focus": 0.4346005018633931,
- "gap": 11.62854754224486
+ "focus": 0.43431893151985146,
+ "gap": 8.62854754224486
},
"startArrowhead": "arrow",
"endArrowhead": "arrow",
diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md
--- a/book/src/SUMMARY.md
+++ b/book/src/SUMMARY.md
@@ -4,8 +4,8 @@
# Administrator guide
- [Quick start](./quick_start.md)
- - [Compiling from source](./compiling_from_source.md)
- [Using Docker Compose](./docker_compose.md)
+ - [Compiling from source](./compiling_from_source.md)
- [Configuration](./configuration.md)
- [TLS support](./tls_support.md)
- [Admin interface](./admin_interface.md)
diff --git a/book/src/compiling_from_source.md b/book/src/compiling_from_source.md
--- a/book/src/compiling_from_source.md
+++ b/book/src/compiling_from_source.md
@@ -1,42 +1,26 @@
# Compiling from source
-For this, you'll require [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) and Rust to be installed.
+To build the project, [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) and Rust must be installed.
If you're compiling from a separate workstation than the one that will be running Sandhole, then grab the source files, build the binary, and copy it over:
```bash
git clone https://github.com/EpicEric/sandhole
cd sandhole
-cargo build --release
+cargo build --locked --release
scp target/release/sandhole user@sandhole.com.br:/usr/local/bin/sandhole
```
-If you're compiling on the machine that'll be running Sandhole, you can install it directly with `cargo install`. This should also add `sandhole` to your `PATH`:
+If you're compiling on the machine where you'll run Sandhole, you can install it directly with `cargo install`.
```bash
-cargo install --git https://github.com/EpicEric/sandhole
+# Install from latest release
+cargo install --locked sandhole
+#
# -- OR --
-git clone https://github.com/EpicEric/sandhole
-cargo install --path sandhole
-# -- OR --
-cargo install sandhole # Installs from latest release sources uploaded to crates.io
+#
+# Install the current development version
+cargo install --locked --git https://github.com/EpicEric/sandhole
```
-Once this is all done, you can start running Sandhole! Just make sure that it points to your own domain:
-
-```bash
-sandhole --domain sandhole.com.br
-```
-
-By default, this will expose ports 80 (for HTTP), 443 (for HTTPS), and 2222 (for SSH). If it all succeeds, you should see the following:
-
-```log
-[2024-11-03T13:10:51Z INFO sandhole] Starting Sandhole...
-[2024-11-03T13:10:51Z INFO sandhole] Key file not found. Creating...
-[2024-11-03T13:10:51Z INFO sandhole] Listening for HTTP connections on port 80.
-[2024-11-03T13:10:51Z INFO sandhole] Listening for HTTPS connections on port 443.
-[2024-11-03T13:10:51Z INFO sandhole] Listening for SSH connections on port 2222.
-[2024-11-03T13:10:51Z INFO sandhole] Sandhole is now running.
-```
-
-Now you're ready to dig sandholes like a crab!
+Cargo should automatically add the binary to your `PATH`.
diff --git a/book/src/configuration.md b/book/src/configuration.md
--- a/book/src/configuration.md
+++ b/book/src/configuration.md
@@ -14,17 +14,13 @@
By default, Sandhole runs on ports 80, 443, and 2222. This assumes that your actual SSH server is running on port 22, and that no other services are listening on the HTTP/HTTPS ports.
-However, it might be more desirable to have Sandhole listen on port 22 instead. In order to keep your SSH server running on a different port, edit the port in `/etc/ssh/sshd_config`, then restart your SSH daemon.
+However, it might be desirable to have Sandhole listen on port 22 instead. In order to keep your OpenSSH server running on a different port, edit the `Port` entry in `/etc/ssh/sshd_config`, then restart your SSH daemon.
Now you'll be able to run Sandhole on port 22:
```bash
sandhole --domain server.com --ssh-port 22
```
-
-### What if I need to run another service on HTTP/HTTPS?
-
-It's simple: just let Sandhole take care of that for you! Nothing stops you from connecting to Sandhole on the localhost, and just like any proxy, it will redirect the traffic appropriately for you. See more on ["exposing your first service"](./exposing_your_first_service.md).
## Allow binding on any subdomains/ports
diff --git a/book/src/custom_domains.md b/book/src/custom_domains.md
--- a/book/src/custom_domains.md
+++ b/book/src/custom_domains.md
@@ -17,7 +17,7 @@
This instructs your DNS server to redirect requests to Sandhole, and tells Sandhole to authorize your SSH key for the given domain, respectively.
-If you need to use multiple keys for the same domain, simply add a TXT record for each one.
+If you need to allow multiple keys for the same domain, simply add a TXT record for each one.
Then, expose your service at the given domain:
diff --git a/book/src/digitalocean_dns.png b/book/src/digitalocean_dns.png
new file mode 100644
--- /dev/null
+++ b/book/src/digitalocean_dns.png
diff --git a/book/src/docker_compose.md b/book/src/docker_compose.md
--- a/book/src/docker_compose.md
+++ b/book/src/docker_compose.md
@@ -1,6 +1,6 @@
# Using Docker Compose
-The most straightforward way to have Sandhole up and running is with Docker Compose. Mainly, this takes care of running [Agnos](https://github.com/krtab/agnos) for you, and also daemonizes your application.
+The most straightforward way to have Sandhole up and running is with Docker Compose. Mainly, this takes care of [managing TLS for you](./tls_support.md), and also daemonizes your application.
For this, you'll first need to install the [Docker Engine](https://docs.docker.com/engine/install/) on your server.
@@ -9,9 +9,9 @@
Then, simply run:
```bash
-docker compose up -d
+docker compose up --detach
```
You should also re-run this command whenever you make changes to your configuration and/or after you update to the latest image (`docker compose pull`). See the [official Docker Compose documentation](https://docs.docker.com/compose/) for more information.
-An alternate configuration using [dnsrobocert](https://adferrand.github.io/dnsrobocert/) is available under [docker-compose-example/sandhole-dnsrobocert](https://github.com/EpicEric/sandhole/tree/main/docker-compose-example/sandhole-dnsrobocert).
+An alternate configuration using [dnsrobocert](https://adferrand.github.io/dnsrobocert/) is available under [docker-compose-example/sandhole-dnsrobocert](https://github.com/EpicEric/sandhole/tree/main/docker-compose-example/sandhole-dnsrobocert) in the repository.
diff --git a/book/src/example_flow.svg b/book/src/example_flow.svg
new file mode 100644
--- /dev/null
+++ b/book/src/example_flow.svg
@@ -0,0 +1,2 @@
+
\ No newline at end of file
diff --git a/book/src/exposing_your_first_service.md b/book/src/exposing_your_first_service.md
--- a/book/src/exposing_your_first_service.md
+++ b/book/src/exposing_your_first_service.md
@@ -1,12 +1,14 @@
# Exposing your first service
-Once you have [an authorized public key](./configuration.md#adding-users-and-admins) in Sandhole, you can expose a local service. Assuming that your local HTTP service is running on port 3000, and that Sandhole is listening on `sandhole.com.br:2222`, all you have to do is run
+Once you have [an authorized public key](./configuration.md#adding-users-and-admins) in Sandhole, you can expose a local service. Assuming that your local HTTP service is running on port 3000, and that Sandhole is listening on `sandhole.com.br:2222`, all you have to do is run:
```bash
ssh -i /your/private/key -p 2222 -R 80:localhost:3000 sandhole.com.br
```
Yep, that's it! Sandhole will log that HTTP is being served for you on a certain subdomain, and you can access the URL printed to the console to see that your service is available to the public.
+
+You'll also receive logs about each incoming HTTP request if the administrator hasn't disabled the logging option.
## Requesting multiple tunnels
@@ -24,7 +26,9 @@
```bash
ssh -i /your/private/key -p 2222 -R test:80:localhost:3000 sandhole.com.br
+#
# -- OR --
+#
ssh -i /your/private/key -p 2222 -R test.sandhole.com.br:80:localhost:3000 sandhole.com.br
```
@@ -32,7 +36,9 @@
```bash
ssh -i /your/private/key -p 2222 -R 4321:localhost:3000 sandhole.com.br
+#
# -- OR --
+#
ssh -i /your/private/key -p 2222 -R localhost:4321:localhost:3000 sandhole.com.br
```
@@ -48,4 +54,4 @@
If you'd like to have persistent tunnels, use a tool like `autossh` to automatically reconnect when disconnected. Note that you might be assigned a new subdomain or port through disconnects, depending on the server configuration.
-For a container-based alternative, [check out the Docker Compose client example](https://github.com/EpicEric/sandhole/tree/main/docker-compose-example/client) in the repository.
+For a container-based alternative, [check out the Docker Compose example](https://github.com/EpicEric/sandhole/tree/main/docker-compose-example/client) in the repository.
diff --git a/book/src/faq.md b/book/src/faq.md
--- a/book/src/faq.md
+++ b/book/src/faq.md
@@ -22,6 +22,10 @@
Websockets are always enabled for HTTP services.
+## What if I need to run another service on the HTTP/HTTPS port?
+
+It's simple: just let Sandhole take care of that for you! Nothing stops you from connecting to Sandhole on the localhost, and just like any reverse proxy, it will redirect the traffic appropriately for you.
+
## How do I disable HTTP/TCP/aliasing?
With the `--disable--http`, `--disable-tcp`, and `--disable-aliasing` [CLI flags](./cli.md) respectively. Note that you cannot disable all three at once, as that'd remove all of Sandhole's functionality.
diff --git a/book/src/how_it_works.svg b/book/src/how_it_works.svg
--- a/book/src/how_it_works.svg
+++ b/book/src/how_it_works.svg
@@ -1,2 +1,2 @@
\ No newline at end of file
+ @font-face { font-family: Excalifont; src: url(data:font/woff2;base64,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); }:2222 (SSH):80 (HTTP):443 (HTTPS)SandholePublic serverLocal serviceRemote servicePrivate serverClientWeb browserInternet
\ No newline at end of file
diff --git a/book/src/introduction.md b/book/src/introduction.md
--- a/book/src/introduction.md
+++ b/book/src/introduction.md
@@ -8,9 +8,11 @@
[Sandhole](https://github.com/EpicEric/sandhole) is an unconventional reverse proxy which uses the built-in reverse port forwarding from SSH, allowing services to expose themselves to the Internet with minimal configuration. This is especially useful for services behind NAT, but you may also use Sandhole for:
-- Quickly prototyping websites, APIs, and TCP services, and sharing them with others.
+- Quickly prototyping and sharing websites or APIs.
- Exposing endpoints or ports on IoT devices, game servers, and other applications.
- Hosting a dual-stack HTTP+SSH service (via ProxyJump), such as a Git instance.
-- Handling a multi-tenant network with several websites under the same domain.
+- Handling a multi-tenant network with several websites and users.
- Using the tunnel for ad hoc peer-to-peer connections, or [even as a basic VPN](./local_forwarding.md).
- And possibly more!
+
+Fun fact: the Sandhole book runs behind a Sandhole instance!
diff --git a/book/src/local_forwarding.md b/book/src/local_forwarding.md
--- a/book/src/local_forwarding.md
+++ b/book/src/local_forwarding.md
@@ -18,9 +18,9 @@
## Enforcing aliasing
-Aliasing is always enabled for SSH hosts, and is conditionally enabled for TCP hosts that have requested a address different from `localhost` (for example, `my.tunnel` in the previous section).
+Aliasing is always enabled for SSH hosts, and is conditionally enabled for TCP hosts that have requested an address other than `localhost`.
-To enable aliasing for HTTP hosts, pass either the `tcp-alias` command to the remote forwarding command as follows:
+To enable aliasing for HTTP hosts, pass the `tcp-alias` command to the remote forwarding command as follows:
```bash
ssh -p 2222 -R my.tunnel:80:localhost:8080 sandhole.com.br tcp-alias
@@ -37,7 +37,3 @@
These fingerprints may belong to keys unrecognized by Sandhole, and they'll still be able to connect to your tunnel.
This option will also enforce aliasing for HTTP hosts.
-
-## Disabling local forwarding
-
-The administrator can disable all local forwardings with the [`--disable-aliasing` CLI flag](./cli.md).
diff --git a/book/src/quick_start.md b/book/src/quick_start.md
--- a/book/src/quick_start.md
+++ b/book/src/quick_start.md
@@ -2,7 +2,50 @@
In order to run Sandhole, you'll need:
-- A server with at least one public address.
-- A domain (for example, `sandhole.com.br`), and its subdomains (`*.sandhole.com.br`), configured via DNS to point to the public address.
+- A server with at least one public address. This tutorial assumes that you're using Linux.
+- Control over a domain name (for example, `sandhole.com.br`) and its subdomains (`*.sandhole.com.br`).
-Then, install the Sandhole binary in your server. Currently, you can do so [through Docker Compose](./docker_compose.md), by downloading [a binary from the latest release](https://github.com/EpicEric/sandhole/releases/latest), or by [compiling it yourself](./compiling_from_source.md).
+## 1. Configure your DNS
+
+Make sure to point the `A` and `AAAA` records to your server's IP address(es).
+
+This step varies depending on your choice of DNS provider. For example, on DigitalOcean, this is what the configuration might look like:
+
+
+
+## 2. Get the executable
+
+Download a copy of the latest release:
+
+```bash
+# x64
+wget --output-document sandhole https://github.com/EpicEric/sandhole/releases/latest/download/sandhole-linux-amd64
+#
+# -- OR --
+#
+# AArch64
+wget --output-document sandhole https://github.com/EpicEric/sandhole/releases/latest/download/sandhole-linux-arm64
+```
+
+If you prefer, you may also use [Docker Compose](./docker_compose.md) or [compile the binary yourself](./compiling_from_source.md).
+
+## 3. Run Sandhole
+
+You can now run Sandhole! Just make sure that it points to your domain:
+
+```bash
+./sandhole --domain sandhole.com.br
+```
+
+By default, this will expose ports 80 (for HTTP), 443 (for HTTPS), and 2222 (for SSH). If it all succeeds, you should see the following:
+
+```log
+[2024-11-03T13:10:51Z INFO sandhole] Starting Sandhole...
+[2024-11-03T13:10:51Z INFO sandhole] Key file not found. Creating...
+[2024-11-03T13:10:51Z INFO sandhole] Listening for HTTP connections on port 80.
+[2024-11-03T13:10:51Z INFO sandhole] Listening for HTTPS connections on port 443.
+[2024-11-03T13:10:51Z INFO sandhole] Listening for SSH connections on port 2222.
+[2024-11-03T13:10:51Z INFO sandhole] Sandhole is now running.
+```
+
+Now you're ready to dig sandholes like a crab! 🦀
diff --git a/book/src/technical_overview.md b/book/src/technical_overview.md
--- a/book/src/technical_overview.md
+++ b/book/src/technical_overview.md
@@ -24,3 +24,27 @@

As such, it's possible to expose services publicly without needing a VPN, even behind NAT or firewalls.
+
+## Example flow
+
+Let's say client A wishes to expose a local service, running on port 8080, to the Internet.
+
+
+
+1. Client A connects to a Sandhole instance while requesting a remote port forwarding:
+
+```bash
+ssh -p 2222 -R mytunnel:80:localhost:8080 sandhole.com.br
+```
+
+2. Sandhole handles the forwarding request and starts proxying requests from `http://mytunnel.sandhole.com.br` to client A's port 8080.
+
+3. Client B accesses `http://mytunnel.sandhole.com.br` through a web browser.
+
+4. Sandhole opens a tunneling channel over SSH to client A, simulating a TCP stream containing client B's request.
+
+5. Client A's HTTP server replies over the SSH channel.
+
+6. Sandhole forwards the reply to client B.
+
+To client A, requests arrive normally at the socket. To client B, Sandhole acts as if it were the service itself.
diff --git a/book/src/tls_support.md b/book/src/tls_support.md
--- a/book/src/tls_support.md
+++ b/book/src/tls_support.md
@@ -18,4 +18,6 @@
## ACME support
+ACME allows you to generate certificates for user-provided domains automatically, without having to edit your configuration for each one.
+
Adding ACME support is as simple as adding your contact e-mail address via `--acme-contact-email you@your.email.com`, but first, make sure that you agree to the [Let's Encrypt Subscriber Agreement](https://letsencrypt.org/repository/). Sandhole will automatically manage the cache for your account and any certificates generated this way.