David Eyers
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    ## Lab 9—Amazon EC2 using Hashicorp Terraform [Lab 8]: /a8wHsmkVTh6-ud_vNAe2Mw This lab involves deploying virtual machines into Amazon EC2 using the [Terraform tool](https://www.terraform.io) from Hashicorp (i.e., the company that makes Vagrant). :::warning :warning: Note that if you receive confusing-looking error messages from Terraform, you should check that your AWS Academy API credentials have not expired: these API credentials are entirely independent from your AWS Academy username+password. The `AWS_SESSION_TOKEN` etc., all roll-over periodically (every few hours). VMs that you created before your AWS session token expires will be accessible after you refresh your AWS session token, although it's possible that the VMs may need to be brought back from a paused or shutdown state. ::: ## Using Vagrant to access Terraform The Owheo Building Labs may have the AWS command line tools installed, but do not have Terraform installed on them. To provide a standardised environment that should also work on your home computers, we instead install and use Terraform within a Vagrant VM. :::warning :warning: Note that you do not need to automate deployment of your assignment two code and configuration, although you must document how you set up your assignment, otherwise. ::: - A Git repository that installs Terraform inside a helper VM is available at https://altitude.otago.ac.nz/cosc349/lab09-terraform-ec2 and has been tested to work on both Intel-based CPUs and Apple's M_ Mac's Arm-based CPUs. (The repository has a branch `docker` that in the past supported Docker on Macs, but is presently broken... but now that Vagrant + VirtualBox work on Arm-based Macs the Docker support is not required.) - `git clone` this repository so that you acquire a local copy. - Change into your Git clone's directory - `vagrant up` to set up the helper VM - `vagrant ssh` into your helper VM - Working inside the helper VM, as for [Lab 8], you will need to have your AWS Academy API credentials set up so that Vagrant can work on your behalf. As suggested by the AWS Academy page that provides you with your credentials (that remain valid for a few hours), store the crentials at `~/.aws/credentials`. ## (Optional) Check that your `aws` command is working - This lab does not require use of the `aws` command line tools, but they can be used to test (and modify) the AWS configuration files (e..g, within `~/.aws` ) that Terraform _does_ rely on. - Inside the helper VM, test that your `aws` command line tools are working (e.g., also testing your `credentials`). - In my case I ran: ``` vagrant@ubuntu-focal:~$ aws s3 ls 2024-09-10 12:25:23 dme26-2024-test 2024-09-10 12:26:31 logs.dme26-2024-test ``` - ... with both indicates that my `aws` command line tools are working as expected, and also indicates that I did not follow my own advice to delete the S3 buckets that I was no longer using... (!) - Run the `aws configure` command and set your region to `us-east-1`, since that is the only region that we can access using AWS Academy. The other options' default values you can accept just by pressing <kbd>return</kbd> or <kbd>enter</kbd>. ## Create a keypair on EC2, if you need to :::info :confused: For some reason, I have described the process here in lab 9 for creating a new key pair as if you have never done so... but you probably created a key pair back in lab 4! You can use the key pair you created back in lab 4, and skip creating another one, and thus this section. In any case, having multiple key pairs will not break anything---provided that you match up the name of the key pair (used by AWS to look it up) with the private key file that you specify (used by your local software to prove to AWS that it's your key). ::: - you can see what keypairs you have available by running a command such as the following: ``` vagrant@ubuntu-focal:~$ aws ec2 describe-key-pairs | grep KeyName | grep -v vockey | cut -d'"' -f 4 cosc349-2024 ``` - if you still have the private key file for the key name that is shown, you do not need to create another key pair and can skip to the next section. :::info :information_source: The screenshots in this section are from a past year (since 2022 I've chosen to re-use my lab 4 keypair!), but should provide enough context to be useful even so. Let the teaching team know if this is not the case! ::: - We will define an AWS key pair with a given name that can be loaded into EC2 instances that you create. - Navigate your web browser to your AWS console: ![](https://i.imgur.com/GiCRDrv.png) - ... more specifically with the goal of reaching your EC2 console: ![](https://i.imgur.com/Nv9FjUU.png) - You may either see "Key pairs 0" defined in the "Resources" displayed at the top of the console page, or see a key that you didn't define that's part of AWS Academy (`vockey` for me). Let's get our own keypair into that list. - On the left-menu under "Network & Security", select "Key Pairs": ![](https://i.imgur.com/Q71sM1v.png) ![](https://i.imgur.com/MNiD3JK.png) - From there, click the red "Create key pair" button, and provide a key pair name (e.g., `cosc349-2024`): ![](https://i.imgur.com/Zw0EDrT.png) - Your key should now be listed: ![](https://i.imgur.com/N4iYXeK.png) - ... but **in addition**, the private portion of your key-pair will have been downloaded by your web browser into a file such as `cosc349-2024.pem`. - Keys should be protected in terms of file system permissions, and stored in a consistent, useful place. It is usually ideal for SSH key pairs to be stored in the `.ssh` directory within your home directory, since this is where tools such as OpenSSH (i.e., `ssh`) will default to looking. To address the location and permissions of your file, execute commands similar to the following (for macOS / Linux / Git Bash on Windows): ```shell=- mv ~/Downloads/cosc349-2024.pem ~/.ssh/ chmod 700 ~/.ssh/cosc349-2024.pem ``` - I launched SSH connections to my VMs below from my host computer. If you want to launch SSH connections from within the helper VM, you'll need to ensure that that VM has your `cosc349-2024.pem` (or equivalent) file in _its_ `~/.ssh/` directory, with the appropriate file permissions. ## Getting started with Terraform - Let's create an initial test of Terraform within a folder checked out from the Git repository for lab 9. First, **from inside your VM** (i.e., using SSH), change into the shared folder: `cd /vagrant/tf-deploy` - Note that I am creating this directory within the directory shared with the VM host: this is so that I can run a handy editor on the host or edit the configuration from within the VM. - I then opened VSCode on my host computer pointed at the `tf-deploy` directory. - Create a file named `main.tf` with the content: ``` provider "aws" { region = "us-east-1" } resource "aws_security_group" "allow_ssh" { name = "allow_ssh" description = "Allow inbound SSH traffic" ingress { description = "SSH from anywhere" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } resource "aws_instance" "web_server" { ami = "ami-0360c520857e3138f" instance_type = "t2.micro" key_name = "cosc349-2024" vpc_security_group_ids = [aws_security_group.allow_ssh.id] user_data = <<-EOF #!/bin/bash sudo apt update sudo apt install -y apache2 sudo systemctl start apache2 sudo systemctl enable apache2 EOF tags = { Name = "WebServer" } } output "web_server_ip" { value = aws_instance.web_server.public_ip } ``` - This is a declarative syntax that contains four top-level parts: - The `provider` is AWS operating in the `us-east-1` region - A security group is set up called "allow_ssh" that allows SSH traffic to reach a VM that uses that security group - A `resource` that defines a VM named "web_server" - An output section that indicates information that Terraform will display when its finished its work. - Run the `terraform init` command from which I get output: ``` Initializing the backend... Initializing provider plugins... - Finding latest version of hashicorp/aws... - Installing hashicorp/aws v5.67.0... - Installed hashicorp/aws v5.67.0 (signed by HashiCorp) Terraform has created a lock file .terraform.lock.hcl to record the provider selections it made above. Include this file in your version control repository so that Terraform can guarantee to make the same selections by default when you run "terraform init" in the future. Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary. ``` - Then run the `terraform plan` command, for which I get output: ``` Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_instance.web_server will be created + resource "aws_instance" "web_server" { + ami = "ami-0360c520857e3138f" + arn = (known after apply) + associate_public_ip_address = (known after apply) + availability_zone = (known after apply) + cpu_core_count = (known after apply) + cpu_threads_per_core = (known after apply) + disable_api_stop = (known after apply) + disable_api_termination = (known after apply) + ebs_optimized = (known after apply) + get_password_data = false + host_id = (known after apply) + host_resource_group_arn = (known after apply) + iam_instance_profile = (known after apply) + id = (known after apply) + instance_initiated_shutdown_behavior = (known after apply) + instance_lifecycle = (known after apply) + instance_state = (known after apply) + instance_type = "t2.micro" + ipv6_address_count = (known after apply) + ipv6_addresses = (known after apply) + key_name = "cosc349-2024" + monitoring = (known after apply) + outpost_arn = (known after apply) + password_data = (known after apply) + placement_group = (known after apply) + placement_partition_number = (known after apply) + primary_network_interface_id = (known after apply) + private_dns = (known after apply) + private_ip = (known after apply) + public_dns = (known after apply) + public_ip = (known after apply) + secondary_private_ips = (known after apply) + security_groups = (known after apply) + source_dest_check = true + spot_instance_request_id = (known after apply) + subnet_id = (known after apply) + tags = { + "Name" = "WebServer" } + tags_all = { + "Name" = "WebServer" } + tenancy = (known after apply) + user_data = "07c9939940fed692444ad659f6257659122880ac" + user_data_base64 = (known after apply) + user_data_replace_on_change = false + vpc_security_group_ids = (known after apply) + capacity_reservation_specification (known after apply) + cpu_options (known after apply) + ebs_block_device (known after apply) + enclave_options (known after apply) + ephemeral_block_device (known after apply) + instance_market_options (known after apply) + maintenance_options (known after apply) + metadata_options (known after apply) + network_interface (known after apply) + private_dns_name_options (known after apply) + root_block_device (known after apply) } # aws_security_group.allow_ssh will be created + resource "aws_security_group" "allow_ssh" { + arn = (known after apply) + description = "Allow inbound SSH traffic" + egress = [ + { + cidr_blocks = [ + "0.0.0.0/0", ] + from_port = 0 + ipv6_cidr_blocks = [] + prefix_list_ids = [] + protocol = "-1" + security_groups = [] + self = false + to_port = 0 # (1 unchanged attribute hidden) }, ] + id = (known after apply) + ingress = [ + { + cidr_blocks = [ + "0.0.0.0/0", ] + description = "SSH from anywhere" + from_port = 22 + ipv6_cidr_blocks = [] + prefix_list_ids = [] + protocol = "tcp" + security_groups = [] + self = false + to_port = 22 }, ] + name = "allow_ssh" + name_prefix = (known after apply) + owner_id = (known after apply) + revoke_rules_on_delete = false + tags_all = (known after apply) + vpc_id = (known after apply) } Plan: 2 to add, 0 to change, 0 to destroy. Changes to Outputs: + web_server_ip = (known after apply) ─────────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. ``` - If you're happy with the proposal Terraform has made, then you can enact something very similar to the present plan using the `terraform apply` command. - Terraform will prompt you for confirmation, to which you say "yes" if you wish to proceed. - For me, when that apply command finished, I was presented with lots of diagnostics, and an "Outputs:" section including `web_server_ip = "3.237.39.227"`. - You should reload your AWS dashboard to confirm that you see the EC2 VM that you created using Terraform ## Connect to your EC2 VM - Unlike Vagrant, Terraform does not provide a built in way to connect to your EC2 VM using SSH. - However, you know the public IP address from the above, and should have your private key file handy, and thus be able to connect to your VM using SSH. In this case I am connecting from my macOS host computer: ``` ➜ ~ ssh -i ~/.ssh/cosc349-2024.pem ubuntu@3.237.39.227 The authenticity of host '3.237.39.227 (3.237.39.227)' can't be established. ED25519 key fingerprint is SHA256:+Bfl2Y+M0DudYJMqTanfzwXjnsy5DZIOaTcMXiXKxGg. This key is not known by any other names. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '3.237.39.227' (ED25519) to the list of known hosts. Welcome to Ubuntu 23.04 (GNU/Linux 6.2.0-1011-aws x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage System information as of Tue Sep 17 10:57:58 UTC 2024 System load: 0.97 Processes: 107 Usage of /: 24.7% of 7.58GB Users logged in: 0 Memory usage: 24% IPv4 address for enX0: 172.31.74.249 Swap usage: 0% 102 updates can be applied immediately. 61 of these updates are standard security updates. To see these additional updates run: apt list --upgradable The programs included with the Ubuntu system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. To run a command as administrator (user "root"), use "sudo <command>". See "man sudo_root" for details. ubuntu@ip-172-31-74-249:~$ ``` - You can test whether this VM is running the webserver that the `user_data` section intalled using: - `sudo lsof -Pni` ``` ubuntu@ip-172-31-74-249:~$ sudo lsof -Pni COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME systemd 1 root 132u IPv6 16762 0t0 TCP *:22 (LISTEN) systemd-r 225 systemd-resolve 13u IPv4 15572 0t0 UDP 127.0.0.53:53 systemd-r 225 systemd-resolve 14u IPv4 15573 0t0 TCP 127.0.0.53:53 (LISTEN) systemd-r 225 systemd-resolve 15u IPv4 15574 0t0 UDP 127.0.0.54:53 systemd-r 225 systemd-resolve 16u IPv4 15575 0t0 TCP 127.0.0.54:53 (LISTEN) systemd-n 308 systemd-network 18u IPv4 16525 0t0 UDP 172.31.74.249:68 chronyd 469 _chrony 5u IPv4 17390 0t0 UDP 127.0.0.1:323 chronyd 469 _chrony 6u IPv6 17391 0t0 UDP [::1]:323 apache2 1929 root 4u IPv6 21328 0t0 TCP *:80 (LISTEN) apache2 1930 www-data 4u IPv6 21328 0t0 TCP *:80 (LISTEN) apache2 1931 www-data 4u IPv6 21328 0t0 TCP *:80 (LISTEN) sshd 2248 root 3u IPv6 16762 0t0 TCP *:22 (LISTEN) sshd 2249 root 4u IPv6 23673 0t0 TCP 172.31.74.249:22->161.65.248.210:63937 (ESTABLISHED) sshd 2380 ubuntu 4u IPv6 23673 0t0 TCP 172.31.74.249:22->161.65.248.210:63937 (ESTABLISHED) ``` - Which on my VM indicates `apache2` is listening on TCP port 80. - You can confirm that your VM is able to access the Internet, e.g., by running a `curl` command, such as in: ``` ubuntu@ip-172-31-74-249:~$ curl --head https://www.cs.otago.ac.nz/ HTTP/1.1 301 Moved Permanently Date: Tue, 17 Sep 2024 11:00:53 GMT Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/5.4.16 SVN/1.7.14 Strict-Transport-Security: max-age=63072000; includeSubDomains; Location: https://www.otago.ac.nz/computer-science/ Content-Type: text/html; charset=iso-8859-1 ``` - However, we cannot access the machine from the Internet as the security groups block HTTP traffic. :::success :book: Using the `allow_ssh` security group resource as a starting point, add into your `main.tf` a resource defining a security group named `allow_web` that opens port 80 and port 443 to all internet traffic. ::: - When you have defined your `allow_ssh` resource, add your new security group ID into the `vpc_security_group_ids` list wtihin the resource defining your web server. - You should be able to run `terraform apply` and Terraform should be able to figure out what to do. - Now see whether you can access the public IP address in your web browser, for example I accessed http://3.237.39.227/ (take care not to have your browser switch this to HTTPS). I see: ![](https://hackmd.io/_uploads/ByKazAaC2.png) ## Add a database server - Add to your `main.tf` a new `aws_instance` resource named `mysql_server`. You can use the following `user_data` to install MySQL. ``` user_data = <<-EOF #!/bin/bash sudo apt update sudo apt install -y mysql-server sudo systemctl start mysql sudo systemctl enable mysql EOF ``` - You will need to create an `allow_mysql` security group (for port 3306). - You should also ensure that your output shows the MySQL server public IP address - Test that you can SSH to your MySQL server, and that you can use `lsof -Pni` to verify that your MySQL server is running. ## Telling the web server the database server's IP - A useful feature of Terraform is that it will determine the necessary order to carry out actions. - For example, if you add this into your `user_data` for your web server: ``` echo MYSQL_SERVER_IP=${aws_instance.mysql_server.private_ip} | sudo tee -a /etc/environment ``` - Then Terraform will replace the `${}` construct with the private IP of your database server, including determining that the database server needs to be created before your web server. - You can test this if you want to, but note that you may need your VM to be redeployed to re-run the intitialisation in `user_data`. - The command `terraform taint aws_instance.web_server` will inform Terraform that at the next `apply` the web server should be recreated. ## Use a template file to run setup shell scripting We will replace the `user_data` structure within each VM to a form that instead replaces Terraform variables within an external file, rather than including the shell commands to run within `main.tf`. Your webserver can use a form such as: ``` user_data = templatefile("${path.module}/build-webserver-vm.tpl", { mysql_server_ip = aws_instance.mysql_server.private_ip }) ``` ... and your database seerver can use a form such as: ``` user_data = templatefile("${path.module}/build-dbserver-vm.tpl", { }) ``` ## Test the web server The web server is configured in a slightly different way to the configuration used in the multi-VM Vagrant example that you've seen earlier in the paper. The `index.html` file is at `/var/www/html/`, and the database testing page will be within the directory too. By default, Apache will continue to show the `index.html` page: you need to explicitly add `index.php` onto the URL in order to test that the PHP script installed on your web server VM does indeedd connect to your database server, as expected. ## If you get stuck in the exercises... <details> <summary>Just in case you are really stuck producing a `main.tf` that works, you can reveal one that works, here....</summary> ``` provider "aws" { region = "us-east-1" } resource "aws_security_group" "allow_ssh" { name = "allow_ssh" description = "Allow inbound SSH traffic" ingress { description = "SSH from anywhere" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } resource "aws_security_group" "allow_web" { name = "allow_web" description = "Allow inbound HTTP(S) traffic" ingress { description = "HTTP from anywhere" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "HTTPS from anywhere" from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } resource "aws_security_group" "allow_mysql" { name = "allow_mysql" description = "Allow inbound MySQL traffic" ingress { description = "MySQL from anywhere" from_port = 3306 to_port = 3306 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } resource "aws_instance" "web_server" { ami = "ami-0360c520857e3138f" instance_type = "t2.micro" key_name = "cosc349-2024" vpc_security_group_ids = [aws_security_group.allow_ssh.id, aws_security_group.allow_web.id] user_data = templatefile("${path.module}/build-webserver-vm.tpl", { mysql_server_ip = aws_instance.mysql_server.private_ip }) tags = { Name = "WebServer" } } resource "aws_instance" "mysql_server" { ami = "ami-0360c520857e3138f" instance_type = "t2.micro" key_name = "cosc349-2024" vpc_security_group_ids = [aws_security_group.allow_ssh.id, aws_security_group.allow_mysql.id] user_data = templatefile("${path.module}/build-dbserver-vm.tpl", { }) tags = { Name = "MySQLServer" } } output "web_server_ip" { value = aws_instance.web_server.public_ip } output "mysql_server_ip" { value = aws_instance.mysql_server.public_ip } ``` </details> ## Destroy your EC2 instance - When you wish to remove your EC2 instance, the `terraform destroy` command should work to remove resources, after you confirm this action. - You should also check on the EC2 Dashboard that your instance really has been terminated... and if it hasn't been, you can always terminate it from the EC2 Dashboard manually.

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully