Pipe Manual

Profile manual

The file

On this page
  1. Sections
  2. Lines
  3. Comments
  4. Quoting
  5. Kinds of values
  6. Files and URLs a profile names
  7. #!include
  8. Line requirements
  9. #!MANAGED-CONFIG
  10. What a profile fetches

A profile is a text file in UTF-8. It is read line by line, and every line is kept exactly as it was written — comments, blank lines, order, line endings (LF or CRLF), a byte-order mark, sections Pipe does not know — so that a profile edited in the app comes back byte for byte, except where it was changed.

#!MANAGED-CONFIG https://example.com/pipe.conf interval=43200

[General]
loglevel = notify
dns-server = system, 9.9.9.9  // the network's resolvers, and one more

[Proxy]
Server 1 = trojan, server1.example.net, 443, password=secret
#!REQUIREMENT SYSTEM=='iOS' Phone = socks5, 192.168.1.20, 1080

[Rule]
#!include rules/streaming.conf
FINAL,Server 1

Sections#

A line [Name] opens a section, and the lines after it, up to the next such line, are its lines.

  • The names of sections are matched in any case, with spaces around them ignored: [proxy group] is [Proxy Group].
  • Two kinds of section carry a name of their own after a space: [WireGuard <name>] and [Ruleset <name>]. The name keeps its case, and may hold spaces: [Ruleset My List].
  • A section may be written more than once. Its parts are read as one section, in the order of the file.
  • A line that says something but stands before the first section is left out, with a warning. Comments and directives may stand there.

The sections Pipe reads:

Section What it holds Described in
[General] Settings for the whole profile General
[Proxy] Proxies Proxies
[Proxy Group] Groups Groups
[Rule] Rules Rules
[Ruleset <name>] A list of rules, used as RULE-SET,<name>,… Rules
[Host] Local answers and servers for names DNS
[URL Rewrite] URLs rewritten, redirected or refused The HTTP engine
[Header Rewrite] Request and answer headers changed The HTTP engine
[Body Rewrite] Request and answer bodies changed The HTTP engine
[Map Local] Requests answered locally The HTTP engine
[MITM] HTTPS decryption The HTTP engine
[Script] Scripts Scripts
[Panel] Panels in the app Scripts
[SSID Setting] What changes on some networks Networks
[IP Rewrite] Packets the tunnel answers itself Networks
[Port Forwarding] Ports forwarded through a policy Networks
[Keystore] Certificates and keys Proxies
[WireGuard <name>] A WireGuard proxy's settings Proxies

Sections that are kept in the file but not used:

Section Said as Why
[Snell Server], [MTProto], on iOS nothing Built-in servers run on a computer; a phone passes them over, as another platform's settings.
[DHCP], on a phone nothing It belongs to a computer that serves a network as its gateway, where it is read.
[Ponte] warning Surge Ponte configuration is not supported.
[Testing] note Throughput testing is not supported yet.
[Replica], any other name note It is not a section of Pipe's format.

The message names the section and how many lines it holds: [Testing] (2 lines): throughput testing is not supported yet; kept in the file, not used.

Lines#

What a line says depends on its section. There are four shapes.

Shape Sections Example
key = value [General], [MITM], [Host], [IP Rewrite], [WireGuard <name>], [SSID Setting] (its settings, comma-separated) loglevel = notify
Name = type, …, key=value, … [Proxy], [Proxy Group], [Script], [Panel], [Keystore] Server 1 = trojan, server1.example.net, 443, password=secret
TYPE,VALUE,POLICY,options [Rule], [Ruleset <name>], and rule-set files DOMAIN-SUFFIX,example.com,Proxy
Words separated by spaces [URL Rewrite], [Header Rewrite], [Body Rewrite], [Map Local], [Port Forwarding] ^http://example\.com https://example.com 302
  • A key = value or Name = … line is split at its first = that is not inside quotes. Spaces around it are taken off. A name keeps its case and may hold spaces (Server 1).
  • After the =, components are separated by commas that are not inside quotes or parentheses. Spaces around each component are taken off.
  • A component holding = is a key=value parameter. The keys of parameters are read in any case. A parameter given twice on one line counts once: the last one given.
  • In [General] and [MITM], a key given on two lines counts once: the later line.
  • An empty component is skipped: a, , b is a, b.

Comments#

  • A line whose first characters, after any spaces, are #, ; or // is a comment.
  • On a line that says something, #, ; or // after a space or a tab, and outside quotes, starts a comment that runs to the end of the line: dns-server = 8.8.8.8 // a public server.
  • Without a space before it, it belongs to the value: test-url = http://example.com/#top keeps its #top, and port-hopping=1234;5000-6000 its ;.
  • A line starting with #! is not a comment but a directive: #!include, line requirements, #!MANAGED-CONFIG, or a module's metadata.

Quoting#

A value is quoted when it holds what would otherwise end it.

  • "…" holds its text; inside it, \" is a quote and \\ a backslash.
  • '…' holds its text exactly as written.
  • A quote opens a quoted value only where a value begins: at the start of a component, after =, after (, or after the spaces that follow one of them; in a line of words, at the start of a word or after =. An apostrophe inside a name is a letter: Yu's Server = … needs no quotes.
  • Inside quotes, commas, =, parentheses and comment marks are part of the value.
  • A parameter's value in double quotes may hold quotes of its own, as modules write a JSON argument: argument="{"lang":"en","debug":true}". It ends at the first quote after which the line ends or the next parameter begins (, key=, or key= in a line of words).

Quote a value that holds a separator (a comma; an =; in a rule's value, a parenthesis), a quote, text that would read as a comment ( #, ; or //), one that begins or ends with a space, or one that would begin a comment, a directive or a section (#, ;, //, [). What the app writes — an edit, an export, the pool as a list — quotes what needs it, the same way for every writer, and reads it back before keeping it; a value with a line break or another control character cannot be written on a line at all, and is refused.

[Proxy Group]
Streaming = select, "Server 1, premium", JP 01, policy-regex-filter="(Server|JP) #1"

[Rule]
URL-REGEX,"^https?://ads\.example/(a,b)",REJECT

In the sections written as words — the rewrite sections and [Map Local] — a word that holds spaces is quoted, and so may be the value of a parameter: data="{ \"ok\": true }".

Kinds of values#

Lists#

A list is written with commas: dns-server = system, 9.9.9.9. An item that holds a comma is quoted. Empty items are skipped.

True and false#

A setting that is on or off takes true or false, and also 1 or 0, yes or no, on or off, in any case. A value that is none of these is warned about, and the setting keeps its default.

Numbers#

Durations are seconds unless a setting says otherwise, and may have a fraction where the setting says so (test-timeout = 2.5). Sizes are bytes. A number that cannot be read is warned about, and the setting keeps its default.

Host lists#

Several settings take a list of hosts — always-real-ip, always-raw-tcp-hosts, force-http-engine-hosts, and [MITM]'s hostname among them. Its items are tried in order, and the first that matches decides.

Item Matches
example.com That name, in any case.
*.example.com, api-?.example.com Names by pattern: * is any run of characters, dots included; ? is one character.
192.168.1.10 That address, as written.
<ip-address> A destination given as an address rather than a name; <ipv4-address> and <ipv6-address> one of each family.
<simple-hostname> A name without a dot.
…:8080 After any of the above: that port only.
…:0 After any of the above: every port.
-… In front of any of the above: what matches is left out.

An item without a port means the ports the setting is about — port 80 for the HTTP engine, 443 for HTTPS decryption — as each setting says. Since the first item that matches decides, exclusions go first: hostname = -pinned.example.com, *.example.com. An item that cannot be read is left out, with a warning, and the rest of the list stands.

Regular expressions#

Rules (DOMAIN-REGEX, URL-REGEX), the rewrite sections, scripts' patterns and groups' filters take regular expressions.

  • A pattern is found anywhere in the text it is tried on, unless it anchors itself with ^ and $.
  • The usual syntax is there: character classes ([a-z], \d, \w, \s), repetition (*, +, ?, {2,5}), alternation, groups and named groups ((?P<name>…)), and flags such as (?i).
  • The patterns that choose requests by their URL — the rewrite sections', [Map Local]'s and scripts' — may look around ((?=…), (?!…), (?<=…), (?<!…)) and refer back (\1), as other apps' modules are written; a match that would take more than 100,000 steps of going back is no match. Rules, groups' filters and what a header or body rewrite finds have neither. A rewrite or a script whose pattern Pipe cannot use is left out, with a warning that says why; a rule, or a group's filter, with such a pattern is an error.
  • In a replacement, $1 is what the first group captured ($1a is group 1 followed by a), \$ is a dollar sign and \\ a backslash.

Ports#

Rules on ports take one port (443), a range (8000-9000), or a bound: >=1024, >1024, <=1024, <1024.

Networks#

Subnet groups, SUBNET rules and [SSID Setting] name networks by subnet expressions: SSID:Home*, TYPE:CELLULAR, ROUTER:192.168.1.1. Networks describes them.

Files and URLs a profile names#

A profile may name other files: by a path relative to the folder the profile is in (rules/streaming.conf), or by a URL.

Where A file A URL
#!include read when the profile is compiled downloaded by the app, and kept current (linked profiles); not read by the CLI
policy-path read when the profile is compiled downloaded by the app, and kept current
RULE-SET, DOMAIN-SET, [Host] lists read when the profile is compiled downloaded by the engine, and kept current
script-path read when the profile is compiled downloaded by the engine, and kept current
[Map Local] data, jq-path read when the profile is compiled not downloaded yet: the line is left out

On iPhone, the app keeps each profile as one file and keeps no other files beside it yet, so a profile there takes what it names from URLs. A file or a list that is not there when the profile is compiled is said, with a warning, on the line that names it.

What compiling reads is limited: the files a profile names may come to 64 MiB in all, each counted once however often it is named. What is copied out of them is counted too, every copy, so that one file named from many lines costs what as many files would: what #!include brings in may come to 64 MiB, and so may the scripts, [Map Local] answers and jq programs written into the compiled configuration. A file past a limit is not read, or not copied, and the line that names it is left out, with a warning.

What the profile compiles to has to fit where Pipe runs. On iOS, whose VPN extension the system gives little memory, that is 16 MiB held in the tunnel and a file of 64 MiB; on CLI hosts it is 256 MiB. A profile past it is refused, saying how much it came to. About 25,000 proxies with 150,000 rules written out fit on a phone. The lists in files, the scripts and the [Map Local] answers it names are written into it; a large list is better named by its URL, which Pipe fetches itself and keeps in a file, at almost no cost to the tunnel.

#!include#

#!include brings lines of other files into a profile, where it is written:

[Rule]
#!include rules/streaming.conf, "rules/work lists.conf"
FINAL,Proxy
  • It is written inside a section, and brings in that section of each file it names — the [Rule] section of rules/streaming.conf above — in place of the #!include line. The other sections of the file are not read.
  • Several files are separated by commas; a name with a space in it is quoted.
  • In [Ruleset *] and [WireGuard *], it brings in every section of that kind the file has, each under its own name. Such a section holds only #!include lines; any other line in it is left out, with a warning.
  • An included file cannot include others: an #!include in it is left out, with a warning.
  • A file is included into a section once: a second #!include of it in the same section — on the same line or another — is left out, with a warning.
  • A file that is not there, or that has no section of the kind asked for, includes nothing, and says so.
  • What is said about an included line names the file and its line: rules/streaming.conf:4: ….

The app's settings edit the profile's own lines; included lines are edited in their own files.

Linked profiles#

A managed profile is replaced whenever it is kept current, so what you add to it is lost. Linked to it instead: a profile of your own whose sections #!include the managed profile's by its URL, with your lines beside them.

[Proxy]
#!include https://example.com/managed.conf
Mine = http, proxy.example, 8080

[Proxy Group]
#!include https://example.com/managed.conf
  • The app downloads the file and keeps it current, as it does a policy-path list, and the profile is compiled again when it changes.
  • Only the sections that say #!include take anything from it. Write your own [Rule] lines before its #!include: its FINAL ends the rules.
  • Its #!MANAGED-CONFIG line stays with it: your profile is not itself managed.
  • An external proxy cannot be used from a managed profile or a file at a URL, whatever was approved: those change without anyone reading them. Write the line in your own profile.
  • Until it has been downloaded, nothing is included from it, and that is said.

Line requirements#

A line requirement keeps a line only where it holds. It is written at the start of a line, followed by the line:

[Proxy Group]
#!REQUIREMENT CORE_VERSION>=22 Auto = smart, Server 1, JP 01
Auto = url-test, Server 1, JP 01 //!REQUIREMENT CORE_VERSION<22
Desk = select, Server 1, DIRECT #!MACOS-ONLY

or at the end of a line, after a space, as #!REQUIREMENT … or //!REQUIREMENT …. #!IOS-ONLY keeps a line in the app; #!MACOS-ONLY and #!LINUX-ONLY select the corresponding CLI host. Foreign guards (#!TVOS-ONLY, #!ANDROID-ONLY, #!HARMONYOS-ONLY, #!WINDOWS-ONLY) are recognized for imported profiles and never apply. A line whose requirement does not hold is read as if it were not there; one whose requirement cannot be read is left out, with a warning. A requirement with no line after it is a comment.

The expression is one word, or a phrase in double quotes: #!REQUIREMENT "SYSTEM=='iOS' AND CORE_VERSION>=22" …. It is made of:

Part Written
Variables CORE_VERSION, SYSTEM, SYSTEM_VERSION, DEVICE_MODEL, LANGUAGE, DEVICE_NAME, in any case
Values text in quotes ('iOS'), numbers, true, false
Comparisons == or =, != or <>, >, >= or =>, <, <= or =<: SYSTEM_VERSION as a version, number by number (18.6.2 is later than 9 and earlier than 18.10); otherwise as numbers when both sides are numbers, as text when not
Text tests BEGINSWITH, ENDSWITH, CONTAINS; LIKE (a pattern with * and ? over the whole value); MATCHES (a regular expression that matches the whole value)
Logic AND or &&, OR or ||, NOT or !, and parentheses

What the variables are on Pipe:

Variable Value on iPhone
CORE_VERSION 6010000: the version of the format Pipe reads, as one number
SYSTEM iOS in the app; macOS or Linux for the compiler on a CLI host
SYSTEM_VERSION the system's version: 26.0, 18.6.2
DEVICE_MODEL the model's identifier: iPhone17,1
LANGUAGE the first of your preferred languages: en-US, pt-BR
DEVICE_NAME iPhone or iPad: iOS does not tell apps the name you gave the device
[Proxy Group]
#!REQUIREMENT "DEVICE_MODEL BEGINSWITH 'iPad'" Tablet = select, Server 1, JP 01

[Rule]
#!REQUIREMENT "LANGUAGE LIKE 'fr*'" DOMAIN-SUFFIX,fr,DIRECT

#!MANAGED-CONFIG#

A profile that a provider keeps up to date says where it comes from:

#!MANAGED-CONFIG https://example.com/pipe.conf interval=86400 strict=false
Part Meaning Default
The URL Where the profile is fetched again from: http:// or https://. —
interval Seconds between fetches. 86400 (a day)
strict true: switching Pipe on fetches an overdue profile first. false

The line may stand anywhere in the file; the first one counts.

How the app keeps a managed profile current:

  • It fetches the profile in use again once its interval has passed: when the app comes to the front, when iOS gives it time in the background, when the tunnel comes up, and — while the app stays in front — at the moment it falls due. Nothing is fetched on a timer of its own, and nothing waits while the app is in the background.
  • While the app stays in front, it waits at least five minutes between checks, whatever the interval says. A fetch that failed is tried again after ten minutes, or after the interval when that is shorter.
  • It asks with what the server said last time (ETag, Last-Modified), so an unchanged profile costs one short request.
  • What comes back replaces the profile's text, whole. A change made to a managed profile is therefore lost at its next update: change it with a module, which patches whatever profile is in use.
  • What comes back must be a profile: in Pipe's format, or another app's, which is converted again as it was when the profile was added (Importing profiles). If it is not a profile, the profile is kept as it was, and the app says why.
  • Only the profile in use is kept current. Another one is brought up to date when it is put in use.
  • With strict=true, switching Pipe on fetches an overdue profile first, for eight seconds at most. The tunnel starts either way, and a failed fetch is said.

The app shows where each thing it keeps current stands — when it was last updated, how often it is, why the last attempt failed — and can update it on request.

What a profile fetches#

What Fetched by Again every Set with
The profile itself the app a day #!MANAGED-CONFIG … interval=
policy-path lists the app a day the group's update-interval
RULE-SET and DOMAIN-SET lists, [Host] lists the engine, through the rules a day update-interval= on the line
Scripts the engine, through the rules a day script-update-interval
The country database the engine, through the rules a week disable-geoip-db-auto-update turns it off
The ASN database the engine, through the rules a week —
Modules installed from a URL the app when you ask —

"Through the rules" means the download goes where the rules send its host, like any other connection.